/* * Copyright 2005-2014 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.transport.http; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.FrameworkServlet; import org.springframework.ws.WebServiceMessageFactory; import org.springframework.ws.server.EndpointAdapter; import org.springframework.ws.server.EndpointExceptionResolver; import org.springframework.ws.server.EndpointMapping; import org.springframework.ws.server.MessageDispatcher; import org.springframework.ws.support.DefaultStrategiesHelper; import org.springframework.ws.transport.WebServiceMessageReceiver; import org.springframework.ws.support.WebUtils; import org.springframework.ws.wsdl.WsdlDefinition; import org.springframework.xml.xsd.XsdSchema; /** * Servlet for simplified dispatching of Web service messages. * * <p>This servlet is a convenient alternative to the standard Spring-MVC {@link DispatcherServlet} with separate {@link * WebServiceMessageReceiverHandlerAdapter}, {@link MessageDispatcher}, and {@link WsdlDefinitionHandlerAdapter} * instances. * * <p>This servlet automatically detects {@link EndpointAdapter EndpointAdapters}, {@link EndpointMapping * EndpointMappings}, and {@link EndpointExceptionResolver EndpointExceptionResolvers} <i>by type</i>. * * <p>This servlet also automatically detects any {@link WsdlDefinition} defined in its application context. This WSDL is * exposed under the bean name: for example, a {@code WsdlDefinition} bean named '{@code echo}' will be * exposed as {@code echo.wsdl} in this servlet's context: {@code http://localhost:8080/spring-ws/echo.wsdl}. * When the {@code transformWsdlLocations} init-param is set to {@code true} in this servlet's configuration * in {@code web.xml}, all {@code location} attributes in the WSDL definitions will reflect the URL of the * incoming request. * * @author Arjen Poutsma * @see org.springframework.web.servlet.DispatcherServlet * @see org.springframework.ws.server.MessageDispatcher * @see org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter * @since 1.0.0 */ @SuppressWarnings("serial") public class MessageDispatcherServlet extends FrameworkServlet { /** Well-known name for the {@link WebServiceMessageFactory} bean in the bean factory for this namespace. */ public static final String DEFAULT_MESSAGE_FACTORY_BEAN_NAME = "messageFactory"; /** Well-known name for the {@link WebServiceMessageReceiver} object in the bean factory for this namespace. */ public static final String DEFAULT_MESSAGE_RECEIVER_BEAN_NAME = "messageReceiver"; /** * Well-known name for the {@link WebServiceMessageReceiverHandlerAdapter} object in the bean factory for this * namespace. */ public static final String DEFAULT_MESSAGE_RECEIVER_HANDLER_ADAPTER_BEAN_NAME = "messageReceiverHandlerAdapter"; /** Well-known name for the {@link WsdlDefinitionHandlerAdapter} object in the bean factory for this namespace. */ public static final String DEFAULT_WSDL_DEFINITION_HANDLER_ADAPTER_BEAN_NAME = "wsdlDefinitionHandlerAdapter"; /** Well-known name for the {@link XsdSchemaHandlerAdapter} object in the bean factory for this namespace. */ public static final String DEFAULT_XSD_SCHEMA_HANDLER_ADAPTER_BEAN_NAME = "xsdSchemaHandlerAdapter"; /** Suffix of a WSDL request uri. */ private static final String WSDL_SUFFIX_NAME = ".wsdl"; /** Suffix of a XSD request uri. */ private static final String XSD_SUFFIX_NAME = ".xsd"; private final DefaultStrategiesHelper defaultStrategiesHelper; private String messageFactoryBeanName = DEFAULT_MESSAGE_FACTORY_BEAN_NAME; private String messageReceiverHandlerAdapterBeanName = DEFAULT_MESSAGE_RECEIVER_HANDLER_ADAPTER_BEAN_NAME; /** The {@link WebServiceMessageReceiverHandlerAdapter} used by this servlet. */ private WebServiceMessageReceiverHandlerAdapter messageReceiverHandlerAdapter; private String wsdlDefinitionHandlerAdapterBeanName = DEFAULT_WSDL_DEFINITION_HANDLER_ADAPTER_BEAN_NAME; /** The {@link WsdlDefinitionHandlerAdapter} used by this servlet. */ private WsdlDefinitionHandlerAdapter wsdlDefinitionHandlerAdapter; private String xsdSchemaHandlerAdapterBeanName = DEFAULT_XSD_SCHEMA_HANDLER_ADAPTER_BEAN_NAME; /** The {@link XsdSchemaHandlerAdapter} used by this servlet. */ private XsdSchemaHandlerAdapter xsdSchemaHandlerAdapter; private String messageReceiverBeanName = DEFAULT_MESSAGE_RECEIVER_BEAN_NAME; /** The {@link WebServiceMessageReceiver} used by this servlet. */ private WebServiceMessageReceiver messageReceiver; /** Keys are bean names, values are {@link WsdlDefinition WsdlDefinitions}. */ private Map<String, WsdlDefinition> wsdlDefinitions; private Map<String, XsdSchema> xsdSchemas; private boolean transformWsdlLocations = false; private boolean transformSchemaLocations = false; /** * Public constructor, necessary for some Web application servers. */ public MessageDispatcherServlet() { this(null); } /** * Constructor to support programmatic configuration of the Servlet with the specified * web application context. This constructor is useful in Servlet 3.0+ environments * where instance-based registration of servlets is possible through the * {@code ServletContext#addServlet} API. * <p>Using this constructor indicates that the following properties / init-params * will be ignored: * <ul> * <li>{@link #setContextClass(Class)} / 'contextClass'</li> * <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li> * <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li> * <li>{@link #setNamespace(String)} / 'namespace'</li> * </ul> * <p>The given web application context may or may not yet be {@linkplain * org.springframework.web.context.ConfigurableWebApplicationContext#refresh() refreshed}. * If it has <strong>not</strong> already been refreshed (the recommended approach), then * the following will occur: * <ul> * <li>If the given context does not already have a {@linkplain * org.springframework.web.context.ConfigurableWebApplicationContext#setParent parent}, * the root application context will be set as the parent.</li> * <li>If the given context has not already been assigned an {@linkplain * org.springframework.web.context.ConfigurableWebApplicationContext#setId id}, one * will be assigned to it</li> * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to * the application context</li> * <li>{@link #postProcessWebApplicationContext} will be called</li> * <li>Any {@code ApplicationContextInitializer}s specified through the * "contextInitializerClasses" init-param or through the {@link * #setContextInitializers} property will be applied.</li> * <li>{@link org.springframework.web.context.ConfigurableWebApplicationContext#refresh refresh()} * will be called if the context implements * {@link org.springframework.web.context.ConfigurableWebApplicationContext}</li> * </ul> * If the context has already been refreshed, none of the above will occur, under the * assumption that the user has performed these actions (or not) per their specific * needs. * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples. * * @param webApplicationContext the context to use * @see FrameworkServlet#FrameworkServlet(WebApplicationContext) * @see org.springframework.web.WebApplicationInitializer * @see #initWebApplicationContext() * @see #configureAndRefreshWebApplicationContext(org.springframework.web.context.ConfigurableWebApplicationContext) */ public MessageDispatcherServlet(WebApplicationContext webApplicationContext) { super(webApplicationContext); defaultStrategiesHelper = new DefaultStrategiesHelper(MessageDispatcherServlet.class); } /** Returns the bean name used to lookup a {@link WebServiceMessageFactory}. */ public String getMessageFactoryBeanName() { return messageFactoryBeanName; } /** * Sets the bean name used to lookup a {@link WebServiceMessageFactory}. Defaults to {@link * #DEFAULT_MESSAGE_FACTORY_BEAN_NAME}. */ public void setMessageFactoryBeanName(String messageFactoryBeanName) { this.messageFactoryBeanName = messageFactoryBeanName; } /** Returns the bean name used to lookup a {@link WebServiceMessageReceiver}. */ public String getMessageReceiverBeanName() { return messageReceiverBeanName; } /** * Sets the bean name used to lookup a {@link WebServiceMessageReceiver}. Defaults to {@link * #DEFAULT_MESSAGE_RECEIVER_BEAN_NAME}. */ public void setMessageReceiverBeanName(String messageReceiverBeanName) { this.messageReceiverBeanName = messageReceiverBeanName; } /** * Indicates whether relative address locations in the WSDL are to be transformed using the request URI of the * incoming {@link HttpServletRequest}. */ public boolean isTransformWsdlLocations() { return transformWsdlLocations; } /** * Sets whether relative address locations in the WSDL are to be transformed using the request URI of the incoming * {@link HttpServletRequest}. Defaults to {@code false}. */ public void setTransformWsdlLocations(boolean transformWsdlLocations) { this.transformWsdlLocations = transformWsdlLocations; } /** * Indicates whether relative address locations in the XSD are to be transformed using the request URI of the * incoming {@link HttpServletRequest}. */ public boolean isTransformSchemaLocations() { return transformSchemaLocations; } /** * Sets whether relative address locations in the XSD are to be transformed using the request URI of the incoming * {@link HttpServletRequest}. Defaults to {@code false}. */ public void setTransformSchemaLocations(boolean transformSchemaLocations) { this.transformSchemaLocations = transformSchemaLocations; } /** Returns the bean name used to lookup a {@link WebServiceMessageReceiverHandlerAdapter}. */ public String getMessageReceiverHandlerAdapterBeanName() { return messageReceiverHandlerAdapterBeanName; } /** * Sets the bean name used to lookup a {@link WebServiceMessageReceiverHandlerAdapter}. Defaults to {@link * #DEFAULT_MESSAGE_RECEIVER_HANDLER_ADAPTER_BEAN_NAME}. */ public void setMessageReceiverHandlerAdapterBeanName(String messageReceiverHandlerAdapterBeanName) { this.messageReceiverHandlerAdapterBeanName = messageReceiverHandlerAdapterBeanName; } /** Returns the bean name used to lookup a {@link WsdlDefinitionHandlerAdapter}. */ public String getWsdlDefinitionHandlerAdapterBeanName() { return wsdlDefinitionHandlerAdapterBeanName; } /** * Sets the bean name used to lookup a {@link WsdlDefinitionHandlerAdapter}. Defaults to {@link * #DEFAULT_WSDL_DEFINITION_HANDLER_ADAPTER_BEAN_NAME}. */ public void setWsdlDefinitionHandlerAdapterBeanName(String wsdlDefinitionHandlerAdapterBeanName) { this.wsdlDefinitionHandlerAdapterBeanName = wsdlDefinitionHandlerAdapterBeanName; } /** Returns the bean name used to lookup a {@link XsdSchemaHandlerAdapter}. */ public String getXsdSchemaHandlerAdapterBeanName() { return xsdSchemaHandlerAdapterBeanName; } /** * Sets the bean name used to lookup a {@link XsdSchemaHandlerAdapter}. Defaults to {@link * #DEFAULT_XSD_SCHEMA_HANDLER_ADAPTER_BEAN_NAME}. */ public void setXsdSchemaHandlerAdapterBeanName(String xsdSchemaHandlerAdapterBeanName) { this.xsdSchemaHandlerAdapterBeanName = xsdSchemaHandlerAdapterBeanName; } @Override protected void doService(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { WsdlDefinition definition = getWsdlDefinition(httpServletRequest); if (definition != null) { wsdlDefinitionHandlerAdapter.handle(httpServletRequest, httpServletResponse, definition); return; } XsdSchema schema = getXsdSchema(httpServletRequest); if (schema != null) { xsdSchemaHandlerAdapter.handle(httpServletRequest, httpServletResponse, schema); return; } messageReceiverHandlerAdapter.handle(httpServletRequest, httpServletResponse, messageReceiver); } /** * This implementation calls {@link #initStrategies}. */ @Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } @Override protected long getLastModified(HttpServletRequest httpServletRequest) { WsdlDefinition definition = getWsdlDefinition(httpServletRequest); if (definition != null) { return wsdlDefinitionHandlerAdapter.getLastModified(httpServletRequest, definition); } XsdSchema schema = getXsdSchema(httpServletRequest); if (schema != null) { return xsdSchemaHandlerAdapter.getLastModified(httpServletRequest, schema); } return messageReceiverHandlerAdapter.getLastModified(httpServletRequest, messageReceiver); } /** Returns the {@link WebServiceMessageReceiver} used by this servlet. */ protected WebServiceMessageReceiver getMessageReceiver() { return messageReceiver; } /** * Determines the {@link WsdlDefinition} for a given request, or {@code null} if none is found. * * <p>Default implementation checks whether the request method is {@code GET}, whether the request uri ends with * {@code ".wsdl"}, and if there is a {@code WsdlDefinition} with the same name as the filename in the * request uri. * * @param request the {@code HttpServletRequest} * @return a definition, or {@code null} */ protected WsdlDefinition getWsdlDefinition(HttpServletRequest request) { if (HttpTransportConstants.METHOD_GET.equals(request.getMethod()) && request.getRequestURI().endsWith(WSDL_SUFFIX_NAME)) { String fileName = WebUtils.extractFilenameFromUrlPath(request.getRequestURI()); return wsdlDefinitions.get(fileName); } else { return null; } } /** * Determines the {@link XsdSchema} for a given request, or {@code null} if none is found. * * <p>Default implementation checks whether the request method is {@code GET}, whether the request uri ends with * {@code ".xsd"}, and if there is a {@code XsdSchema} with the same name as the filename in the request * uri. * * @param request the {@code HttpServletRequest} * @return a schema, or {@code null} */ protected XsdSchema getXsdSchema(HttpServletRequest request) { if (HttpTransportConstants.METHOD_GET.equals(request.getMethod()) && request.getRequestURI().endsWith(XSD_SUFFIX_NAME)) { String fileName = WebUtils.extractFilenameFromUrlPath(request.getRequestURI()); return xsdSchemas.get(fileName); } else { return null; } } /** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ protected void initStrategies(ApplicationContext context) { initMessageReceiverHandlerAdapter(context); initWsdlDefinitionHandlerAdapter(context); initXsdSchemaHandlerAdapter(context); initMessageReceiver(context); initWsdlDefinitions(context); initXsdSchemas(context); } private void initMessageReceiverHandlerAdapter(ApplicationContext context) { try { try { messageReceiverHandlerAdapter = context.getBean(getMessageReceiverHandlerAdapterBeanName(), WebServiceMessageReceiverHandlerAdapter.class); } catch (NoSuchBeanDefinitionException ignored) { messageReceiverHandlerAdapter = new WebServiceMessageReceiverHandlerAdapter(); } initWebServiceMessageFactory(context); messageReceiverHandlerAdapter.afterPropertiesSet(); } catch (Exception ex) { throw new BeanInitializationException("Could not initialize WebServiceMessageReceiverHandlerAdapter", ex); } } private void initWebServiceMessageFactory(ApplicationContext context) { WebServiceMessageFactory messageFactory; try { messageFactory = context.getBean(getMessageFactoryBeanName(), WebServiceMessageFactory.class); } catch (NoSuchBeanDefinitionException ignored) { messageFactory = defaultStrategiesHelper .getDefaultStrategy(WebServiceMessageFactory.class, context); if (logger.isDebugEnabled()) { logger.debug("No WebServiceMessageFactory found in servlet '" + getServletName() + "': using default"); } } messageReceiverHandlerAdapter.setMessageFactory(messageFactory); } private void initWsdlDefinitionHandlerAdapter(ApplicationContext context) { try { try { wsdlDefinitionHandlerAdapter = context.getBean(getWsdlDefinitionHandlerAdapterBeanName(), WsdlDefinitionHandlerAdapter.class); } catch (NoSuchBeanDefinitionException ignored) { wsdlDefinitionHandlerAdapter = new WsdlDefinitionHandlerAdapter(); } wsdlDefinitionHandlerAdapter.setTransformLocations(isTransformWsdlLocations()); wsdlDefinitionHandlerAdapter.setTransformSchemaLocations(isTransformSchemaLocations()); wsdlDefinitionHandlerAdapter.afterPropertiesSet(); } catch (Exception ex) { throw new BeanInitializationException("Could not initialize WsdlDefinitionHandlerAdapter", ex); } } private void initXsdSchemaHandlerAdapter(ApplicationContext context) { try { try { xsdSchemaHandlerAdapter = context .getBean(getXsdSchemaHandlerAdapterBeanName(), XsdSchemaHandlerAdapter.class); } catch (NoSuchBeanDefinitionException ignored) { xsdSchemaHandlerAdapter = new XsdSchemaHandlerAdapter(); } xsdSchemaHandlerAdapter.setTransformSchemaLocations(isTransformSchemaLocations()); xsdSchemaHandlerAdapter.afterPropertiesSet(); } catch (Exception ex) { throw new BeanInitializationException("Could not initialize XsdSchemaHandlerAdapter", ex); } } private void initMessageReceiver(ApplicationContext context) { try { messageReceiver = context.getBean(getMessageReceiverBeanName(), WebServiceMessageReceiver.class); } catch (NoSuchBeanDefinitionException ex) { messageReceiver = defaultStrategiesHelper .getDefaultStrategy(WebServiceMessageReceiver.class, context); if (messageReceiver instanceof BeanNameAware) { ((BeanNameAware) messageReceiver).setBeanName(getServletName()); } if (logger.isDebugEnabled()) { logger.debug("No MessageDispatcher found in servlet '" + getServletName() + "': using default"); } } } private void initWsdlDefinitions(ApplicationContext context) { wsdlDefinitions = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, WsdlDefinition.class, true, false); if (logger.isDebugEnabled()) { for (Map.Entry<String, WsdlDefinition> entry : wsdlDefinitions.entrySet()) { String beanName = entry.getKey(); WsdlDefinition definition = entry.getValue(); logger.debug("Published [" + definition + "] as " + beanName + WSDL_SUFFIX_NAME); } } } private void initXsdSchemas(ApplicationContext context) { xsdSchemas = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, XsdSchema.class, true, false); if (logger.isDebugEnabled()) { for (Map.Entry<String, XsdSchema> entry : xsdSchemas.entrySet()) { String beanName = entry.getKey(); XsdSchema schema = entry.getValue(); logger.debug("Published [" + schema + "] as " + beanName + XSD_SUFFIX_NAME); } } } }