View Javadoc

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