1 /*
2  * Copyright 2005-2014 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.ws.WebServiceMessageFactory;
32 import org.springframework.ws.server.EndpointAdapter;
33 import org.springframework.ws.server.EndpointExceptionResolver;
34 import org.springframework.ws.server.EndpointMapping;
35 import org.springframework.ws.server.MessageDispatcher;
36 import org.springframework.ws.support.DefaultStrategiesHelper;
37 import org.springframework.ws.transport.WebServiceMessageReceiver;
38 import org.springframework.ws.support.WebUtils;
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  *
45  * <p>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  *
49  * <p>This servlet automatically detects {@link EndpointAdapter EndpointAdapters}, {@link EndpointMapping
50  * EndpointMappings}, and {@link EndpointExceptionResolver EndpointExceptionResolvers} <i>by type</i>.
51  *
52  * <p>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} bean named '{@code echo}' will be
54  * exposed as {@code echo.wsdl} in this servlet's context: {@code http://localhost:8080/spring-ws/echo.wsdl}.
55  * When the {@code transformWsdlLocations} init-param is set to {@code true} in this servlet's configuration
56  * in {@code web.xml}, all {@code location} 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 @SuppressWarnings("serial")
66 public class MessageDispatcherServlet extends FrameworkServlet {
67
68     /** Well-known name for the {@link WebServiceMessageFactory} bean in the bean factory for this namespace. */
69     public static final String DEFAULT_MESSAGE_FACTORY_BEAN_NAME = "messageFactory";
70
71     /** Well-known name for the {@link WebServiceMessageReceiver} object in the bean factory for this namespace. */
72     public static final String DEFAULT_MESSAGE_RECEIVER_BEAN_NAME = "messageReceiver";
73
74     /**
75      * Well-known name for the {@link WebServiceMessageReceiverHandlerAdapter} object in the bean factory for this
76      * namespace.
77      */

78     public static final String DEFAULT_MESSAGE_RECEIVER_HANDLER_ADAPTER_BEAN_NAME = "messageReceiverHandlerAdapter";
79
80     /** Well-known name for the {@link WsdlDefinitionHandlerAdapter} object in the bean factory for this namespace. */
81     public static final String DEFAULT_WSDL_DEFINITION_HANDLER_ADAPTER_BEAN_NAME = "wsdlDefinitionHandlerAdapter";
82
83     /** Well-known name for the {@link XsdSchemaHandlerAdapter} object in the bean factory for this namespace. */
84     public static final String DEFAULT_XSD_SCHEMA_HANDLER_ADAPTER_BEAN_NAME = "xsdSchemaHandlerAdapter";
85
86     /** Suffix of a WSDL request uri. */
87     private static final String WSDL_SUFFIX_NAME = ".wsdl";
88
89     /** Suffix of a XSD request uri. */
90     private static final String XSD_SUFFIX_NAME = ".xsd";
91
92     private final DefaultStrategiesHelper defaultStrategiesHelper;
93
94     private String messageFactoryBeanName = DEFAULT_MESSAGE_FACTORY_BEAN_NAME;
95
96     private String messageReceiverHandlerAdapterBeanName = DEFAULT_MESSAGE_RECEIVER_HANDLER_ADAPTER_BEAN_NAME;
97
98     /** The {@link WebServiceMessageReceiverHandlerAdapter} used by this servlet. */
99     private WebServiceMessageReceiverHandlerAdapter messageReceiverHandlerAdapter;
100
101     private String wsdlDefinitionHandlerAdapterBeanName = DEFAULT_WSDL_DEFINITION_HANDLER_ADAPTER_BEAN_NAME;
102
103     /** The {@link WsdlDefinitionHandlerAdapter} used by this servlet. */
104     private WsdlDefinitionHandlerAdapter wsdlDefinitionHandlerAdapter;
105
106     private String xsdSchemaHandlerAdapterBeanName = DEFAULT_XSD_SCHEMA_HANDLER_ADAPTER_BEAN_NAME;
107
108     /** The {@link XsdSchemaHandlerAdapter} used by this servlet. */
109     private XsdSchemaHandlerAdapter xsdSchemaHandlerAdapter;
110
111     private String messageReceiverBeanName = DEFAULT_MESSAGE_RECEIVER_BEAN_NAME;
112
113     /** The {@link WebServiceMessageReceiver} used by this servlet. */
114     private WebServiceMessageReceiver messageReceiver;
115
116     /** Keys are bean names, values are {@link WsdlDefinition WsdlDefinitions}. */
117     private Map<String, WsdlDefinition> wsdlDefinitions;
118
119     private Map<String, XsdSchema> xsdSchemas;
120
121     private boolean transformWsdlLocations = false;
122
123     private boolean transformSchemaLocations = false;
124
125     /**
126      * Public constructor, necessary for some Web application servers.
127      */

