diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.java b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.java new file mode 100644 index 00000000..e25e1a2e --- /dev/null +++ b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.java @@ -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 methodArgumentResolvers; + + private List 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 methodArgumentResolvers) { + this.methodArgumentResolvers = methodArgumentResolvers; + } + + /** Sets the list of {@code MethodReturnValueHandler}s to use. */ + public void setMethodReturnValueHandlers(List 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 methodArgumentResolvers = + strategiesHelper.getDefaultStrategies(MethodArgumentResolver.class); + setMethodArgumentResolvers(methodArgumentResolvers); + } + if (CollectionUtils.isEmpty(methodReturnValueHandlers)) { + List 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. + *

+ * 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. + *

+ * 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"); + } +} diff --git a/core/src/main/java/org/springframework/ws/support/DefaultStrategiesHelper.java b/core/src/main/java/org/springframework/ws/support/DefaultStrategiesHelper.java index 4efb7ea9..144251bd 100644 --- a/core/src/main/java/org/springframework/ws/support/DefaultStrategiesHelper.java +++ b/core/src/main/java/org/springframework/ws/support/DefaultStrategiesHelper.java @@ -111,9 +111,16 @@ public class DefaultStrategiesHelper { if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); result = new ArrayList(classNames.length); + ClassLoader classLoader = null; + if (applicationContext != null) { + classLoader = applicationContext.getClassLoader(); + } + if (classLoader == null) { + classLoader = DefaultStrategiesHelper.class.getClassLoader(); + } for (String className : classNames) { - Class clazz = - (Class) ClassUtils.forName(className, DefaultStrategiesHelper.class.getClassLoader()); + Class clazz = (Class) 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 instantiateBean(Class clazz, ApplicationContext applicationContext) { T strategy = BeanUtils.instantiateClass(clazz); if (strategy instanceof BeanNameAware) { diff --git a/core/src/main/resources/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.properties b/core/src/main/resources/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.properties new file mode 100644 index 00000000..8c89cb51 --- /dev/null +++ b/core/src/main/resources/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.properties @@ -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 \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapterTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapterTest.java new file mode 100644 index 00000000..2f884da6 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapterTest.java @@ -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(""); + 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(""); + 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); + } +}