SWS-351 - Arbitrary parameter injection for @Endpoints

This commit is contained in:
Arjen Poutsma
2010-04-29 13:40:59 +00:00
parent 61741d668c
commit 2f8476ac2a
4 changed files with 398 additions and 3 deletions

View File

@@ -0,0 +1,194 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.server.endpoint.adapter;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.MethodEndpoint;
import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver;
import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler;
import org.springframework.ws.support.DefaultStrategiesHelper;
/**
* Default extension of {@link AbstractMethodEndpointAdapter} with support for pluggable {@linkplain
* MethodArgumentResolver argument resolvers} and {@linkplain MethodReturnValueHandler return value handlers}.
*
* @author Arjen Poutsma
* @since 2.0
*/
public class DefaultMethodEndpointAdapter extends AbstractMethodEndpointAdapter {
private List<MethodArgumentResolver> methodArgumentResolvers;
private List<MethodReturnValueHandler> methodReturnValueHandlers;
/**
* Initializes a {@code DefaultMethodEndpointAdapter} with the default strategies.
*
* @see #initDefaultStrategies()
*/
public DefaultMethodEndpointAdapter() {
initDefaultStrategies();
}
/** Sets the list of {@code MethodArgumentResolver}s to use. */
public void setMethodArgumentResolvers(List<MethodArgumentResolver> methodArgumentResolvers) {
this.methodArgumentResolvers = methodArgumentResolvers;
}
/** Sets the list of {@code MethodReturnValueHandler}s to use. */
public void setMethodReturnValueHandlers(List<MethodReturnValueHandler> methodReturnValueHandlers) {
this.methodReturnValueHandlers = methodReturnValueHandlers;
}
/** Initialize the default implementations for the adapter's strategies */
protected void initDefaultStrategies() {
Resource resource =
new ClassPathResource(ClassUtils.getShortName(DefaultMethodEndpointAdapter.class) + ".properties",
DefaultMethodEndpointAdapter.class);
DefaultStrategiesHelper strategiesHelper = new DefaultStrategiesHelper(resource);
if (CollectionUtils.isEmpty(methodArgumentResolvers)) {
List<MethodArgumentResolver> methodArgumentResolvers =
strategiesHelper.getDefaultStrategies(MethodArgumentResolver.class);
setMethodArgumentResolvers(methodArgumentResolvers);
}
if (CollectionUtils.isEmpty(methodReturnValueHandlers)) {
List<MethodReturnValueHandler> methodReturnValueHandlers =
strategiesHelper.getDefaultStrategies(MethodReturnValueHandler.class);
setMethodReturnValueHandlers(methodReturnValueHandlers);
}
}
@Override
protected boolean supportsInternal(MethodEndpoint methodEndpoint) {
return supportsParameters(methodEndpoint.getMethodParameters()) &&
supportsReturnType(methodEndpoint.getReturnType());
}
private boolean supportsParameters(MethodParameter[] methodParameters) {
for (MethodParameter methodParameter : methodParameters) {
boolean supported = false;
for (MethodArgumentResolver methodArgumentResolver : methodArgumentResolvers) {
if (logger.isTraceEnabled()) {
logger.trace("Testing if endpoint adapter [" + methodArgumentResolver + "] supports [" +
methodParameter.getGenericParameterType() + "]");
}
if (methodArgumentResolver.supportsParameter(methodParameter)) {
supported = true;
break;
}
}
if (!supported) {
return false;
}
}
return true;
}
private boolean supportsReturnType(MethodParameter methodReturnType) {
if (Void.TYPE.equals(methodReturnType.getParameterType())) {
return true;
}
for (MethodReturnValueHandler methodReturnValueHandler : methodReturnValueHandlers) {
if (methodReturnValueHandler.supportsReturnType(methodReturnType)) {
return true;
}
}
return false;
}
@Override
protected final void invokeInternal(MessageContext messageContext, MethodEndpoint methodEndpoint) throws Exception {
Object[] args = getMethodArguments(messageContext, methodEndpoint);
if (logger.isTraceEnabled()) {
StringBuilder builder = new StringBuilder("Invoking [");
builder.append(methodEndpoint).append("] with arguments ");
builder.append(Arrays.asList(args));
logger.trace(builder.toString());
}
Object returnValue = methodEndpoint.invoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + methodEndpoint + "] returned [" + returnValue + "]");
}
Class<?> returnType = methodEndpoint.getMethod().getReturnType();
if (!Void.TYPE.equals(returnType)) {
handleMethodReturnValue(messageContext, returnValue, methodEndpoint);
}
}
/**
* Returns the argument array for the given method endpoint.
* <p/>
* This implementation iterates over the set {@linkplain #setMethodArgumentResolvers(List) argument resolvers} to
* resolve each argument.
*
* @param messageContext the current message context
* @param methodEndpoint the method endpoint to get arguments for
* @return the arguments
* @throws Exception in case of errors
*/
protected Object[] getMethodArguments(MessageContext messageContext, MethodEndpoint methodEndpoint)
throws Exception {
MethodParameter[] parameters = methodEndpoint.getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
for (MethodArgumentResolver methodArgumentResolver : methodArgumentResolvers) {
if (methodArgumentResolver.supportsParameter(parameters[i])) {
args[i] = methodArgumentResolver.resolveArgument(messageContext, parameters[i]);
break;
}
}
}
return args;
}
/**
* Handle the return value for the given method endpoint.
* <p/>
* This implementation iterates over the set {@linkplain #setMethodReturnValueHandler(java.util.List) return value
* handlers} to resolve the return value.
*
* @param messageContext the current message context
* @param returnValue the return value
* @param methodEndpoint the method endpoint to get arguments for
* @throws Exception in case of errors
*/
protected void handleMethodReturnValue(MessageContext messageContext,
Object returnValue,
MethodEndpoint methodEndpoint) throws Exception {
MethodParameter returnType = methodEndpoint.getReturnType();
for (MethodReturnValueHandler methodReturnValueHandler : methodReturnValueHandlers) {
if (methodReturnValueHandler.supportsReturnType(returnType)) {
methodReturnValueHandler.handleReturnValue(messageContext, returnType, returnValue);
return;
}
}
throw new IllegalStateException(
"Return value [" + returnValue + "] not resolved by any MethodReturnValueHandler");
}
}

