View Javadoc

1   /*
2    * Copyright 2005-2012 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.transport.http;
18  
19  import java.util.Map;
20  import javax.servlet.http.HttpServletRequest;
21  import javax.servlet.http.HttpServletResponse;
22  
23  import org.springframework.beans.factory.BeanFactoryUtils;
24  import org.springframework.beans.factory.BeanInitializationException;
25  import org.springframework.beans.factory.BeanNameAware;
26  import org.springframework.beans.factory.NoSuchBeanDefinitionException;
27  import org.springframework.context.ApplicationContext;
28  import org.springframework.web.context.WebApplicationContext;
29  import org.springframework.web.servlet.DispatcherServlet;
30  import org.springframework.web.servlet.FrameworkServlet;
31  import org.springframework.web.util.WebUtils;
32  import org.springframework.ws.WebServiceMessageFactory;
33  import org.springframework.ws.server.EndpointAdapter;
34  import org.springframework.ws.server.EndpointExceptionResolver;
35  import org.springframework.ws.server.EndpointMapping;
36  import org.springframework.ws.server.MessageDispatcher;
37  import org.springframework.ws.support.DefaultStrategiesHelper;
38  import org.springframework.ws.transport.WebServiceMessageReceiver;
39  import org.springframework.ws.wsdl.WsdlDefinition;
40  import org.springframework.xml.xsd.XsdSchema;
41  
42  /**
43   * Servlet for simplified dispatching of Web service messages.
44   * <p/>
45   * This servlet is a convenient alternative to the standard Spring-MVC {@link DispatcherServlet} with separate {@link
46   * WebServiceMessageReceiverHandlerAdapter}, {@link MessageDispatcher}, and {@link WsdlDefinitionHandlerAdapter}
47   * instances.
48   * <p/>
49   * This servlet automatically detects {@link EndpointAdapter EndpointAdapters}, {@link EndpointMapping
50   * EndpointMappings}, and {@link EndpointExceptionResolver EndpointExceptionResolvers} <i>by type</i>.
51   * <p/>
52   * This servlet also automatically detects any {@link WsdlDefinition} defined in its application context. This WSDL is
53   * exposed under the bean name: for example, a <code>WsdlDefinition</code> bean named '<code>echo</code>' will be
54   * exposed as <code>echo.wsdl</code> in this servlet's context: <code>http://localhost:8080/spring-ws/echo.wsdl</code>.
55   * When the <code>transformWsdlLocations</code> init-param is set to <code>true</code> in this servlet's configuration
56   * in <code>web.xml</code>, all <code>location</code> attributes in the WSDL definitions will reflect the URL of the
57   * incoming request.
58   *
59   * @author Arjen Poutsma
60   * @see org.springframework.web.servlet.DispatcherServlet
61   * @see org.springframework.ws.server.MessageDispatcher
62   * @see org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter
63   * @since 1.0.0
64   */
65  public class MessageDispatcherServlet extends FrameworkServlet {
66  
67      /** Well-known name for the {@link WebServiceMessageFactory} bean in the bean factory for this namespace. */
68      public static final String DEFAULT_MESSAGE_FACTORY_BEAN_NAME = "messageFactory";
69  
70      /** Well-known name for the {@link WebServiceMessageReceiver} object in the bean factory for this namespace. */
71      public static final String DEFAULT_MESSAGE_RECEIVER_BEAN_NAME = "messageReceiver";
72  
73      /**
74       * Well-known name for the {@link WebServiceMessageReceiverHandlerAdapter} object in the bean factory for this
75       * namespace.
76       */
77      public static final String DEFAULT_MESSAGE_RECEIVER_HANDLER_ADAPTER_BEAN_NAME = "messageReceiverHandlerAdapter";
78  
79      /** Well-known name for the {@link WsdlDefinitionHandlerAdapter} object in the bean factory for this namespace. */
80      public static final String DEFAULT_WSDL_DEFINITION_HANDLER_ADAPTER_BEAN_NAME = "wsdlDefinitionHandlerAdapter";
81  
82      /** Well-known name for the {@link XsdSchemaHandlerAdapter} object in the bean factory for this namespace. */
83      public static final String DEFAULT_XSD_SCHEMA_HANDLER_ADAPTER_BEAN_NAME = "xsdSchemaHandlerAdapter";
84  
85      /** Suffix of a WSDL request uri. */
86      private static final String WSDL_SUFFIX_NAME = ".wsdl";
87  
88      /** Suffix of a XSD request uri. */
89      private static final String XSD_SUFFIX_NAME = ".xsd";
90  
91      private final DefaultStrategiesHelper defaultStrategiesHelper;
92  
93      private String messageFactoryBeanName = DEFAULT_MESSAGE_FACTORY_BEAN_NAME;
94  
95      private String messageReceiverHandlerAdapterBeanName = DEFAULT_MESSAGE_RECEIVER_HANDLER_ADAPTER_BEAN_NAME;
96  
97      /** The {@link WebServiceMessageReceiverHandlerAdapter} used by this servlet. */
98      private WebServiceMessageReceiverHandlerAdapter messageReceiverHandlerAdapter;
99  
100     private String wsdlDefinitionHandlerAdapterBeanName = DEFAULT_WSDL_DEFINITION_HANDLER_ADAPTER_BEAN_NAME;
101 
102     /** The {@link WsdlDefinitionHandlerAdapter} used by this servlet. */
103     private WsdlDefinitionHandlerAdapter wsdlDefinitionHandlerAdapter;
104 
105     private String xsdSchemaHandlerAdapterBeanName = DEFAULT_XSD_SCHEMA_HANDLER_ADAPTER_BEAN_NAME;
106 
107     /** The {@link XsdSchemaHandlerAdapter} used by this servlet. */
108     private XsdSchemaHandlerAdapter xsdSchemaHandlerAdapter;
109 
110     private String messageReceiverBeanName = DEFAULT_MESSAGE_RECEIVER_BEAN_NAME;
111 
112     /** The {@link WebServiceMessageReceiver} used by this servlet. */
113     private WebServiceMessageReceiver messageReceiver;
114 
115     /** Keys are bean names, values are {@link WsdlDefinition WsdlDefinitions}. */
116     private Map<String, WsdlDefinition> wsdlDefinitions;
117 
118     private Map<String, XsdSchema> xsdSchemas;
119 
120     private boolean transformWsdlLocations = false;
121 
122     private boolean transformSchemaLocations = false;
123 
124     /**
125      * Public constructor, necessary for some Web application servers.
126      */
127     public MessageDispatcherServlet() {
128         this(null);
129     }
130 
131     /**
132      * Constructor to support programmatic configuration of the Servlet with the specified
133      * web application context. This constructor is useful in Servlet 3.0+ environments
134      * where instance-based registration of servlets is possible through the
135      * {@code ServletContext#addServlet} API.
136      * <p>Using this constructor indicates that the following properties / init-params
137      * will be ignored:
138      * <ul>
139      * <li>{@link #setContextClass(Class)} / 'contextClass'</li>
140      * <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li>
141      * <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li>
142      * <li>{@link #setNamespace(String)} / 'namespace'</li>
143      * </ul>
144      * <p>The given web application context may or may not yet be {@linkplain
145      * org.springframework.web.context.ConfigurableWebApplicationContext#refresh() refreshed}.
146      * If it has <strong>not</strong> already been refreshed (the recommended approach), then
147      * the following will occur:
148      * <ul>
149      * <li>If the given context does not already have a {@linkplain
150      * org.springframework.web.context.ConfigurableWebApplicationContext#setParent parent},
151      * the root application context will be set as the parent.</li>
152      * <li>If the given context has not already been assigned an {@linkplain
153      * org.springframework.web.context.ConfigurableWebApplicationContext#setId id}, one
154      * will be assigned to it</li>
155      * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
156      * the application context</li>
157      * <li>{@link #postProcessWebApplicationContext} will be called</li>
158      * <li>Any {@code ApplicationContextInitializer}s specified through the
159      * "contextInitializerClasses" init-param or through the {@link
160      * #setContextInitializers} property will be applied.</li>
161      * <li>{@link org.springframework.web.context.ConfigurableWebApplicationContext#refresh refresh()}
162      * will be called if the context implements
163      * {@link org.springframework.web.context.ConfigurableWebApplicationContext}</li>
164      * </ul>
165      * If the context has already been refreshed, none of the above will occur, under the
166      * assumption that the user has performed these actions (or not) per their specific
167      * needs.
168      * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
169      *
170      * @param webApplicationContext the context to use
171      * @see FrameworkServlet#FrameworkServlet(WebApplicationContext)
172      * @see org.springframework.web.WebApplicationInitializer
173      * @see #initWebApplicationContext()
174      * @see #configureAndRefreshWebApplicationContext(org.springframework.web.context.ConfigurableWebApplicationContext)
175      */
176     public MessageDispatcherServlet(WebApplicationContext webApplicationContext) {
177         super(webApplicationContext);
178         defaultStrategiesHelper = new DefaultStrategiesHelper(MessageDispatcherServlet.class);
179     }
180 
181     /** Returns the bean name used to lookup a {@link WebServiceMessageFactory}. */
182     public String getMessageFactoryBeanName() {
183         return messageFactoryBeanName;
184     }
185 
186     /**
187      * Sets the bean name used to lookup a {@link WebServiceMessageFactory}. Defaults to {@link
188      * #DEFAULT_MESSAGE_FACTORY_BEAN_NAME}.
189      */
190     public void setMessageFactoryBeanName(String messageFactoryBeanName) {
191         this.messageFactoryBeanName = messageFactoryBeanName;
192     }
193 
194     /** Returns the bean name used to lookup a {@link WebServiceMessageReceiver}. */
195     public String getMessageReceiverBeanName() {
196         return messageReceiverBeanName;
197     }
198 
199     /**
200      * Sets the bean name used to lookup a {@link WebServiceMessageReceiver}. Defaults to {@link
201      * #DEFAULT_MESSAGE_RECEIVER_BEAN_NAME}.
202      */
203     public void setMessageReceiverBeanName(String messageReceiverBeanName) {
204         this.messageReceiverBeanName = messageReceiverBeanName;
205     }
206 
207     /**
208      * Indicates whether relative address locations in the WSDL are to be transformed using the request URI of the
209      * incoming {@link HttpServletRequest}.
210      */
211     public boolean isTransformWsdlLocations() {
212         return transformWsdlLocations;
213     }
214 
215     /**
216      * Sets whether relative address locations in the WSDL are to be transformed using the request URI of the incoming
217      * {@link HttpServletRequest}. Defaults to <code>false</code>.
218      */
219     public void setTransformWsdlLocations(boolean transformWsdlLocations) {
220         this.transformWsdlLocations = transformWsdlLocations;
221     }
222 
223     /**
224      * Indicates whether relative address locations in the XSD are to be transformed using the request URI of the
225      * incoming {@link HttpServletRequest}.
226      */
227     public boolean isTransformSchemaLocations() {
228         return transformSchemaLocations;
229     }
230 
231     /**
232      * Sets whether relative address locations in the XSD are to be transformed using the request URI of the incoming
233      * {@link HttpServletRequest}. Defaults to <code>false</code>.
234      */
235     public void setTransformSchemaLocations(boolean transformSchemaLocations) {
236         this.transformSchemaLocations = transformSchemaLocations;
237     }
238 
239     /** Returns the bean name used to lookup a {@link WebServiceMessageReceiverHandlerAdapter}. */
240     public String getMessageReceiverHandlerAdapterBeanName() {
241         return messageReceiverHandlerAdapterBeanName;
242     }
243 
244     /**
245      * Sets the bean name used to lookup a {@link WebServiceMessageReceiverHandlerAdapter}. Defaults to {@link
246      * #DEFAULT_MESSAGE_RECEIVER_HANDLER_ADAPTER_BEAN_NAME}.
247      */
248     public void setMessageReceiverHandlerAdapterBeanName(String messageReceiverHandlerAdapterBeanName) {
249         this.messageReceiverHandlerAdapterBeanName = messageReceiverHandlerAdapterBeanName;
250     }
251 
252     /** Returns the bean name used to lookup a {@link WsdlDefinitionHandlerAdapter}. */
253     public String getWsdlDefinitionHandlerAdapterBeanName() {
254         return wsdlDefinitionHandlerAdapterBeanName;
255     }
256 
257     /**
258      * Sets the bean name used to lookup a {@link WsdlDefinitionHandlerAdapter}. Defaults to {@link
259      * #DEFAULT_WSDL_DEFINITION_HANDLER_ADAPTER_BEAN_NAME}.
260      */
261     public void setWsdlDefinitionHandlerAdapterBeanName(String wsdlDefinitionHandlerAdapterBeanName) {
262         this.wsdlDefinitionHandlerAdapterBeanName = wsdlDefinitionHandlerAdapterBeanName;
263     }
264 
265     /** Returns the bean name used to lookup a {@link XsdSchemaHandlerAdapter}. */
266     public String getXsdSchemaHandlerAdapterBeanName() {
267         return xsdSchemaHandlerAdapterBeanName;
268     }
269 
270     /**
271      * Sets the bean name used to lookup a {@link XsdSchemaHandlerAdapter}. Defaults to {@link
272      * #DEFAULT_XSD_SCHEMA_HANDLER_ADAPTER_BEAN_NAME}.
273      */
274     public void setXsdSchemaHandlerAdapterBeanName(String xsdSchemaHandlerAdapterBeanName) {
275         this.xsdSchemaHandlerAdapterBeanName = xsdSchemaHandlerAdapterBeanName;
276     }
277 
278 
279     @Override
280     protected void doService(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
281             throws Exception {
282         WsdlDefinition definition = getWsdlDefinition(httpServletRequest);
283         if (definition != null) {
284             wsdlDefinitionHandlerAdapter.handle(httpServletRequest, httpServletResponse, definition);
285             return;
286         }
287         XsdSchema schema = getXsdSchema(httpServletRequest);
288         if (schema != null) {
289             xsdSchemaHandlerAdapter.handle(httpServletRequest, httpServletResponse, schema);
290             return;
291         }
292         messageReceiverHandlerAdapter.handle(httpServletRequest, httpServletResponse, messageReceiver);
293     }
294 
295     /**
296      * This implementation calls {@link #initStrategies}.
297      */
298     @Override
299     protected void onRefresh(ApplicationContext context) {
300         initStrategies(context);
301     }
302 
303     @Override
304     protected long getLastModified(HttpServletRequest httpServletRequest) {
305         WsdlDefinition definition = getWsdlDefinition(httpServletRequest);
306         if (definition != null) {
307             return wsdlDefinitionHandlerAdapter.getLastModified(httpServletRequest, definition);
308         }
309         XsdSchema schema = getXsdSchema(httpServletRequest);
310         if (schema != null) {
311             return xsdSchemaHandlerAdapter.getLastModified(httpServletRequest, schema);
312         }
313         return messageReceiverHandlerAdapter.getLastModified(httpServletRequest, messageReceiver);
314     }
315 
316     /** Returns the {@link WebServiceMessageReceiver} used by this servlet. */
317     protected WebServiceMessageReceiver getMessageReceiver() {
318         return messageReceiver;
319     }
320 
321     /**
322      * Determines the {@link WsdlDefinition} for a given request, or <code>null</code> if none is found.
323      * <p/>
324      * Default implementation checks whether the request method is <code>GET</code>, whether the request uri ends with
325      * <code>".wsdl"</code>, and if there is a <code>WsdlDefinition</code> with the same name as the filename in the
326      * request uri.
327      *
328      * @param request the <code>HttpServletRequest</code>
329      * @return a definition, or <code>null</code>
330      */
331     protected WsdlDefinition getWsdlDefinition(HttpServletRequest request) {
332         if (HttpTransportConstants.METHOD_GET.equals(request.getMethod()) &&
333                 request.getRequestURI().endsWith(WSDL_SUFFIX_NAME)) {
334             String fileName = WebUtils.extractFilenameFromUrlPath(request.getRequestURI());
335             return wsdlDefinitions.get(fileName);
336         }
337         else {
338             return null;
339         }
340     }
341 
342     /**
343      * Determines the {@link XsdSchema} for a given request, or <code>null</code> if none is found.
344      * <p/>
345      * Default implementation checks whether the request method is <code>GET</code>, whether the request uri ends with
346      * <code>".xsd"</code>, and if there is a <code>XsdSchema</code> with the same name as the filename in the request
347      * uri.
348      *
349      * @param request the <code>HttpServletRequest</code>
350      * @return a schema, or <code>null</code>
351      */
352     protected XsdSchema getXsdSchema(HttpServletRequest request) {
353         if (HttpTransportConstants.METHOD_GET.equals(request.getMethod()) &&
354                 request.getRequestURI().endsWith(XSD_SUFFIX_NAME)) {
355             String fileName = WebUtils.extractFilenameFromUrlPath(request.getRequestURI());
356             return xsdSchemas.get(fileName);
357         }
358         else {
359             return null;
360         }
361     }
362 
363     /**
364      * Initialize the strategy objects that this servlet uses.
365      * <p>May be overridden in subclasses in order to initialize further strategy objects.
366      */
367     protected void initStrategies(ApplicationContext context) {
368         initMessageReceiverHandlerAdapter(context);
369         initWsdlDefinitionHandlerAdapter(context);
370         initXsdSchemaHandlerAdapter(context);
371         initMessageReceiver(context);
372         initWsdlDefinitions(context);
373         initXsdSchemas(context);
374     }
375 
376 
377     private void initMessageReceiverHandlerAdapter(ApplicationContext context) {
378         try {
379             try {
380                 messageReceiverHandlerAdapter = context.getBean(getMessageReceiverHandlerAdapterBeanName(),
381                         WebServiceMessageReceiverHandlerAdapter.class);
382             }
383             catch (NoSuchBeanDefinitionException ignored) {
384                 messageReceiverHandlerAdapter = new WebServiceMessageReceiverHandlerAdapter();
385             }
386             initWebServiceMessageFactory(context);
387             messageReceiverHandlerAdapter.afterPropertiesSet();
388         }
389         catch (Exception ex) {
390             throw new BeanInitializationException("Could not initialize WebServiceMessageReceiverHandlerAdapter", ex);
391         }
392     }
393 
394     private void initWebServiceMessageFactory(ApplicationContext context) {
395         WebServiceMessageFactory messageFactory;
396         try {
397             messageFactory = context.getBean(getMessageFactoryBeanName(), WebServiceMessageFactory.class);
398         }
399         catch (NoSuchBeanDefinitionException ignored) {
400             messageFactory = defaultStrategiesHelper
401                     .getDefaultStrategy(WebServiceMessageFactory.class, context);
402             if (logger.isDebugEnabled()) {
403                 logger.debug("No WebServiceMessageFactory found in servlet '" + getServletName() + "': using default");
404             }
405         }
406         messageReceiverHandlerAdapter.setMessageFactory(messageFactory);
407     }
408 
409     private void initWsdlDefinitionHandlerAdapter(ApplicationContext context) {
410         try {
411             try {
412                 wsdlDefinitionHandlerAdapter =
413                         context.getBean(getWsdlDefinitionHandlerAdapterBeanName(), WsdlDefinitionHandlerAdapter.class);
414 
415             }
416             catch (NoSuchBeanDefinitionException ignored) {
417                 wsdlDefinitionHandlerAdapter = new WsdlDefinitionHandlerAdapter();
418             }
419             wsdlDefinitionHandlerAdapter.setTransformLocations(isTransformWsdlLocations());
420             wsdlDefinitionHandlerAdapter.setTransformSchemaLocations(isTransformSchemaLocations());
421             wsdlDefinitionHandlerAdapter.afterPropertiesSet();
422         }
423         catch (Exception ex) {
424             throw new BeanInitializationException("Could not initialize WsdlDefinitionHandlerAdapter", ex);
425         }
426     }
427 
428     private void initXsdSchemaHandlerAdapter(ApplicationContext context) {
429         try {
430             try {
431                 xsdSchemaHandlerAdapter = context
432                         .getBean(getXsdSchemaHandlerAdapterBeanName(), XsdSchemaHandlerAdapter.class);
433 
434             }
435             catch (NoSuchBeanDefinitionException ignored) {
436                 xsdSchemaHandlerAdapter = new XsdSchemaHandlerAdapter();
437             }
438             xsdSchemaHandlerAdapter.setTransformSchemaLocations(isTransformSchemaLocations());
439             xsdSchemaHandlerAdapter.afterPropertiesSet();
440         }
441         catch (Exception ex) {
442             throw new BeanInitializationException("Could not initialize XsdSchemaHandlerAdapter", ex);
443         }
444     }
445 
446     private void initMessageReceiver(ApplicationContext context) {
447         try {
448             messageReceiver = context.getBean(getMessageReceiverBeanName(), WebServiceMessageReceiver.class);
449         }
450         catch (NoSuchBeanDefinitionException ex) {
451             messageReceiver = defaultStrategiesHelper
452                     .getDefaultStrategy(WebServiceMessageReceiver.class, context);
453             if (messageReceiver instanceof BeanNameAware) {
454                 ((BeanNameAware) messageReceiver).setBeanName(getServletName());
455             }
456             if (logger.isDebugEnabled()) {
457                 logger.debug("No MessageDispatcher found in servlet '" + getServletName() + "': using default");
458             }
459         }
460     }
461 
462     private void initWsdlDefinitions(ApplicationContext context) {
463         wsdlDefinitions = BeanFactoryUtils
464                 .beansOfTypeIncludingAncestors(context, WsdlDefinition.class, true, false);
465         if (logger.isDebugEnabled()) {
466             for (Map.Entry<String, WsdlDefinition> entry : wsdlDefinitions.entrySet()) {
467                 String beanName = entry.getKey();
468                 WsdlDefinition definition = entry.getValue();
469                 logger.debug("Published [" + definition + "] as " + beanName + WSDL_SUFFIX_NAME);
470             }
471         }
472     }
473 
474     private void initXsdSchemas(ApplicationContext context) {
475         xsdSchemas = BeanFactoryUtils
476                 .beansOfTypeIncludingAncestors(context, XsdSchema.class, true, false);
477         if (logger.isDebugEnabled()) {
478             for (Map.Entry<String, XsdSchema> entry : xsdSchemas.entrySet()) {
479                 String beanName = entry.getKey();
480                 XsdSchema schema = entry.getValue();
481                 logger.debug("Published [" + schema + "] as " + beanName + XSD_SUFFIX_NAME);
482             }
483         }
484     }
485 }