SWS-351 - Arbitrary parameter injection for @Endpoints
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user