From 66ead00a73238b02e25cb218f7b4d6adc66b2ca6 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Sun, 8 Oct 2006 13:45:21 +0000 Subject: [PATCH] Added MessageDispatcherServlet. --- .../springframework/ws/MessageDispatcher.java | 261 +++++------- .../AbstractEndpointExceptionResolver.java | 7 +- .../ws/soap/SoapMessageDispatcher.java | 20 +- .../http/MessageDispatcherServlet.java | 389 ++++++++++++++++++ .../http/MessageDispatcherServlet.properties | 7 + .../ws/MessageDispatcherTest.java | 3 - .../http/MessageDispatcherServletTest.java | 107 +++++ 7 files changed, 620 insertions(+), 174 deletions(-) create mode 100644 core/src/main/java/org/springframework/ws/transport/http/MessageDispatcherServlet.java create mode 100644 core/src/main/resources/org/springframework/ws/transport/http/MessageDispatcherServlet.properties create mode 100644 core/src/test/java/org/springframework/ws/transport/http/MessageDispatcherServletTest.java diff --git a/core/src/main/java/org/springframework/ws/MessageDispatcher.java b/core/src/main/java/org/springframework/ws/MessageDispatcher.java index de63fa63..87cbb1d7 100644 --- a/core/src/main/java/org/springframework/ws/MessageDispatcher.java +++ b/core/src/main/java/org/springframework/ws/MessageDispatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2005 the original author or authors. + * Copyright 2005, 2006 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. @@ -16,30 +16,21 @@ package org.springframework.ws; -import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.BeanNameAware; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.support.ApplicationObjectSupport; -import org.springframework.core.io.ClassPathResource; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; import org.springframework.ws.context.MessageContext; import org.springframework.ws.endpoint.MessageEndpoint; +import org.springframework.ws.endpoint.MessageEndpointAdapter; +import org.springframework.ws.endpoint.PayloadEndpointAdapter; /** - * Central dispatcher for use withing Spring-WS. Dispatches SOAP messages to registered endoints. + * Central dispatcher for use withing Spring-WS. Dispatches Web service messages to registered endoints. *

* This dispatcher is quite similar to Spring MVCs DispatcherServlet. Just like it's counterpart, this * dispatcher is very flexible.

* A web application can use any number of MessageDispatchers. Since a MessageDispatcher * also implements MessageEndpoint, it is also possible to chain them: one dispatcher can be registered as - * the endpoint of another. + * the endpoint of another, though the MessageEndpointAdapter. * * @author Arjen Poutsma * @see EndpointMapping @@ -61,13 +52,7 @@ import org.springframework.ws.endpoint.MessageEndpoint; * @see EndpointExceptionResolver * @see org.springframework.web.servlet.DispatcherServlet */ -public class MessageDispatcher extends ApplicationObjectSupport implements MessageEndpoint, BeanNameAware { - - /** - * Name of the class path resource (relative to the MessageDispatcher class) that defines MessageDispatcher's - * default strategy names. - */ - private static final String DEFAULT_STRATEGIES_PATH = "MessageDispatcher.properties"; +public class MessageDispatcher implements MessageEndpoint, BeanNameAware { /** * Log category to use when no mapped endpoint is found for a request. @@ -79,31 +64,15 @@ public class MessageDispatcher extends ApplicationObjectSupport implements Messa */ protected static final Log endpointNotFoundLogger = LogFactory.getLog(ENDPOINT_NOT_FOUND_LOG_CATEGORY); - private static final Properties defaultStrategies = new Properties(); - - static { - // Load default strategy implementations from properties file. - // This is currently strictly internal and not meant to be customized - // by application developers. - try { - ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, MessageDispatcher.class); - InputStream is = resource.getInputStream(); - try { - defaultStrategies.load(is); - } - finally { - is.close(); - } - } - catch (IOException ex) { - throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); - } - } + /** + * Logger available to subclasses. + */ + protected final Log logger = LogFactory.getLog(getClass()); /** - * List of EndpointMappings used in this dispatcher + * The registered bean name for this dispatcher. */ - private List endpointMappings; + private String beanName; /** * List of EndpointAdapters used in this dispatcher @@ -116,89 +85,61 @@ public class MessageDispatcher extends ApplicationObjectSupport implements Messa private List endpointExceptionResolvers; /** - * The registered bean name for this dispatcher. + * List of EndpointMappings used in this dispatcher */ - private String beanName; + private List endpointMappings; /** - * Initializes the dispatcher. + * Initializes a new instance of the MessageDispatcher. */ - public void initApplicationContext() throws BeansException { - initEndpointMappings(); - initEndpointAdapters(); - initEndpointExceptionResolvers(); + public MessageDispatcher() { + initDefaultStrategies(); } /** - * Initialize the EndpointMappings used in this class. + * Returns the EndpointAdapters to use by this MessageDispatcher. */ - private void initEndpointMappings() { - // Ensure we have at least one EndpointMapping, by registering a default if not others are found - if (endpointMappings == null) { - endpointMappings = getDefaultStrategies(EndpointMapping.class); - logger.info("No EndpointMapping found: using default"); - } + public List getEndpointAdapters() { + return endpointAdapters; } /** - * Initialize the EndpointAdapters used by this class. + * Sets the EndpointAdapters to use by this MessageDispatcher. */ - private void initEndpointAdapters() { - if (endpointAdapters == null) { - // Ensure we have at least some EndpointAdapters, by registereing a default if no others are found - endpointAdapters = getDefaultStrategies(EndpointAdapter.class); - logger.info("No EndpointAdapters found: using default"); - } + public void setEndpointAdapters(List endpointAdapters) { + this.endpointAdapters = endpointAdapters; } /** - * Initialize the EndpointExceptionResolvers used in this class. + * Returns the EndpointExceptionResolvers to use by this MessageDispatcher. */ - private void initEndpointExceptionResolvers() { - if (endpointExceptionResolvers == null) { - // Ensure we have at least some EndpointExceptionResolvers, by registereing a default if no others are found - endpointAdapters = getDefaultStrategies(EndpointExceptionResolver.class); - logger.info("No EndpointExceptionResolver found: using default"); - } + public List getEndpointExceptionResolvers() { + return endpointExceptionResolvers; } /** - * Create a List of default strategy objects for the given strategy interface.

The default implementation uses - * the "MessageDispatcher.properties" file (in the same package as the MessageDispatcher class) to determine the - * class names. It instantiates the strategy objects and satisifies ApplicationContextAware and InitializingBean if - * necessary. - * - * @param strategyInterface the strategy interface - * @return the List of corresponding strategy objects - * @throws BeansException if initialization failed + * Sets the EndpointExceptionResolvers to use by this MessageDispatcher. */ - protected List getDefaultStrategies(Class strategyInterface) throws BeansException { - String key = strategyInterface.getName(); - try { - List strategies = null; - String value = defaultStrategies.getProperty(key); - if (value != null) { - String[] classNames = StringUtils.commaDelimitedListToStringArray(value); - strategies = new ArrayList(classNames.length); - for (int i = 0; i < classNames.length; i++) { - Class clazz = Class.forName(classNames[i], true, getClass().getClassLoader()); - Object strategy = BeanUtils.instantiateClass(clazz); - if (strategy instanceof ApplicationContextAware) { - ((ApplicationContextAware) strategy).setApplicationContext(getApplicationContext()); - } - strategies.add(strategy); - } - } - else { - strategies = Collections.EMPTY_LIST; - } - return strategies; - } - catch (ClassNotFoundException ex) { - throw new BeanInitializationException( - "Could not find MessageDispatcher's default strategy class for interface [" + key + "]", - ex); - } + public void setEndpointExceptionResolvers(List endpointExceptionResolvers) { + this.endpointExceptionResolvers = endpointExceptionResolvers; + } + + /** + * Returns the EndpointMappings to use by this MessageDispatcher. + */ + public List getEndpointMappings() { + return endpointMappings; + } + + /** + * Sets the EndpointMappings to use by this MessageDispatcher. + */ + public void setEndpointMappings(List endpointMappings) { + this.endpointMappings = endpointMappings; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; } public void invoke(MessageContext messageContext) throws Exception { @@ -263,43 +204,6 @@ public class MessageDispatcher extends ApplicationObjectSupport implements Messa } } - /** - * Callback for pre-processing of given invocation chain and message context. Gets called before invocation of - * handleRequest on the interceptors. - *

- * Default implementation does nothing, and returns true. - * - * @param mappedEndpoint the mapped EndpointInvocationChain - * @param messageContext the message context - * @return true if processing should continue; false otherwise - */ - protected boolean handleRequest(EndpointInvocationChain mappedEndpoint, MessageContext messageContext) { - return true; - } - - /** - * Trigger handleResponse or handleFault on the mapped EndpointInterceptors. Will just invoke said method on all - * interceptors whose handleRequest invocation returned true, in addition to the last interceptor who - * returned false. - * - * @param mappedEndpoint the mapped EndpointInvocationChain - * @param interceptorIndex index of last interceptor that was called - * @param messageContext the message context, whose request and response are filled - * @see EndpointInterceptor#handleResponse(MessageContext, Object) - */ - protected void triggerHandleResponse(EndpointInvocationChain mappedEndpoint, - int interceptorIndex, - MessageContext messageContext) throws Exception { - if (mappedEndpoint != null && messageContext.hasResponse() && - !ObjectUtils.isEmpty(mappedEndpoint.getInterceptors())) { - boolean resume = true; - for (int i = interceptorIndex; resume && i >= 0; i--) { - EndpointInterceptor interceptor = mappedEndpoint.getInterceptors()[i]; - resume = interceptor.handleResponse(messageContext, mappedEndpoint.getEndpoint()); - } - } - } - /** * Returns the endpoint for this request. All endpoint mappings are tried, in order. * @@ -319,7 +223,6 @@ public class MessageDispatcher extends ApplicationObjectSupport implements Messa else if (logger.isDebugEnabled()) { logger.debug("Endpoint mapping [" + endpointMapping + "] has no mapping for request"); } - } return null; } @@ -344,6 +247,35 @@ public class MessageDispatcher extends ApplicationObjectSupport implements Messa "supported interface like MessageHandler or PayloadEndpoint?"); } + /** + * Callback for pre-processing of given invocation chain and message context. Gets called before invocation of + * handleRequest on the interceptors. + *

+ * Default implementation does nothing, and returns true. + * + * @param mappedEndpoint the mapped EndpointInvocationChain + * @param messageContext the message context + * @return true if processing should continue; false otherwise + */ + protected boolean handleRequest(EndpointInvocationChain mappedEndpoint, MessageContext messageContext) { + return true; + } + + /** + * Initialize the default implementations for the dispatcher's strategies: MessageEndpointAdapter and + * PayloadEndpointAdapter. + * + * @see #setEndpointAdapters(java.util.List) + * @see org.springframework.ws.endpoint.MessageEndpointAdapter + * @see org.springframework.ws.endpoint.PayloadEndpointAdapter + */ + protected void initDefaultStrategies() { + List defaultEndpointAdapters = new ArrayList(); + defaultEndpointAdapters.add(new MessageEndpointAdapter()); + defaultEndpointAdapters.add(new PayloadEndpointAdapter()); + setEndpointAdapters(defaultEndpointAdapters); + } + /** * Determine an error SOAPMessage respone via the registered EndpointExceptionResolvers. * Most likely, the response contains a SOAPFault. If no suitable resolver was found, the exception is @@ -371,28 +303,25 @@ public class MessageDispatcher extends ApplicationObjectSupport implements Messa } /** - * Sets the EndpointMappings to use by this MessageDispatcher. + * Trigger handleResponse or handleFault on the mapped EndpointInterceptors. Will just invoke said method on all + * interceptors whose handleRequest invocation returned true, in addition to the last interceptor who + * returned false. + * + * @param mappedEndpoint the mapped EndpointInvocationChain + * @param interceptorIndex index of last interceptor that was called + * @param messageContext the message context, whose request and response are filled + * @see EndpointInterceptor#handleResponse(MessageContext,Object) */ - public void setEndpointMappings(List endpointMappings) { - this.endpointMappings = endpointMappings; + protected void triggerHandleResponse(EndpointInvocationChain mappedEndpoint, + int interceptorIndex, + MessageContext messageContext) throws Exception { + if (mappedEndpoint != null && messageContext.hasResponse() && + !ObjectUtils.isEmpty(mappedEndpoint.getInterceptors())) { + boolean resume = true; + for (int i = interceptorIndex; resume && i >= 0; i--) { + EndpointInterceptor interceptor = mappedEndpoint.getInterceptors()[i]; + resume = interceptor.handleResponse(messageContext, mappedEndpoint.getEndpoint()); + } + } } - - /** - * Sets the EndpointAdapters to use by this MessageDispatcher. - */ - public void setEndpointAdapters(List endpointAdapters) { - this.endpointAdapters = endpointAdapters; - } - - /** - * Sets the EndpointExceptionResolvers to use by this MessageDispatcher. - */ - public void setEndpointExceptionResolvers(List endpointExceptionResolvers) { - this.endpointExceptionResolvers = endpointExceptionResolvers; - } - - public void setBeanName(String beanName) { - this.beanName = beanName; - } - } diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractEndpointExceptionResolver.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractEndpointExceptionResolver.java index a171bf7f..79de9a1e 100644 --- a/core/src/main/java/org/springframework/ws/endpoint/AbstractEndpointExceptionResolver.java +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractEndpointExceptionResolver.java @@ -20,7 +20,6 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.ws.EndpointExceptionResolver; import org.springframework.ws.context.MessageContext; @@ -52,10 +51,10 @@ public abstract class AbstractEndpointExceptionResolver implements EndpointExcep * Default implementation. Checks whether the given endpoint is in the set of mapped endpoints. Calls * resolveExceptionInternal. * - * @see #resolveExceptionInternal(org.springframework.ws.context.MessageContext, Object, Exception) + * @see #resolveExceptionInternal(org.springframework.ws.context.MessageContext,Object,Exception) */ public final boolean resolveException(MessageContext messageContext, Object endpoint, Exception ex) { - if (this.mappedEndpoints != null && !this.mappedEndpoints.contains(endpoint)) { + if (mappedEndpoints != null && !mappedEndpoints.contains(endpoint)) { return false; } return resolveExceptionInternal(messageContext, endpoint, ex); @@ -68,7 +67,7 @@ public abstract class AbstractEndpointExceptionResolver implements EndpointExcep * @param endpoint the executed endpoint, or null if none chosen at the time of the exception * @param ex the exception that got thrown during endpoint execution * @return true if resolved; false otherwise - * @see #resolveException(org.springframework.ws.context.MessageContext, Object, Exception) + * @see #resolveException(org.springframework.ws.context.MessageContext,Object,Exception) */ protected abstract boolean resolveExceptionInternal(MessageContext messageContext, Object endpoint, Exception ex); diff --git a/core/src/main/java/org/springframework/ws/soap/SoapMessageDispatcher.java b/core/src/main/java/org/springframework/ws/soap/SoapMessageDispatcher.java index 451b2bf6..feb98f09 100644 --- a/core/src/main/java/org/springframework/ws/soap/SoapMessageDispatcher.java +++ b/core/src/main/java/org/springframework/ws/soap/SoapMessageDispatcher.java @@ -17,6 +17,7 @@ package org.springframework.ws.soap; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -29,6 +30,7 @@ import org.springframework.ws.EndpointInvocationChain; import org.springframework.ws.MessageDispatcher; import org.springframework.ws.context.MessageContext; import org.springframework.ws.soap.context.SoapMessageContext; +import org.springframework.ws.soap.endpoint.SimpleSoapExceptionResolver; import org.springframework.ws.soap.soap12.Soap12Header; /** @@ -68,6 +70,22 @@ public class SoapMessageDispatcher extends MessageDispatcher { this.mustUnderstandFaultLocale = mustUnderstandFaultLocale; } + /** + * Initialize the default implementations for the dispatcher's strategies: MessageEndpointAdapter and + * PayloadEndpointAdapter as endpoint adapters, and a SimpleSoapExceptionResolver as + * exception resolver. + * + * @see #setEndpointAdapters(java.util.List) + * @see #setEndpointExceptionResolvers(java.util.List) + * @see org.springframework.ws.endpoint.MessageEndpointAdapter + * @see org.springframework.ws.endpoint.PayloadEndpointAdapter + * @see org.springframework.ws.soap.endpoint.SimpleSoapExceptionResolver + */ + protected void initDefaultStrategies() { + super.initDefaultStrategies(); + setEndpointExceptionResolvers(Collections.singletonList(new SimpleSoapExceptionResolver())); + } + /** * Process the MustUnderstand headers in the incoming SOAP request message. Iterates over all SOAP * headers which should be understood, and determines whether these are supported. Generates a SOAP MustUnderstand @@ -183,7 +201,7 @@ public class SoapMessageDispatcher extends MessageDispatcher { * @param interceptorIndex index of last interceptor that was called * @param messageContext the message context, whose request and response are filled * @see org.springframework.ws.EndpointInterceptor#handleResponse(org.springframework.ws.context.MessageContext, - * Object) + *Object) */ protected void triggerHandleResponse(EndpointInvocationChain mappedEndpoint, int interceptorIndex, diff --git a/core/src/main/java/org/springframework/ws/transport/http/MessageDispatcherServlet.java b/core/src/main/java/org/springframework/ws/transport/http/MessageDispatcherServlet.java new file mode 100644 index 00000000..1465e24c --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/http/MessageDispatcherServlet.java @@ -0,0 +1,389 @@ +package org.springframework.ws.transport.http; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.OrderComparator; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.FrameworkServlet; +import org.springframework.ws.EndpointAdapter; +import org.springframework.ws.EndpointExceptionResolver; +import org.springframework.ws.EndpointMapping; +import org.springframework.ws.MessageDispatcher; +import org.springframework.ws.context.MessageContextFactory; + +/** + * Servlet for simplified dispatching of Web service messages. Delegates to a MessageDispatcher and a + * MessageEndpointHandlerAdapter. + *

+ * This servlet is a convenient alternative for a standard Spring-MVC DispatcherServlet with a separate + * MessageEndpointHandlerAdapter and a MessageDispatcher. + *

+ * This servlet automatically detects EndpointAdapters, EndpointMappings, and + * EndpointExceptionResolvers, by name or by type. For instance, when the + * detectAllEndpointAdapters propery is true (the default), all endpoint adapters defined in + * the application context are registered with the message dispatcher. When it is set to false, this + * servlet tries to find a bean with the "endpointAdapter" bean name in the context. + * + * @author Arjen Poutsma + * @see org.springframework.web.servlet.DispatcherServlet + * @see org.springframework.ws.MessageDispatcher + * @see org.springframework.ws.transport.http.MessageEndpointHandlerAdapter + */ +public class MessageDispatcherServlet extends FrameworkServlet { + + /** + * Well-known name for the EndpointAdapter object in the bean factory for this namespace. Only used + * when detectAllEndpointAdapters is turned off. + * + * @see #setDetectAllEndpointAdapters(boolean) + * @see org.springframework.ws.EndpointAdapter + */ + public static final String ENDPOINT_ADAPTER_BEAN_NAME = "endpointAdapter"; + + /** + * Well-known name for the EndpointExceptionResolver object in the bean factory for this namespace. + * Only used when "detectAllEndpointExceptionResolvers" is turned off. + * + * @see #setDetectAllEndpointExceptionResolvers + */ + public static final String ENDPOINT_EXCEPTION_RESOLVER_BEAN_NAME = "endpointExceptionResolver"; + + /** + * Well-known name for the EndpointMapping object in the bean factory for this namespace. Only used + * when "detectAllEndpointMappings" is turned off. + * + * @see #setDetectAllEndpointMappings + */ + public static final String ENDPOINT_MAPPING_BEAN_NAME = "endpointMapping"; + + /** + * Well-known name for the MessageContextFactory object in the bean factory for this namespace. + */ + public static final String MESSAGE_CONTEXT_FACTORY_BEAN_NAME = "messageContextFactory"; + + /** + * Well-known name for the MessageDispatcher object in the bean factory for this namespace. + */ + public static final String MESSAGE_DISPATCHER_BEAN_NAME = "messageDispatcher"; + + /** + * Name of the class path resource (relative to the MessageDispatcherServlet class) that defines + * MessageDispatcherServlet's default strategy names. + */ + private static final String DEFAULT_STRATEGIES_PATH = "MessageDispatcherServlet.properties"; + + private static final Properties defaultStrategies = new Properties(); + + /** + * Detect all EndpointAdapters or just expect "endpointAdapter" bean? + */ + private boolean detectAllEndpointAdapters = true; + + /** + * Detect all EndpointExceptionResolvers or just expect "endpointExceptionResolver" bean? + */ + private boolean detectAllEndpointExceptionResolvers = true; + + /** + * Detect all EndpointMappings or just expect "endpointMapping" bean? + */ + private boolean detectAllEndpointMappings = true; + + /** + * The MessageEndpointHandlerAdapter used by this servlet. + */ + private MessageEndpointHandlerAdapter handlerAdapter = new MessageEndpointHandlerAdapter(); + + /** + * The MessageDispatcher used by this servlet. + */ + private MessageDispatcher messageDispatcher; + + static { + // Load default strategy implementations from properties file. + // This is currently strictly internal and not meant to be customized + // by application developers. + try { + ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, MessageDispatcherServlet.class); + InputStream is = resource.getInputStream(); + try { + defaultStrategies.load(is); + } + finally { + is.close(); + } + } + catch (IOException ex) { + throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); + } + } + + /** + * Returns the MessageDispatcher used by this servlet. + */ + protected MessageDispatcher getMessageDispatcher() { + return messageDispatcher; + } + + /** + * Set whether to detect all EndpointAdapter beans in this servlet's context. Else, just a single bean + * with name "endpointAdapter" will be expected. + *

+ * Default is true. Turn this off if you want this servlet to use a single adapter, despite multiple + * adapter beans being defined in the context. + * + * @see org.springframework.ws.EndpointAdapter + */ + public void setDetectAllEndpointAdapters(boolean detectAllEndpointAdapters) { + this.detectAllEndpointAdapters = detectAllEndpointAdapters; + } + + /** + * Set whether to detect all EndpointExceptionResolver beans in this servlet's context. Else, just a + * single bean with name "endpointExceptionResolver" will be expected. + *

+ * Default is true. Turn this off if you want this servlet to use a single exception resolver, despite + * multiple resolver beans being defined in the context. + * + * @see org.springframework.ws.EndpointExceptionResolver + */ + public void setDetectAllEndpointExceptionResolvers(boolean detectAllEndpointExceptionResolvers) { + this.detectAllEndpointExceptionResolvers = detectAllEndpointExceptionResolvers; + } + + /** + * Set whether to detect all EndpointMapping beans in this servlet's context. Else, just a single bean + * with name "endpointMapping" will be expected. + *

+ * Default is true. Turn this off if you want this servlet to use a single mapping, despite multiple + * mapping beans being defined in the context. + * + * @see org.springframework.ws.EndpointMapping + */ + public void setDetectAllEndpointMappings(boolean detectAllEndpointMappings) { + this.detectAllEndpointMappings = detectAllEndpointMappings; + } + + protected long getLastModified(HttpServletRequest req) { + return handlerAdapter.getLastModified(req, messageDispatcher); + } + + protected void initFrameworkServlet() throws ServletException, BeansException { + initMessageContextFactory(); + initMessageDispatcher(); + } + + protected void doService(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) + throws Exception { + handlerAdapter.handle(httpServletRequest, httpServletResponse, messageDispatcher); + } + + /** + * Create a List of default strategy objects for the given strategy interface. + *

+ * The default implementation uses the "MessageDispatcherServlet.properties" file (in the same package as the + * MessageDispatcherServlet class) to determine the class names. It instantiates the strategy objects and satisifies + * ApplicationContextAware if necessary. + * + * @param strategyInterface the strategy interface + * @return the List of corresponding strategy objects + * @throws BeansException if initialization failed + * @see #DEFAULT_STRATEGIES_PATH + */ + protected List getDefaultStrategies(Class strategyInterface) throws BeansException { + String key = strategyInterface.getName(); + try { + List strategies = null; + String value = defaultStrategies.getProperty(key); + if (value != null) { + String[] classNames = StringUtils.commaDelimitedListToStringArray(value); + strategies = new ArrayList(classNames.length); + for (int i = 0; i < classNames.length; i++) { + Class clazz = Class.forName(classNames[i], true, getClass().getClassLoader()); + Object strategy = BeanUtils.instantiateClass(clazz); + if (strategy instanceof ApplicationContextAware) { + ((ApplicationContextAware) strategy).setApplicationContext(getWebApplicationContext()); + } + strategies.add(strategy); + } + } + else { + strategies = Collections.EMPTY_LIST; + } + return strategies; + } + catch (ClassNotFoundException ex) { + throw new BeanInitializationException( + "Could not find DispatcherServlet's default strategy class for interface [" + key + "]", ex); + } + } + + /** + * Return the default strategy object for the given strategy interface. + *

+ * Default implementation delegates to getDefaultStrategies, expecting a single object in the list. + * + * @param strategyInterface the strategy interface + * @return the corresponding strategy object + * @throws BeansException if initialization failed + * @see #getDefaultStrategies + */ + protected Object getDefaultStrategy(Class strategyInterface) throws BeansException { + List strategies = getDefaultStrategies(strategyInterface); + if (strategies.size() != 1) { + throw new BeanInitializationException( + "MessageDispatcherServlet needs exactly 1 strategy for [" + strategyInterface.getName() + "]"); + } + return strategies.get(0); + } + + /** + * Initialize the EndpointAdapters used by this class. If no adapter beans are defined in the bean + * factory for this namespace, we default to the strategies in MessageDispatcher. + * + * @see org.springframework.ws.MessageDispatcher#initDefaultStrategies() + */ + private void initEndpointAdapters() throws BeansException { + if (detectAllEndpointAdapters) { + // Find all EndpointAdapters in the ApplicationContext, including ancestor contexts. + Map matchingBeans = BeanFactoryUtils + .beansOfTypeIncludingAncestors(getWebApplicationContext(), EndpointAdapter.class, true, false); + if (!matchingBeans.isEmpty()) { + List endpointAdapters = new ArrayList(matchingBeans.values()); + // We keep EndpointAdapters in sorted order. + Collections.sort(endpointAdapters, new OrderComparator()); + messageDispatcher.setEndpointAdapters(endpointAdapters); + } + } + else { + try { + Object endointAdapter = + getWebApplicationContext().getBean(ENDPOINT_ADAPTER_BEAN_NAME, EndpointAdapter.class); + messageDispatcher.setEndpointAdapters(Collections.singletonList(endointAdapter)); + } + catch (NoSuchBeanDefinitionException ex) { + // Ignore, we'll use the default adapters of MessageDispatcher + } + } + } + + /** + * Initialize the EndpointExceptionResolver used by this class. If no bean is defined with the given + * name in the BeanFactory for this namespace, we default to the strategies in MessageDispatcher. + */ + private void initEndpointExceptionResolvers() throws BeansException { + if (detectAllEndpointExceptionResolvers) { + // Find all EndpointExceptionResolvers in the ApplicationContext, including ancestor contexts. + Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(getWebApplicationContext(), + EndpointExceptionResolver.class, true, false); + if (!matchingBeans.isEmpty()) { + List endpointExceptionResolvers = new ArrayList(matchingBeans.values()); + // We keep EndpointExceptionResolvers in sorted order. + Collections.sort(endpointExceptionResolvers, new OrderComparator()); + messageDispatcher.setEndpointExceptionResolvers(endpointExceptionResolvers); + } + } + else { + try { + Object endpointExceptionResolver = getWebApplicationContext() + .getBean(ENDPOINT_EXCEPTION_RESOLVER_BEAN_NAME, EndpointExceptionResolver.class); + messageDispatcher.setEndpointExceptionResolvers(Collections.singletonList(endpointExceptionResolver)); + } + catch (NoSuchBeanDefinitionException ex) { + // Ignore, we'll use the default adapters of MessageDispatcher + } + } + } + + /** + * Initialize the EndpointMappings used by this class. If no mapping beans are defined in the + * BeanFactory for this namespace, we default to the strategies in MessageDispatcher. + * + * @see org.springframework.ws.MessageDispatcher#initDefaultStrategies() + */ + private void initEndpointMappings() throws BeansException { + if (detectAllEndpointMappings) { + // Find all EndpointMappings in the ApplicationContext, including ancestor contexts. + Map matchingBeans = BeanFactoryUtils + .beansOfTypeIncludingAncestors(getWebApplicationContext(), EndpointMapping.class, true, false); + if (!matchingBeans.isEmpty()) { + List endpointMappings = new ArrayList(matchingBeans.values()); + // We keep EndpointMappings in sorted order. + Collections.sort(endpointMappings, new OrderComparator()); + messageDispatcher.setEndpointMappings(endpointMappings); + } + } + else { + try { + Object endpointMapping = + getWebApplicationContext().getBean(ENDPOINT_MAPPING_BEAN_NAME, EndpointMapping.class); + messageDispatcher.setEndpointMappings(Collections.singletonList(endpointMapping)); + } + catch (NoSuchBeanDefinitionException ex) { + // Ignore, we'll use the default adapters of MessageDispatcher + } + } + } + + private void initMessageContextFactory() throws BeansException { + MessageContextFactory messageContextFactory; + try { + messageContextFactory = (MessageContextFactory) getWebApplicationContext() + .getBean(MESSAGE_CONTEXT_FACTORY_BEAN_NAME, MessageContextFactory.class); + } + catch (NoSuchBeanDefinitionException ignored) { + messageContextFactory = (MessageContextFactory) getDefaultStrategy(MessageContextFactory.class); + if (logger.isInfoEnabled()) { + logger.info("Unable to locate MessageContextFactory with name '" + MESSAGE_CONTEXT_FACTORY_BEAN_NAME + + "': using default [" + messageContextFactory + "]"); + } + if (messageContextFactory instanceof InitializingBean) { + try { + ((InitializingBean) messageContextFactory).afterPropertiesSet(); + } + catch (Exception ex) { + throw new BeanInitializationException( + "Could not invoke afterPropertiesSet() on messageContextFactory", ex); + } + } + } + handlerAdapter.setMessageContextFactory(messageContextFactory); + } + + private void initMessageDispatcher() { + try { + messageDispatcher = (MessageDispatcher) getWebApplicationContext() + .getBean(MESSAGE_DISPATCHER_BEAN_NAME, MessageDispatcher.class); + } + catch (NoSuchBeanDefinitionException ex) { + messageDispatcher = (MessageDispatcher) getDefaultStrategy(MessageDispatcher.class); + messageDispatcher.setBeanName(getServletName()); + if (logger.isInfoEnabled()) { + logger.info("Unable to locate MessageDispatcher with name '" + MESSAGE_DISPATCHER_BEAN_NAME + + "': using default [" + messageDispatcher + "]"); + } + // We only autodetect the following strategies when a message dispatcher has not been defined in + // the application context + initEndpointMappings(); + initEndpointAdapters(); + initEndpointExceptionResolvers(); + } + } +} diff --git a/core/src/main/resources/org/springframework/ws/transport/http/MessageDispatcherServlet.properties b/core/src/main/resources/org/springframework/ws/transport/http/MessageDispatcherServlet.properties new file mode 100644 index 00000000..e418729f --- /dev/null +++ b/core/src/main/resources/org/springframework/ws/transport/http/MessageDispatcherServlet.properties @@ -0,0 +1,7 @@ +# Default implementation classes for MessageDispatcherServlet's strategy interfaces. +# Used as fallback when no matching beans are found in the DispatcherServlet context. +# Not meant to be customized by application developers. + +org.springframework.ws.MessageDispatcher=org.springframework.ws.soap.SoapMessageDispatcher + +org.springframework.ws.context.MessageContextFactory=org.springframework.ws.soap.saaj.SaajSoapMessageContextFactory \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/MessageDispatcherTest.java b/core/src/test/java/org/springframework/ws/MessageDispatcherTest.java index a621fcb1..ca8f4a33 100644 --- a/core/src/test/java/org/springframework/ws/MessageDispatcherTest.java +++ b/core/src/test/java/org/springframework/ws/MessageDispatcherTest.java @@ -20,7 +20,6 @@ import java.util.Collections; import junit.framework.TestCase; import org.easymock.MockControl; -import org.springframework.context.support.StaticApplicationContext; import org.springframework.ws.context.MessageContext; import org.springframework.ws.mock.MockMessageContext; @@ -32,8 +31,6 @@ public class MessageDispatcherTest extends TestCase { protected void setUp() throws Exception { dispatcher = new MessageDispatcher(); - dispatcher.setApplicationContext(new StaticApplicationContext()); - dispatcher.initApplicationContext(); messageContext = new MockMessageContext(); } diff --git a/core/src/test/java/org/springframework/ws/transport/http/MessageDispatcherServletTest.java b/core/src/test/java/org/springframework/ws/transport/http/MessageDispatcherServletTest.java new file mode 100644 index 00000000..8e3ad1ef --- /dev/null +++ b/core/src/test/java/org/springframework/ws/transport/http/MessageDispatcherServletTest.java @@ -0,0 +1,107 @@ +package org.springframework.ws.transport.http; + +import java.util.List; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; + +import junit.framework.TestCase; +import org.springframework.beans.BeansException; +import org.springframework.mock.web.MockServletConfig; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.ws.MessageDispatcher; +import org.springframework.ws.endpoint.PayloadEndpointAdapter; +import org.springframework.ws.endpoint.mapping.PayloadRootQNameEndpointMapping; +import org.springframework.ws.soap.endpoint.SimpleSoapExceptionResolver; + +public class MessageDispatcherServletTest extends TestCase { + + private ServletConfig config; + + private MessageDispatcherServlet servlet; + + protected void setUp() throws Exception { + config = new MockServletConfig(new MockServletContext(), "spring-ws"); + servlet = new MessageDispatcherServlet(); + } + + private void assertStrategies(Class expectedClass, List actual) { + assertEquals("Invalid amount of strategies", 1, actual.size()); + Object strategy = actual.get(0); + assertTrue("Invalid strategy", expectedClass.isAssignableFrom(strategy.getClass())); + } + + public void testBeanNameStrategies() throws ServletException { + servlet.setDetectAllEndpointAdapters(false); + servlet.setDetectAllEndpointMappings(false); + servlet.setDetectAllEndpointExceptionResolvers(false); + servlet.setContextClass(BeanNameWebApplicationContext.class); + servlet.init(config); + MessageDispatcher messageDispatcher = servlet.getMessageDispatcher(); + assertNotNull("No messageDispatcher created", messageDispatcher); + assertStrategies(PayloadRootQNameEndpointMapping.class, messageDispatcher.getEndpointMappings()); + assertStrategies(PayloadEndpointAdapter.class, messageDispatcher.getEndpointAdapters()); + assertStrategies(SimpleSoapExceptionResolver.class, messageDispatcher.getEndpointExceptionResolvers()); + } + + public void testConfiguredMessageDispatcher() throws ServletException { + servlet.setContextClass(MessageDispatcherWebApplicationContext.class); + servlet.init(config); + MessageDispatcher messageDispatcher = servlet.getMessageDispatcher(); + assertNotNull("No messageDispatcher created", messageDispatcher); + assertNull("Default strategies loaded", messageDispatcher.getEndpointMappings()); + assertNull("Default strategies loaded", messageDispatcher.getEndpointExceptionResolvers()); + } + + public void testDefaultStrategies() throws ServletException { + servlet.setContextClass(StaticWebApplicationContext.class); + servlet.init(config); + MessageDispatcher messageDispatcher = servlet.getMessageDispatcher(); + assertNotNull("No messageDispatcher created", messageDispatcher); + } + + public void testDetectedStrategies() throws ServletException { + servlet.setContextClass(DetectWebApplicationContext.class); + servlet.init(config); + MessageDispatcher messageDispatcher = servlet.getMessageDispatcher(); + assertNotNull("No messageDispatcher created", messageDispatcher); + assertStrategies(PayloadRootQNameEndpointMapping.class, messageDispatcher.getEndpointMappings()); + assertStrategies(PayloadEndpointAdapter.class, messageDispatcher.getEndpointAdapters()); + assertStrategies(SimpleSoapExceptionResolver.class, messageDispatcher.getEndpointExceptionResolvers()); + } + + private static class DetectWebApplicationContext extends StaticWebApplicationContext { + + public void refresh() throws BeansException, IllegalStateException { + registerSingleton("payloadMapping", PayloadRootQNameEndpointMapping.class); + registerSingleton("payloadAdapter", PayloadEndpointAdapter.class); + registerSingleton("simpleExceptionResolver", SimpleSoapExceptionResolver.class); + super.refresh(); + } + } + + private static class BeanNameWebApplicationContext extends StaticWebApplicationContext { + + public void refresh() throws BeansException, IllegalStateException { + registerSingleton(MessageDispatcherServlet.ENDPOINT_MAPPING_BEAN_NAME, + PayloadRootQNameEndpointMapping.class); + registerSingleton(MessageDispatcherServlet.ENDPOINT_ADAPTER_BEAN_NAME, PayloadEndpointAdapter.class); + registerSingleton(MessageDispatcherServlet.ENDPOINT_EXCEPTION_RESOLVER_BEAN_NAME, + SimpleSoapExceptionResolver.class); + super.refresh(); + } + } + + private static class MessageDispatcherWebApplicationContext extends StaticWebApplicationContext { + + public void refresh() throws BeansException, IllegalStateException { + registerSingleton(MessageDispatcherServlet.MESSAGE_DISPATCHER_BEAN_NAME, MessageDispatcher.class); + registerSingleton(MessageDispatcherServlet.ENDPOINT_MAPPING_BEAN_NAME, + PayloadRootQNameEndpointMapping.class); + registerSingleton(MessageDispatcherServlet.ENDPOINT_ADAPTER_BEAN_NAME, PayloadEndpointAdapter.class); + registerSingleton(MessageDispatcherServlet.ENDPOINT_EXCEPTION_RESOLVER_BEAN_NAME, + SimpleSoapExceptionResolver.class); + super.refresh(); + } + } +} \ No newline at end of file