diff --git a/core-tiger/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractAnnotationMethodEndpointMapping.java b/core-tiger/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractAnnotationMethodEndpointMapping.java index b98d20f7..7179046e 100644 --- a/core-tiger/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractAnnotationMethodEndpointMapping.java +++ b/core-tiger/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractAnnotationMethodEndpointMapping.java @@ -19,23 +19,34 @@ package org.springframework.ws.server.endpoint.mapping; import java.lang.annotation.Annotation; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.ws.server.endpoint.annotation.Endpoint; /** * Abstract base for {@link org.springframework.ws.server.EndpointMapping} implementations that map classes tagged with * an annotation. By default the annotation is {@link Endpoint}, but this can be overriden in subclasses. *
- * The methods of each bean carrying @Endpoint will be registered using {@link #registerMethods(Object)}. + * The methods of each bean carrying @Endpoint will be registered using {@link #registerMethods(String)}. * * @author Arjen Poutsma * @since 1.0.0 */ -public abstract class AbstractAnnotationMethodEndpointMapping extends AbstractMethodEndpointMapping - implements BeanPostProcessor { +public abstract class AbstractAnnotationMethodEndpointMapping extends AbstractMethodEndpointMapping { - public final Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - return bean; + private boolean detectEndpointsInAncestorContexts = false; + + /** + * Set whether to detect endpoint beans in ancestor ApplicationContexts. + * + * Default is "false": Only endpoint beans in the current ApplicationContext will be detected, i.e. only in the + * context that this EndpointMapping itself is defined in (typically the current MessageDispatcherServlet's + * context). + * + * Switch this flag on to detect endpoint beans in ancestor contexts (typically the Spring root + * WebApplicationContext) as well. + */ + public void setDetectEndpointsInAncestorContexts(boolean detectEndpointsInAncestorContexts) { + this.detectEndpointsInAncestorContexts = detectEndpointsInAncestorContexts; } /** Returns the 'endpoint' annotation type. Default is {@link Endpoint}. */ @@ -43,12 +54,21 @@ public abstract class AbstractAnnotationMethodEndpointMapping extends AbstractMe return Endpoint.class; } - public final Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - Class endpointClass = getEndpointClass(bean); - if (endpointClass != null && endpointClass.getAnnotation(getEndpointAnnotationType()) != null) { - registerMethods(bean); + protected final void initApplicationContext() throws BeansException { + if (logger.isDebugEnabled()) { + logger.debug("Looking for endpoints in application context: " + getApplicationContext()); + } + String[] beanNames = (this.detectEndpointsInAncestorContexts ? + BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : + getApplicationContext().getBeanNamesForType(Object.class)); + + for (int i = 0; i < beanNames.length; i++) { + String beanName = beanNames[i]; + Class endpointClass = getApplicationContext().getType(beanName); + if (endpointClass != null && endpointClass.getAnnotation(getEndpointAnnotationType()) != null) { + registerMethods(beanName); + } } - return bean; } } diff --git a/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/OtherBean.java b/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/OtherBean.java new file mode 100644 index 00000000..40f30e0d --- /dev/null +++ b/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/OtherBean.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008 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.mapping; + +import org.springframework.ws.server.endpoint.annotation.PayloadRoot; + +class OtherBean { + + @PayloadRoot(localPart = "Request2", namespace = "http://springframework.org/spring-ws") + public void doIt() { + + } + +} diff --git a/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/PayloadRootAnnotationMethodEndpointMappingTest.java b/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/PayloadRootAnnotationMethodEndpointMappingTest.java index 85939c94..ac6b635b 100644 --- a/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/PayloadRootAnnotationMethodEndpointMappingTest.java +++ b/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/PayloadRootAnnotationMethodEndpointMappingTest.java @@ -16,11 +16,12 @@ package org.springframework.ws.server.endpoint.mapping; +import java.lang.reflect.Method; + import junit.framework.TestCase; + import org.springframework.context.support.StaticApplicationContext; import org.springframework.ws.server.endpoint.MethodEndpoint; -import org.springframework.ws.server.endpoint.annotation.Endpoint; -import org.springframework.ws.server.endpoint.annotation.PayloadRoot; public class PayloadRootAnnotationMethodEndpointMappingTest extends TestCase { @@ -31,7 +32,7 @@ public class PayloadRootAnnotationMethodEndpointMappingTest extends TestCase { protected void setUp() throws Exception { applicationContext = new StaticApplicationContext(); applicationContext.registerSingleton("mapping", PayloadRootAnnotationMethodEndpointMapping.class); - applicationContext.registerSingleton("endpoint", MyEndpoint.class); + applicationContext.registerSingleton("endpoint", PayloadRootEndpoint.class); applicationContext.registerSingleton("other", OtherBean.class); applicationContext.refresh(); mapping = (PayloadRootAnnotationMethodEndpointMapping) applicationContext.getBean("mapping"); @@ -40,30 +41,12 @@ public class PayloadRootAnnotationMethodEndpointMappingTest extends TestCase { public void testRegistration() throws NoSuchMethodException { MethodEndpoint endpoint = mapping.lookupEndpoint("{http://springframework.org/spring-ws}Request"); assertNotNull("MethodEndpoint not registered", endpoint); - MethodEndpoint expected = new MethodEndpoint(applicationContext.getBean("endpoint"), "doIt", new Class[0]); + Method doIt = PayloadRootEndpoint.class.getMethod("doIt", new Class[0]); + MethodEndpoint expected = new MethodEndpoint("endpoint", applicationContext, doIt); assertEquals("Invalid endpoint registered", expected, endpoint); assertNull("Invalid endpoint registered", mapping.lookupEndpoint("{http://springframework.org/spring-ws}Request2")); } - @Endpoint - private static class MyEndpoint { - - @PayloadRoot(localPart = "Request", namespace = "http://springframework.org/spring-ws") - public void doIt() { - - } - - } - - private static class OtherBean { - - @PayloadRoot(localPart = "Request2", namespace = "http://springframework.org/spring-ws") - public void doIt() { - - } - - } - } \ No newline at end of file diff --git a/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/PayloadRootEndpoint.java b/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/PayloadRootEndpoint.java new file mode 100644 index 00000000..1299219d --- /dev/null +++ b/core-tiger/src/test/java/org/springframework/ws/server/endpoint/mapping/PayloadRootEndpoint.java @@ -0,0 +1,30 @@ +/* + * Copyright 2008 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.mapping; + +import org.springframework.ws.server.endpoint.annotation.Endpoint; +import org.springframework.ws.server.endpoint.annotation.PayloadRoot; + +@Endpoint +class PayloadRootEndpoint { + + @PayloadRoot(localPart = "Request", namespace = "http://springframework.org/spring-ws") + public void doIt() { + + } + +} diff --git a/core-tiger/src/test/java/org/springframework/ws/soap/server/endpoint/mapping/SoapActionAnnotationMethodEndpointMappingTest.java b/core-tiger/src/test/java/org/springframework/ws/soap/server/endpoint/mapping/SoapActionAnnotationMethodEndpointMappingTest.java index c12dfe99..9d44b840 100644 --- a/core-tiger/src/test/java/org/springframework/ws/soap/server/endpoint/mapping/SoapActionAnnotationMethodEndpointMappingTest.java +++ b/core-tiger/src/test/java/org/springframework/ws/soap/server/endpoint/mapping/SoapActionAnnotationMethodEndpointMappingTest.java @@ -16,8 +16,11 @@ package org.springframework.ws.soap.server.endpoint.mapping; +import java.lang.reflect.Method; + import junit.framework.TestCase; import static org.easymock.EasyMock.*; + import org.springframework.context.support.StaticApplicationContext; import org.springframework.ws.WebServiceMessageFactory; import org.springframework.ws.context.DefaultMessageContext; @@ -43,18 +46,19 @@ public class SoapActionAnnotationMethodEndpointMappingTest extends TestCase { } public void testRegistration() throws Exception { - SoapMessage requestMock = createMock(SoapMessage.class); - expect(requestMock.getSoapAction()).andReturn("http://springframework.org/spring-ws/SoapAction"); - WebServiceMessageFactory factoryMock = createMock(WebServiceMessageFactory.class); - replay(requestMock, factoryMock); + SoapMessage requestMock = createMock(SoapMessage.class); + expect(requestMock.getSoapAction()).andReturn("http://springframework.org/spring-ws/SoapAction"); + WebServiceMessageFactory factoryMock = createMock(WebServiceMessageFactory.class); + replay(requestMock, factoryMock); - MessageContext context = new DefaultMessageContext(requestMock, factoryMock); + MessageContext context = new DefaultMessageContext(requestMock, factoryMock); EndpointInvocationChain chain = mapping.getEndpoint(context); assertNotNull("MethodEndpoint not registered", chain); - MethodEndpoint expected = new MethodEndpoint(applicationContext.getBean("endpoint"), "doIt", new Class[0]); + Method doIt = MyEndpoint.class.getMethod("doIt", new Class[0]); + MethodEndpoint expected = new MethodEndpoint("endpoint", applicationContext, doIt); assertEquals("Invalid endpoint registered", expected, chain.getEndpoint()); - - verify(requestMock,factoryMock); + + verify(requestMock, factoryMock); } @Endpoint diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/MethodEndpoint.java b/core/src/main/java/org/springframework/ws/server/endpoint/MethodEndpoint.java index 7f687afa..5b785bae 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/MethodEndpoint.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/MethodEndpoint.java @@ -19,6 +19,7 @@ package org.springframework.ws.server.endpoint; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.springframework.beans.factory.BeanFactory; import org.springframework.core.JdkVersion; import org.springframework.util.Assert; @@ -32,9 +33,11 @@ import org.springframework.util.Assert; */ public final class MethodEndpoint { - private Object bean; + private final Object bean; - private Method method; + private final Method method; + + private final BeanFactory beanFactory; /** * Constructs a new method endpoint with the given bean and method. @@ -47,6 +50,7 @@ public final class MethodEndpoint { Assert.notNull(method, "method must not be null"); this.bean = bean; this.method = method; + this.beanFactory = null; } /** @@ -62,6 +66,26 @@ public final class MethodEndpoint { Assert.notNull(methodName, "method must not be null"); this.bean = bean; this.method = bean.getClass().getMethod(methodName, parameterTypes); + this.beanFactory = null; + } + + /** + * Constructs a new method endpoint with the given bean name and method. The bean name will be lazily initized when + * {@link #invoke(Object[])} is called. + * + * @param beanName the bean name + * @param beanFactory the bean factory to use for bean initialization + * @param method the method + */ + public MethodEndpoint(String beanName, BeanFactory beanFactory, Method method) { + Assert.hasText(beanName, "'beanName' must not be null"); + Assert.notNull(beanFactory, "'beanFactory' must not be null"); + Assert.notNull(method, "'method' must not be null"); + Assert.isTrue(beanFactory.containsBean(beanName), + "Bean factory [" + beanFactory + "] does not contain bean " + "with name [" + beanName + "]"); + this.bean = beanName; + this.beanFactory = beanFactory; + this.method = method; } /** Returns the object bean for this method endpoint. */ @@ -82,8 +106,13 @@ public final class MethodEndpoint { * @throws Exception when the method invocation results in an exception */ public Object invoke(Object[] args) throws Exception { + Object endpoint = bean; + if (endpoint instanceof String) { + String endpointName = (String) endpoint; + endpoint = beanFactory.getBean(endpointName); + } try { - return this.method.invoke(this.bean, args); + return this.method.invoke(endpoint, args); } catch (InvocationTargetException ex) { handleInvocationTargetException(ex); diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMethodEndpointMapping.java b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMethodEndpointMapping.java index 54a9e39b..5bc5166c 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMethodEndpointMapping.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMethodEndpointMapping.java @@ -124,6 +124,29 @@ public abstract class AbstractMethodEndpointMapping extends AbstractEndpointMapp } } + /** + * Helper method that registers the methods of the given class. This method iterates over the methods of the class, + * and calls {@link #getLookupKeyForMethod(Method)} for each. If this returns a string, the method is registered + * using {@link #registerEndpoint(String,MethodEndpoint)}. + * + * @see #getLookupKeyForMethod(Method) + */ + protected void registerMethods(String beanName) { + Assert.hasText(beanName, "'beanName' must not be empty"); + Class endpointClass = getApplicationContext().getType(beanName); + Method[] methods = endpointClass.getMethods(); + for (int i = 0; i < methods.length; i++) { + if (JdkVersion.isAtLeastJava15() && methods[i].isSynthetic() || + methods[i].getDeclaringClass().equals(Object.class)) { + continue; + } + String key = getLookupKeyForMethod(methods[i]); + if (StringUtils.hasLength(key)) { + registerEndpoint(key, new MethodEndpoint(beanName, getApplicationContext(), methods[i])); + } + } + } + /** * Returns the the endpoint keys for the given method. Returnsnull if the method is not to be
* registered, which is the default.