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.server;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.springframework.beans.BeansException;
27  import org.springframework.beans.factory.BeanFactoryUtils;
28  import org.springframework.beans.factory.BeanNameAware;
29  import org.springframework.context.ApplicationContext;
30  import org.springframework.context.ApplicationContextAware;
31  import org.springframework.core.OrderComparator;
32  import org.springframework.util.CollectionUtils;
33  import org.springframework.util.ObjectUtils;
34  import org.springframework.web.servlet.DispatcherServlet;
35  import org.springframework.ws.FaultAwareWebServiceMessage;
36  import org.springframework.ws.NoEndpointFoundException;
37  import org.springframework.ws.WebServiceMessage;
38  import org.springframework.ws.context.MessageContext;
39  import org.springframework.ws.server.endpoint.MessageEndpoint;
40  import org.springframework.ws.server.endpoint.PayloadEndpoint;
41  import org.springframework.ws.server.endpoint.adapter.MessageEndpointAdapter;
42  import org.springframework.ws.server.endpoint.adapter.PayloadEndpointAdapter;
43  import org.springframework.ws.soap.server.SoapMessageDispatcher;
44  import org.springframework.ws.support.DefaultStrategiesHelper;
45  import org.springframework.ws.transport.WebServiceMessageReceiver;
46  
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  
50  /**
51   * Central dispatcher for use within Spring-WS, dispatching Web service messages to registered endpoints.
52   * <p/>
53   * This dispatcher is quite similar to Spring MVCs {@link DispatcherServlet}. Just like its counterpart, this dispatcher
54   * is very flexible. This class is SOAP agnostic; in typical SOAP Web Services, the {@link SoapMessageDispatcher}
55   * subclass is used.
56   * <ul>
57   * <li>It can use any {@link EndpointMapping} implementation - whether standard, or provided as
58   * part of an application - to control the routing of request messages to endpoint objects. Endpoint mappings can be
59   * registered using the {@link #setEndpointMappings(List) endpointMappings} property.</li>
60   * <li>It can use any {@link EndpointAdapter}; this allows one to use any endpoint interface or form. Defaults to
61   * the {@link MessageEndpointAdapter} and {@link PayloadEndpointAdapter}, for {@link MessageEndpoint} and
62   * {@link PayloadEndpoint}, respectively, and the
63   * {@link org.springframework.ws.server.endpoint.adapter.MessageMethodEndpointAdapter MessageMethodEndpointAdapter} and
64   * {@link org.springframework.ws.server.endpoint.adapter.PayloadMethodEndpointAdapter PayloadMethodEndpointAdapter}.
65   * Additional endpoint adapters can be added through the {@link #setEndpointAdapters(List) endpointAdapters} property.</li>
66   * <li>Its exception resolution strategy can be specified via a
67   * {@link EndpointExceptionResolver}, for example mapping certain exceptions to SOAP Faults. Default is none. Additional
68   * exception resolvers can be added through the {@link #setEndpointExceptionResolvers(List) endpointExceptionResolvers}
69   * property.</li>
70   * </ul>
71   *
72   * @author Arjen Poutsma
73   * @see EndpointMapping
74   * @see EndpointAdapter
75   * @see EndpointExceptionResolver
76   * @see org.springframework.web.servlet.DispatcherServlet
77   * @since 1.0.0
78   */
79  public class MessageDispatcher implements WebServiceMessageReceiver, BeanNameAware, ApplicationContextAware {
80  
81      /** Logger available to subclasses. */
82      protected final Log logger = LogFactory.getLog(getClass());
83  
84      /** Log category to use when no mapped endpoint is found for a request. */
85      public static final String ENDPOINT_NOT_FOUND_LOG_CATEGORY = "org.springframework.ws.server.EndpointNotFound";
86  
87      /** Additional logger to use when no mapped endpoint is found for a request. */
88      protected static final Log endpointNotFoundLogger =
89              LogFactory.getLog(MessageDispatcher.ENDPOINT_NOT_FOUND_LOG_CATEGORY);
90  
91      /** Log category to use for message tracing. */
92      public static final String MESSAGE_TRACING_LOG_CATEGORY = "org.springframework.ws.server.MessageTracing";
93  
94      /** Additional logger to use for sent message tracing. */
95      protected static final Log sentMessageTracingLogger =
96              LogFactory.getLog(MessageDispatcher.MESSAGE_TRACING_LOG_CATEGORY + ".sent");
97  
98      /** Additional logger to use for received message tracing. */
99      protected static final Log receivedMessageTracingLogger =
100             LogFactory.getLog(MessageDispatcher.MESSAGE_TRACING_LOG_CATEGORY + ".received");
101 
102     private final DefaultStrategiesHelper defaultStrategiesHelper;
103 
104     /** The registered bean name for this dispatcher. */
105     private String beanName;
106 
107     /** List of EndpointAdapters used in this dispatcher. */
108     private List<EndpointAdapter> endpointAdapters;
109 
110     /** List of EndpointExceptionResolvers used in this dispatcher. */
111     private List<EndpointExceptionResolver> endpointExceptionResolvers;
112 
113     /** List of EndpointMappings used in this dispatcher. */
114     private List<EndpointMapping> endpointMappings;
115 
116     /** Initializes a new instance of the <code>MessageDispatcher</code>. */
117     public MessageDispatcher() {
118         defaultStrategiesHelper = new DefaultStrategiesHelper(getClass());
119     }
120 
121     /** Returns the <code>EndpointAdapter</code>s to use by this <code>MessageDispatcher</code>. */
122     public List<EndpointAdapter> getEndpointAdapters() {
123         return endpointAdapters;
124     }
125 
126     /** Sets the <code>EndpointAdapter</code>s to use by this <code>MessageDispatcher</code>. */
127     public void setEndpointAdapters(List<EndpointAdapter> endpointAdapters) {
128         this.endpointAdapters = endpointAdapters;
129     }
130 
131     /** Returns the <code>EndpointExceptionResolver</code>s to use by this <code>MessageDispatcher</code>. */
132     public List<EndpointExceptionResolver> getEndpointExceptionResolvers() {
133         return endpointExceptionResolvers;
134     }
135 
136     /** Sets the <code>EndpointExceptionResolver</code>s to use by this <code>MessageDispatcher</code>. */
137     public void setEndpointExceptionResolvers(List<EndpointExceptionResolver> endpointExceptionResolvers) {
138         this.endpointExceptionResolvers = endpointExceptionResolvers;
139     }
140 
141     /** Returns the <code>EndpointMapping</code>s to use by this <code>MessageDispatcher</code>. */
142     public List<EndpointMapping> getEndpointMappings() {
143         return endpointMappings;
144     }
145 
146     /** Sets the <code>EndpointMapping</code>s to use by this <code>MessageDispatcher</code>. */
147     public void setEndpointMappings(List<EndpointMapping> endpointMappings) {
148         this.endpointMappings = endpointMappings;
149     }
150 
151     public final void setBeanName(String beanName) {
152         this.beanName = beanName;
153     }
154 
155     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
156         initEndpointAdapters(applicationContext);
157         initEndpointExceptionResolvers(applicationContext);
158         initEndpointMappings(applicationContext);
159     }
160 
161     public void receive(MessageContext messageContext) throws Exception {
162         // Let's keep a reference to the request content as it came in, it might be changed by interceptors in dispatch()
163         String requestContent = "";
164         if (receivedMessageTracingLogger.isTraceEnabled() || sentMessageTracingLogger.isTraceEnabled()) {
165             requestContent = getMessageContent(messageContext.getRequest());            
166         }
167         if (receivedMessageTracingLogger.isTraceEnabled()) {
168             receivedMessageTracingLogger.trace("Received request [" + requestContent + "]");
169         }
170         else if (receivedMessageTracingLogger.isDebugEnabled()) {
171             receivedMessageTracingLogger.debug("Received request [" + messageContext.getRequest() + "]");
172         }
173         dispatch(messageContext);
174         if (messageContext.hasResponse()) {
175             WebServiceMessage response = messageContext.getResponse();
176             if (sentMessageTracingLogger.isTraceEnabled()) {
177                 String responseContent = getMessageContent(response);
178                 sentMessageTracingLogger.trace("Sent response [" + responseContent + "] for request [" +
179                                 requestContent + "]");
180             }
181             else if (sentMessageTracingLogger.isDebugEnabled()) {
182                 sentMessageTracingLogger.debug("Sent response [" + response + "] for request [" +
183                         messageContext.getRequest() + "]");
184             }
185         }
186         else if (sentMessageTracingLogger.isDebugEnabled()) {
187             sentMessageTracingLogger
188                     .debug("MessageDispatcher with name '" + beanName + "' sends no response for request [" +
189                             messageContext.getRequest() + "]");
190         }
191     }
192 
193     private String getMessageContent(WebServiceMessage message) throws IOException {
194         ByteArrayOutputStream bos = new ByteArrayOutputStream();
195         message.writeTo(bos);
196         return bos.toString("UTF-8");
197     }
198 
199     /**
200      * Dispatches the request in the given MessageContext according to the configuration.
201      *
202      * @param messageContext the message context
203      * @throws org.springframework.ws.NoEndpointFoundException
204      *          thrown when an endpoint cannot be resolved for the incoming message
205      */
206     protected final void dispatch(MessageContext messageContext) throws Exception {
207         EndpointInvocationChain mappedEndpoint = null;
208         int interceptorIndex = -1;
209         try {
210             try {
211                 // Determine endpoint for the current context
212                 mappedEndpoint = getEndpoint(messageContext);
213                 if (mappedEndpoint == null || mappedEndpoint.getEndpoint() == null) {
214                     throw new NoEndpointFoundException(messageContext.getRequest());
215                 }
216                 if (!handleRequest(mappedEndpoint, messageContext)) {
217                     return;
218                 }
219                 // Apply handleRequest of registered interceptors
220                 if (mappedEndpoint.getInterceptors() != null) {
221                     for (int i = 0; i < mappedEndpoint.getInterceptors().length; i++) {
222                         EndpointInterceptor interceptor = mappedEndpoint.getInterceptors()[i];
223                         interceptorIndex = i;
224                         if (!interceptor.handleRequest(messageContext, mappedEndpoint.getEndpoint())) {
225                             triggerHandleResponse(mappedEndpoint, interceptorIndex, messageContext);
226                             triggerAfterCompletion(mappedEndpoint, interceptorIndex, messageContext, null);
227                             return;
228                         }
229                     }
230                 }
231                 // Actually invoke the endpoint
232                 EndpointAdapter endpointAdapter = getEndpointAdapter(mappedEndpoint.getEndpoint());
233                 endpointAdapter.invoke(messageContext, mappedEndpoint.getEndpoint());
234 
235 	            // Apply handleResponse methods of registered interceptors
236 	            triggerHandleResponse(mappedEndpoint, interceptorIndex, messageContext);
237             }
238             catch (NoEndpointFoundException ex) {
239                 // No triggering of interceptors if no endpoint is found
240                 if (endpointNotFoundLogger.isWarnEnabled()) {
241                     endpointNotFoundLogger.warn("No endpoint mapping found for [" + messageContext.getRequest() + "]");
242                 }
243                 throw ex;
244             }
245             catch (Exception ex) {
246                 Object endpoint = mappedEndpoint != null ? mappedEndpoint.getEndpoint() : null;
247                 processEndpointException(messageContext, endpoint, ex);
248 	            triggerHandleResponse(mappedEndpoint, interceptorIndex, messageContext);
249             }
250             triggerAfterCompletion(mappedEndpoint, interceptorIndex, messageContext, null);
251         }
252         catch (NoEndpointFoundException ex) {
253             throw ex;
254         }
255         catch (Exception ex) {
256 			// Trigger after-completion for thrown exception.
257             triggerAfterCompletion(mappedEndpoint, interceptorIndex, messageContext, ex);
258             throw ex;
259         }
260     }
261 
262     /**
263      * Returns the endpoint for this request. All endpoint mappings are tried, in order.
264      *
265      * @return the <code>EndpointInvocationChain</code>, or <code>null</code> if no endpoint could be found.
266      */
267     protected EndpointInvocationChain getEndpoint(MessageContext messageContext) throws Exception {
268         for (EndpointMapping endpointMapping : getEndpointMappings()) {
269             EndpointInvocationChain endpoint = endpointMapping.getEndpoint(messageContext);
270             if (endpoint != null) {
271                 if (logger.isDebugEnabled()) {
272                     logger.debug("Endpoint mapping [" + endpointMapping + "] maps request to endpoint [" +
273                             endpoint.getEndpoint() + "]");
274                 }
275                 return endpoint;
276             }
277             else if (logger.isDebugEnabled()) {
278                 logger.debug("Endpoint mapping [" + endpointMapping + "] has no mapping for request");
279             }
280         }
281         return null;
282     }
283 
284     /**
285      * Returns the <code>EndpointAdapter</code> for the given endpoint.
286      *
287      * @param endpoint the endpoint to find an adapter for
288      * @return the adapter
289      */
290     protected EndpointAdapter getEndpointAdapter(Object endpoint) {
291         for (EndpointAdapter endpointAdapter : getEndpointAdapters()) {
292             if (logger.isDebugEnabled()) {
293                 logger.debug("Testing endpoint adapter [" + endpointAdapter + "]");
294             }
295             if (endpointAdapter.supports(endpoint)) {
296                 return endpointAdapter;
297             }
298         }
299         throw new IllegalStateException("No adapter for endpoint [" + endpoint + "]: Is your endpoint annotated with " +
300                 "@Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?");
301     }
302 
303     /**
304      * Callback for pre-processing of given invocation chain and message context. Gets called before invocation of
305      * <code>handleRequest</code> on the interceptors.
306      * <p/>
307      * Default implementation does nothing, and returns <code>true</code>.
308      *
309      * @param mappedEndpoint the mapped <code>EndpointInvocationChain</code>
310      * @param messageContext the message context
311      * @return <code>true</code> if processing should continue; <code>false</code> otherwise
312      */
313     protected boolean handleRequest(EndpointInvocationChain mappedEndpoint, MessageContext messageContext) {
314         return true;
315     }
316 
317     /**
318      * Determine an error <code>SOAPMessage</code> response via the registered <code>EndpointExceptionResolvers</code>.
319      * Most likely, the response contains a <code>SOAPFault</code>. If no suitable resolver was found, the exception is
320      * rethrown.
321      *
322      * @param messageContext current SOAPMessage request
323      * @param endpoint       the executed endpoint, or null if none chosen at the time of the exception
324      * @param ex             the exception that got thrown during handler execution
325      * @throws Exception if no suitable resolver is found
326      */
327     protected void processEndpointException(MessageContext messageContext, Object endpoint, Exception ex)
328             throws Exception {
329         if (!CollectionUtils.isEmpty(getEndpointExceptionResolvers())) {
330             for (EndpointExceptionResolver resolver : getEndpointExceptionResolvers()) {
331                 if (resolver.resolveException(messageContext, endpoint, ex)) {
332                     if (logger.isDebugEnabled()) {
333                         logger.debug("Endpoint invocation resulted in exception - responding with Fault", ex);
334                     }
335                     return;
336                 }
337             }
338         }
339         // exception not resolved
340         throw ex;
341     }
342 
343     /**
344      * Trigger handleResponse or handleFault on the mapped EndpointInterceptors. Will just invoke said method on all
345      * interceptors whose handleRequest invocation returned <code>true</code>, in addition to the last interceptor who
346      * returned <code>false</code>.
347      *
348      * @param mappedEndpoint   the mapped EndpointInvocationChain
349      * @param interceptorIndex index of last interceptor that was called
350      * @param messageContext   the message context, whose request and response are filled
351      * @see EndpointInterceptor#handleResponse(MessageContext,Object)
352      * @see EndpointInterceptor#handleFault(MessageContext, Object)
353      */
354     private void triggerHandleResponse(EndpointInvocationChain mappedEndpoint,
355                                        int interceptorIndex,
356                                        MessageContext messageContext) throws Exception {
357         if (mappedEndpoint != null && messageContext.hasResponse() &&
358                 !ObjectUtils.isEmpty(mappedEndpoint.getInterceptors())) {
359             boolean hasFault = false;
360             WebServiceMessage response = messageContext.getResponse();
361             if (response instanceof FaultAwareWebServiceMessage) {
362                 hasFault = ((FaultAwareWebServiceMessage) response).hasFault();
363             }
364             boolean resume = true;
365             for (int i = interceptorIndex; resume && i >= 0; i--) {
366                 EndpointInterceptor interceptor = mappedEndpoint.getInterceptors()[i];
367                 if (!hasFault) {
368                     resume = interceptor.handleResponse(messageContext, mappedEndpoint.getEndpoint());
369                 }
370                 else {
371                     resume = interceptor.handleFault(messageContext, mappedEndpoint.getEndpoint());
372                 }
373             }
374         }
375     }
376 
377     /**
378      * Trigger afterCompletion callbacks on the mapped EndpointInterceptors.
379      * Will just invoke afterCompletion for all interceptors whose handleRequest invocation
380      * has successfully completed and returned true, in addition to the last interceptor who
381      * returned <code>false</code>.
382      * 
383      * @param mappedEndpoint   the mapped EndpointInvocationChain
384      * @param interceptorIndex index of last interceptor that successfully completed
385      * @param ex Exception thrown on handler execution, or <code>null</code> if none
386      * @see EndpointInterceptor#afterCompletion
387      */
388     private void triggerAfterCompletion(EndpointInvocationChain mappedEndpoint,
389             int interceptorIndex,
390             MessageContext messageContext,
391             Exception ex) throws Exception {
392 
393         // Apply afterCompletion methods of registered interceptors.
394         if (mappedEndpoint != null) {
395             EndpointInterceptor[] interceptors = mappedEndpoint.getInterceptors();
396             if (interceptors != null) {
397                 for (int i = interceptorIndex; i >= 0; i--) {
398                     EndpointInterceptor interceptor = interceptors[i];
399                     try {
400                         interceptor.afterCompletion(messageContext, mappedEndpoint.getEndpoint(), ex);
401                     }
402                     catch (Throwable ex2) {
403                         logger.error("EndpointInterceptor.afterCompletion threw exception", ex2);
404                     }
405                 }
406             }
407         }
408     }
409 
410 
411     /**
412      * Initialize the <code>EndpointAdapters</code> used by this class. If no adapter beans are explicitly set by using
413      * the <code>endpointAdapters</code> property, we use the default strategies.
414      *
415      * @see #setEndpointAdapters(java.util.List)
416      */
417     private void initEndpointAdapters(ApplicationContext applicationContext) throws BeansException {
418         if (endpointAdapters == null) {
419             Map<String, EndpointAdapter> matchingBeans = BeanFactoryUtils
420                     .beansOfTypeIncludingAncestors(applicationContext, EndpointAdapter.class, true, false);
421             if (!matchingBeans.isEmpty()) {
422                 endpointAdapters = new ArrayList<EndpointAdapter>(matchingBeans.values());
423                 Collections.sort(endpointAdapters, new OrderComparator());
424             }
425             else {
426                 endpointAdapters =
427                         defaultStrategiesHelper.getDefaultStrategies(EndpointAdapter.class, applicationContext);
428                 if (logger.isDebugEnabled()) {
429                     logger.debug("No EndpointAdapters found, using defaults");
430                 }
431             }
432         }
433     }
434 
435     /**
436      * Initialize the <code>EndpointExceptionResolver</code> used by this class. If no resolver beans are explicitly set
437      * by using the <code>endpointExceptionResolvers</code> property, we use the default strategies.
438      *
439      * @see #setEndpointExceptionResolvers(java.util.List)
440      */
441     private void initEndpointExceptionResolvers(ApplicationContext applicationContext) throws BeansException {
442         if (endpointExceptionResolvers == null) {
443             Map<String, EndpointExceptionResolver> matchingBeans = BeanFactoryUtils
444                     .beansOfTypeIncludingAncestors(applicationContext, EndpointExceptionResolver.class, true, false);
445             if (!matchingBeans.isEmpty()) {
446                 endpointExceptionResolvers = new ArrayList<EndpointExceptionResolver>(matchingBeans.values());
447                 Collections.sort(endpointExceptionResolvers, new OrderComparator());
448             }
449             else {
450                 endpointExceptionResolvers = defaultStrategiesHelper
451                         .getDefaultStrategies(EndpointExceptionResolver.class, applicationContext);
452                 if (logger.isDebugEnabled()) {
453                     logger.debug("No EndpointExceptionResolvers found, using defaults");
454                 }
455             }
456         }
457     }
458 
459     /**
460      * Initialize the <code>EndpointMappings</code> used by this class. If no mapping beans are explictely set by using
461      * the <code>endpointMappings</code> property, we use the default strategies.
462      *
463      * @see #setEndpointMappings(java.util.List)
464      */
465     private void initEndpointMappings(ApplicationContext applicationContext) throws BeansException {
466         if (endpointMappings == null) {
467             Map<String, EndpointMapping> matchingBeans = BeanFactoryUtils
468                     .beansOfTypeIncludingAncestors(applicationContext, EndpointMapping.class, true, false);
469             if (!matchingBeans.isEmpty()) {
470                 endpointMappings = new ArrayList<EndpointMapping>(matchingBeans.values());
471                 Collections.sort(endpointMappings, new OrderComparator());
472             }
473             else {
474                 endpointMappings =
475                         defaultStrategiesHelper.getDefaultStrategies(EndpointMapping.class, applicationContext);
476                 if (logger.isDebugEnabled()) {
477                     logger.debug("No EndpointMappings found, using defaults");
478                 }
479             }
480         }
481     }
482 }