128     public MessageDispatcherServlet() {
129         this(null);
130     }
131
132     /**
133      * Constructor to support programmatic configuration of the Servlet with the specified
134      * web application context. This constructor is useful in Servlet 3.0+ environments
135      * where instance-based registration of servlets is possible through the
136      * {@code ServletContext#addServlet} API.
137      * <p>Using this constructor indicates that the following properties / init-params
138      * will be ignored:
139      * <ul>
140      * <li>{@link #setContextClass(Class)} / 'contextClass'</li>
141      * <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li>
142      * <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li>
143      * <li>{@link #setNamespace(String)} / 'namespace'</li>
144      * </ul>
145      * <p>The given web application context may or may not yet be {@linkplain
146      * org.springframework.web.context.ConfigurableWebApplicationContext#refresh() refreshed}.
147      * If it has <strong>not</strong> already been refreshed (the recommended approach), then
148      * the following will occur:
149      * <ul>
150      * <li>If the given context does not already have a {@linkplain
151      * org.springframework.web.context.ConfigurableWebApplicationContext#setParent parent},
152      * the root application context will be set as the parent.</li>
153      * <li>If the given context has not already been assigned an {@linkplain
154      * org.springframework.web.context.ConfigurableWebApplicationContext#setId id}, one
155      * will be assigned to it</li>
156      * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
157      * the application context</li>
158      * <li>{@link #postProcessWebApplicationContext} will be called</li>
159      * <li>Any {@code ApplicationContextInitializer}s specified through the
160      * "contextInitializerClasses" init-param or through the {@link
161      * #setContextInitializers} property will be applied.</li>
162      * <li>{@link org.springframework.web.context.ConfigurableWebApplicationContext#refresh refresh()}
163      * will be called if the context implements
164      * {@link org.springframework.web.context.ConfigurableWebApplicationContext}</li>
165      * </ul>
166      * If the context has already been refreshed, none of the above will occur, under the
167      * assumption that the user has performed these actions (or not) per their specific
168      * needs.
169      * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
170      *
171      * @param webApplicationContext the context to use
172      * @see FrameworkServlet#FrameworkServlet(WebApplicationContext)
173      * @see org.springframework.web.WebApplicationInitializer
174      * @see #initWebApplicationContext()
175      * @see #configureAndRefreshWebApplicationContext(org.springframework.web.context.ConfigurableWebApplicationContext)
176      */

177     public MessageDispatcherServlet(WebApplicationContext webApplicationContext) {
178         super(webApplicationContext);
179         defaultStrategiesHelper = new DefaultStrategiesHelper(MessageDispatcherServlet.class);
180     }
181
182     /** Returns the bean name used to lookup a {@link WebServiceMessageFactory}. */
183     public String getMessageFactoryBeanName() {
184         return messageFactoryBeanName;
185     }
186
187     /**
188      * Sets the bean name used to lookup a {@link WebServiceMessageFactory}. Defaults to {@link
189      * #DEFAULT_MESSAGE_FACTORY_BEAN_NAME}.
190      */

191     public void setMessageFactoryBeanName(String messageFactoryBeanName) {
192         this.messageFactoryBeanName = messageFactoryBeanName;
193     }
194
195     /** Returns the bean name used to lookup a {@link WebServiceMessageReceiver}. */
196     public String getMessageReceiverBeanName() {
197         return messageReceiverBeanName;
198     }
199
200     /**
201      * Sets the bean name used to lookup a {@link WebServiceMessageReceiver}. Defaults to {@link
202      * #DEFAULT_MESSAGE_RECEIVER_BEAN_NAME}.
203      */