View File

@@ -111,9 +111,16 @@ public class DefaultStrategiesHelper {
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
result = new ArrayList<T>(classNames.length);
ClassLoader classLoader = null;
if (applicationContext != null) {
classLoader = applicationContext.getClassLoader();
}
if (classLoader == null) {
classLoader = DefaultStrategiesHelper.class.getClassLoader();
}
for (String className : classNames) {
Class<T> clazz =
(Class<T>) ClassUtils.forName(className, DefaultStrategiesHelper.class.getClassLoader());
Class<T> clazz = (Class<T>) ClassUtils.forName(className, classLoader);
Assert.isTrue(strategyInterface.isAssignableFrom(clazz), clazz.getName() + " is not a " + strategyInterface.getName());
T strategy = instantiateBean(clazz, applicationContext);
result.add(strategy);
}
@@ -130,7 +137,7 @@ public class DefaultStrategiesHelper {
}
}
/** Instantiates the given bean, simulating the standard bean lifecycle. */
/** Instantiates the given bean, simulating the standard bean life cycle. */
private <T> T instantiateBean(Class<T> clazz, ApplicationContext applicationContext) {
T strategy = BeanUtils.instantiateClass(clazz);
if (strategy instanceof BeanNameAware) {

View File

@@ -0,0 +1,16 @@
# Default implementation classes for DefaultMethodEndpointAdapter's strategy interfaces.
# Used as fallback when no matching beans are configured.
# Not meant to be customized by application developers.
org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver=org.springframework.ws.server.endpoint.adapter.method.Dom4jPayloadMethodProcessor,\
org.springframework.ws.server.endpoint.adapter.method.DomPayloadMethodProcessor,\
org.springframework.ws.server.endpoint.adapter.method.JDomPayloadMethodProcessor,\
org.springframework.ws.server.endpoint.adapter.method.MessageContextMethodArgumentResolver,\
org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor,\
org.springframework.ws.server.endpoint.adapter.method.StaxPayloadMethodArgumentResolver,\
org.springframework.ws.soap.server.endpoint.adapter.method.SoapMethodArgumentResolver
org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler=org.springframework.ws.server.endpoint.adapter.method.Dom4jPayloadMethodProcessor,\
org.springframework.ws.server.endpoint.adapter.method.DomPayloadMethodProcessor,\
org.springframework.ws.server.endpoint.adapter.method.JDomPayloadMethodProcessor,\
org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.server.endpoint.adapter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.springframework.core.MethodParameter;
import org.springframework.ws.MockWebServiceMessage;
import org.springframework.ws.MockWebServiceMessageFactory;
import org.springframework.ws.context.DefaultMessageContext;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.MethodEndpoint;
import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver;
import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler;
import org.junit.Before;
import org.junit.Test;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
/** @author Arjen Poutsma */
public class DefaultMethodEndpointAdapterTest {
private DefaultMethodEndpointAdapter adapter;
private MethodArgumentResolver argumentResolver1;
private MethodArgumentResolver argumentResolver2;
private MethodReturnValueHandler returnValueHandler;
private MethodEndpoint supportedEndpoint;
private MethodEndpoint unsupportedEndpoint;
private MethodEndpoint exceptionEndpoint;
private String supportedArgument;
@Before
public void setUp() throws Exception {
adapter = new DefaultMethodEndpointAdapter();
argumentResolver1 = createMock("stringResolver", MethodArgumentResolver.class);
argumentResolver2 = createMock("intResolver", MethodArgumentResolver.class);
returnValueHandler = createMock(MethodReturnValueHandler.class);
adapter.setMethodArgumentResolvers(Arrays.asList(argumentResolver1, argumentResolver2));
adapter.setMethodReturnValueHandlers(Collections.singletonList(returnValueHandler));
supportedEndpoint = new MethodEndpoint(this, "supported", String.class, Integer.class);
unsupportedEndpoint = new MethodEndpoint(this, "unsupported", String.class);
exceptionEndpoint = new MethodEndpoint(this, "exception", String.class);
}
@Test
public void supportsSupported() throws Exception {
expect(argumentResolver1.supportsParameter(isA(MethodParameter.class))).andReturn(true);
expect(argumentResolver1.supportsParameter(isA(MethodParameter.class))).andReturn(false);
expect(argumentResolver2.supportsParameter(isA(MethodParameter.class))).andReturn(true);
expect(returnValueHandler.supportsReturnType(isA(MethodParameter.class))).andReturn(true);
replay(argumentResolver1, argumentResolver2, returnValueHandler);
boolean result = adapter.supports(supportedEndpoint);
assertTrue("adapter does not support method", result);
verify(argumentResolver1, argumentResolver2, returnValueHandler);
}
@Test
public void supportsUnsupportedParameter() throws Exception {
expect(argumentResolver1.supportsParameter(isA(MethodParameter.class))).andReturn(false);
expect(argumentResolver2.supportsParameter(isA(MethodParameter.class))).andReturn(false);
replay(argumentResolver1, argumentResolver2, returnValueHandler);
boolean result = adapter.supports(unsupportedEndpoint);
assertFalse("adapter does not support method", result);
verify(argumentResolver1, argumentResolver2, returnValueHandler);
}
@Test
public void supportsUnsupportedReturnType() throws Exception {
expect(argumentResolver1.supportsParameter(isA(MethodParameter.class))).andReturn(true);
expect(returnValueHandler.supportsReturnType(isA(MethodParameter.class))).andReturn(false);
replay(argumentResolver1, argumentResolver2, returnValueHandler);
boolean result = adapter.supports(unsupportedEndpoint);
assertFalse("adapter does not support method", result);
verify(argumentResolver1, argumentResolver2, returnValueHandler);
}
@Test
public void invokeSupported() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage("<root xmlns='http://springframework.org'/>");
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
String value = "Foo";
// arg 0
expect(argumentResolver1.supportsParameter(isA(MethodParameter.class))).andReturn(true);
expect(argumentResolver1.resolveArgument(eq(messageContext), isA(MethodParameter.class))).andReturn(value);
// arg 1
expect(argumentResolver1.supportsParameter(isA(MethodParameter.class))).andReturn(false);
expect(argumentResolver2.supportsParameter(isA(MethodParameter.class))).andReturn(true);
expect(argumentResolver2.resolveArgument(eq(messageContext), isA(MethodParameter.class))).andReturn(new Integer(42));
expect(returnValueHandler.supportsReturnType(isA(MethodParameter.class))).andReturn(true);
returnValueHandler.handleReturnValue(eq(messageContext), isA(MethodParameter.class), eq(value));
replay(argumentResolver1, argumentResolver2, returnValueHandler);
adapter.invoke(messageContext, supportedEndpoint);
assertEquals("Invalid argument passed", value, supportedArgument);
verify(argumentResolver1, argumentResolver2, returnValueHandler);
}
@Test
public void invokeException() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage("<root xmlns='http://springframework.org'/>");
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
String value = "Foo";
expect(argumentResolver1.supportsParameter(isA(MethodParameter.class))).andReturn(true);
expect(argumentResolver1.resolveArgument(eq(messageContext), isA(MethodParameter.class))).andReturn(value);
replay(argumentResolver1, argumentResolver2, returnValueHandler);
try {
adapter.invoke(messageContext, exceptionEndpoint);
fail("IOException expected");
}
catch (IOException expected) {
// expected
}
assertEquals("Invalid argument passed", value, supportedArgument);
verify(argumentResolver1, argumentResolver2, returnValueHandler);
}
public String supported(String s, Integer i) {
supportedArgument = s;
return s;
}
public String unsupported(String s) {
return s;
}
public String exception(String s) throws IOException {
supportedArgument = s;
throw new IOException(s);
}
}