View Javadoc

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