204     public void setMessageReceiverBeanName(String messageReceiverBeanName) {
205         this.messageReceiverBeanName = messageReceiverBeanName;
206     }
207
208     /**
209      * Indicates whether relative address locations in the WSDL are to be transformed using the request URI of the
210      * incoming {@link HttpServletRequest}.
211      */

212     public boolean isTransformWsdlLocations() {
213         return transformWsdlLocations;
214     }
215
216     /**
217      * Sets whether relative address locations in the WSDL are to be transformed using the request URI of the incoming
218      * {@link HttpServletRequest}. Defaults to {@code false}.
219      */

220     public void setTransformWsdlLocations(boolean transformWsdlLocations) {
221         this.transformWsdlLocations = transformWsdlLocations;
222     }
223
224     /**
225      * Indicates whether relative address locations in the XSD are to be transformed using the request URI of the
226      * incoming {@link HttpServletRequest}.
227      */

228     public boolean isTransformSchemaLocations() {
229         return transformSchemaLocations;
230     }
231
232     /**
233      * Sets whether relative address locations in the XSD are to be transformed using the request URI of the incoming
234      * {@link HttpServletRequest}. Defaults to {@code false}.
235      */

236     public void setTransformSchemaLocations(boolean transformSchemaLocations) {
237         this.transformSchemaLocations = transformSchemaLocations;
238     }
239
240     /** Returns the bean name used to lookup a {@link WebServiceMessageReceiverHandlerAdapter}. */
241     public String getMessageReceiverHandlerAdapterBeanName() {
242         return messageReceiverHandlerAdapterBeanName;
243     }
244
245     /**
246      * Sets the bean name used to lookup a {@link WebServiceMessageReceiverHandlerAdapter}. Defaults to {@link
247      * #DEFAULT_MESSAGE_RECEIVER_HANDLER_ADAPTER_BEAN_NAME}.
248      */

249     public void setMessageReceiverHandlerAdapterBeanName(String messageReceiverHandlerAdapterBeanName) {
250         this.messageReceiverHandlerAdapterBeanName = messageReceiverHandlerAdapterBeanName;
251     }
252
253     /** Returns the bean name used to lookup a {@link WsdlDefinitionHandlerAdapter}. */
254     public String getWsdlDefinitionHandlerAdapterBeanName() {
255         return wsdlDefinitionHandlerAdapterBeanName;
256     }
257
258     /**
259      * Sets the bean name used to lookup a {@link WsdlDefinitionHandlerAdapter}. Defaults to {@link
260      * #DEFAULT_WSDL_DEFINITION_HANDLER_ADAPTER_BEAN_NAME}.
261      */

262     public void setWsdlDefinitionHandlerAdapterBeanName(String wsdlDefinitionHandlerAdapterBeanName) {
263         this.wsdlDefinitionHandlerAdapterBeanName = wsdlDefinitionHandlerAdapterBeanName;
264     }
265
266     /** Returns the bean name used to lookup a {@link XsdSchemaHandlerAdapter}. */
267     public String getXsdSchemaHandlerAdapterBeanName() {
268         return xsdSchemaHandlerAdapterBeanName;
269     }
270
271     /**
272      * Sets the bean name used to lookup a {@link XsdSchemaHandlerAdapter}. Defaults to {@link
273      * #DEFAULT_XSD_SCHEMA_HANDLER_ADAPTER_BEAN_NAME}.
274      */

275     public void setXsdSchemaHandlerAdapterBeanName(String xsdSchemaHandlerAdapterBeanName) {
276         this.xsdSchemaHandlerAdapterBeanName = xsdSchemaHandlerAdapterBeanName;
277     }
278
279
280     @Override
281     protected void doService(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
282             throws Exception {
283         WsdlDefinition definition = getWsdlDefinition(httpServletRequest);
284         if (definition != null) {
285             wsdlDefinitionHandlerAdapter.handle(httpServletRequest, httpServletResponse, definition);
286             return;
287         }
288         XsdSchema schema = getXsdSchema(httpServletRequest);
289         if (schema != null) {
290             xsdSchemaHandlerAdapter.handle(httpServletRequest, httpServletResponse, schema);
291             return;
292         }
293         messageReceiverHandlerAdapter.handle(httpServletRequest, httpServletResponse, messageReceiver);
294     }
295
296     /**
297      * This implementation calls {@link #initStrategies}.
298      */

299     @Override
300     protected void onRefresh(ApplicationContext context) {
301         initStrategies(context);
302     }
303
304     @Override
305     protected long getLastModified(HttpServletRequest httpServletRequest) {
306         WsdlDefinition definition = getWsdlDefinition(httpServletRequest);
307         if (definition != null) {
308             return wsdlDefinitionHandlerAdapter.getLastModified(httpServletRequest, definition);
309         }
310         XsdSchema schema = getXsdSchema(httpServletRequest);
311         if (schema != null) {
312             return xsdSchemaHandlerAdapter.getLastModified(httpServletRequest, schema);
313         }
314         return messageReceiverHandlerAdapter.getLastModified(httpServletRequest, messageReceiver);
315     }
316
317     /** Returns the {@link WebServiceMessageReceiver} used by this servlet. */
318     protected WebServiceMessageReceiver getMessageReceiver() {
319         return messageReceiver;
320     }
321
322     /**
323      * Determines the {@link WsdlDefinition} for a given request, or {@code nullif none is found.
324      *
325      * <p>Default implementation checks whether the request method is {@code GET}, whether the request uri ends with
326      * {@code ".wsdl"}, and if there is a {@code WsdlDefinition} with the same name as the filename in the
327      * request uri.
328      *
329      * @param request the {@code HttpServletRequest}
330      * @return a definition, or {@code null}
331      */

332     protected WsdlDefinition getWsdlDefinition(HttpServletRequest request) {
333         if (HttpTransportConstants.METHOD_GET.equals(request.getMethod()) &&
334                 request.getRequestURI().endsWith(WSDL_SUFFIX_NAME)) {
335             String fileName = WebUtils.extractFilenameFromUrlPath(request.getRequestURI());
336             return wsdlDefinitions.get(fileName);
337         }
338         else {
339             return null;
340         }
341     }
342
343     /**
344      * Determines the {@link XsdSchema} for a given request, or {@code nullif none is found.
345      *
346      * <p>Default implementation checks whether the request method is {@code GET}, whether the request uri ends with
347      * {@code ".xsd"}, and if there is a {@code XsdSchema} with the same name as the filename in the request
348      * uri.
349      *
350      * @param request the {@code HttpServletRequest}
351      * @return a schema, or {@code null}
352      */

353     protected XsdSchema getXsdSchema(HttpServletRequest request) {
354         if (HttpTransportConstants.METHOD_GET.equals(request.getMethod()) &&
355                 request.getRequestURI().endsWith(XSD_SUFFIX_NAME)) {
356             String fileName = WebUtils.extractFilenameFromUrlPath(request.getRequestURI());
357             return xsdSchemas.get(fileName);
358         }
359         else {
360             return null;
361         }
362     }
363
364     /**
365      * Initialize the strategy objects that this servlet uses.
366      * <p>May be overridden in subclasses in order to initialize further strategy objects.
367      */

368     protected void initStrategies(ApplicationContext context) {
369         initMessageReceiverHandlerAdapter(context);
370         initWsdlDefinitionHandlerAdapter(context);
371         initXsdSchemaHandlerAdapter(context);
372         initMessageReceiver(context);
373         initWsdlDefinitions(context);
374         initXsdSchemas(context);
375     }
376
377
378     private void initMessageReceiverHandlerAdapter(ApplicationContext context) {
379         try {
380             try {
381                 messageReceiverHandlerAdapter = context.getBean(getMessageReceiverHandlerAdapterBeanName(),
382                         WebServiceMessageReceiverHandlerAdapter.class);
383             }
384             catch (NoSuchBeanDefinitionException ignored) {
385                 messageReceiverHandlerAdapter = new WebServiceMessageReceiverHandlerAdapter();
386             }
387             initWebServiceMessageFactory(context);
388             messageReceiverHandlerAdapter.afterPropertiesSet();
389         }
390         catch (Exception ex) {
391             throw new BeanInitializationException("Could not initialize WebServiceMessageReceiverHandlerAdapter", ex);
392         }
393     }
394
395     private void initWebServiceMessageFactory(ApplicationContext context) {
396         WebServiceMessageFactory messageFactory;
397         try {
398             messageFactory = context.getBean(getMessageFactoryBeanName(), WebServiceMessageFactory.class);
399         }
400         catch (NoSuchBeanDefinitionException ignored) {
401             messageFactory = defaultStrategiesHelper
402                     .getDefaultStrategy(WebServiceMessageFactory.class, context);
403             if (logger.isDebugEnabled()) {
404                 logger.debug("No WebServiceMessageFactory found in servlet '" + getServletName() + "': using default");
405             }
406         }
407         messageReceiverHandlerAdapter.setMessageFactory(messageFactory);
408     }
409
410     private void initWsdlDefinitionHandlerAdapter(ApplicationContext context) {
411         try {
412             try {
413                 wsdlDefinitionHandlerAdapter =
414                         context.getBean(getWsdlDefinitionHandlerAdapterBeanName(), WsdlDefinitionHandlerAdapter.class);
415
416             }
417             catch (NoSuchBeanDefinitionException ignored) {
418                 wsdlDefinitionHandlerAdapter = new WsdlDefinitionHandlerAdapter();
419             }
420             wsdlDefinitionHandlerAdapter.setTransformLocations(isTransformWsdlLocations());
421             wsdlDefinitionHandlerAdapter.setTransformSchemaLocations(isTransformSchemaLocations());
422             wsdlDefinitionHandlerAdapter.afterPropertiesSet();
423         }
424         catch (Exception ex) {
425             throw new BeanInitializationException("Could not initialize WsdlDefinitionHandlerAdapter", ex);
426         }
427     }
428
429     private void initXsdSchemaHandlerAdapter(ApplicationContext context) {
430         try {
431             try {
432                 xsdSchemaHandlerAdapter = context
433                         .getBean(getXsdSchemaHandlerAdapterBeanName(), XsdSchemaHandlerAdapter.class);
434
435             }
436             catch (NoSuchBeanDefinitionException ignored) {
437                 xsdSchemaHandlerAdapter = new XsdSchemaHandlerAdapter();
438             }
439             xsdSchemaHandlerAdapter.setTransformSchemaLocations(isTransformSchemaLocations());
440             xsdSchemaHandlerAdapter.afterPropertiesSet();
441         }
442         catch (Exception ex) {
443             throw new BeanInitializationException("Could not initialize XsdSchemaHandlerAdapter", ex);
444         }
445     }
446
447     private void initMessageReceiver(ApplicationContext context) {
448         try {
449             messageReceiver = context.getBean(getMessageReceiverBeanName(), WebServiceMessageReceiver.class);
450         }
451         catch (NoSuchBeanDefinitionException ex) {
452             messageReceiver = defaultStrategiesHelper
453                     .getDefaultStrategy(WebServiceMessageReceiver.class, context);
454             if (messageReceiver instanceof BeanNameAware) {
455                 ((BeanNameAware) messageReceiver).setBeanName(getServletName());
456             }
457             if (logger.isDebugEnabled()) {
458                 logger.debug("No MessageDispatcher found in servlet '" + getServletName() + "': using default");
459             }
460         }
461     }
462
463     private void initWsdlDefinitions(ApplicationContext context) {
464         wsdlDefinitions = BeanFactoryUtils
465                 .beansOfTypeIncludingAncestors(context, WsdlDefinition.classtruefalse);
466         if (logger.isDebugEnabled()) {
467             for (Map.Entry<String, WsdlDefinition> entry : wsdlDefinitions.entrySet()) {
468                 String beanName = entry.getKey();
469                 WsdlDefinition definition = entry.getValue();
470                 logger.debug("Published [" + definition + "] as " + beanName + WSDL_SUFFIX_NAME);
471             }
472         }
473     }
474
475     private void initXsdSchemas(ApplicationContext context) {
476         xsdSchemas = BeanFactoryUtils
477                 .beansOfTypeIncludingAncestors(context, XsdSchema.classtruefalse);
478         if (logger.isDebugEnabled()) {
479             for (Map.Entry<String, XsdSchema> entry : xsdSchemas.entrySet()) {
480                 String beanName = entry.getKey();
481                 XsdSchema schema = entry.getValue();
482                 logger.debug("Published [" + schema + "] as " + beanName + XSD_SUFFIX_NAME);
483             }
484         }
485     }
486 }
487