001: /*
002: * Copyright 2005, 2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.ws.server;
018:
019: import java.io.ByteArrayOutputStream;
020: import java.util.ArrayList;
021: import java.util.Collections;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.springframework.beans.BeansException;
029: import org.springframework.beans.factory.BeanFactoryUtils;
030: import org.springframework.beans.factory.BeanNameAware;
031: import org.springframework.context.ApplicationContext;
032: import org.springframework.context.ApplicationContextAware;
033: import org.springframework.core.OrderComparator;
034: import org.springframework.core.io.ClassPathResource;
035: import org.springframework.core.io.Resource;
036: import org.springframework.util.ClassUtils;
037: import org.springframework.util.ObjectUtils;
038: import org.springframework.web.servlet.DispatcherServlet;
039: import org.springframework.ws.FaultAwareWebServiceMessage;
040: import org.springframework.ws.NoEndpointFoundException;
041: import org.springframework.ws.WebServiceMessage;
042: import org.springframework.ws.context.MessageContext;
043: import org.springframework.ws.server.endpoint.MessageEndpoint;
044: import org.springframework.ws.server.endpoint.PayloadEndpoint;
045: import org.springframework.ws.server.endpoint.adapter.MessageEndpointAdapter;
046: import org.springframework.ws.server.endpoint.adapter.MessageMethodEndpointAdapter;
047: import org.springframework.ws.server.endpoint.adapter.PayloadEndpointAdapter;
048: import org.springframework.ws.server.endpoint.adapter.PayloadMethodEndpointAdapter;
049: import org.springframework.ws.soap.server.SoapMessageDispatcher;
050: import org.springframework.ws.transport.WebServiceMessageReceiver;
051: import org.springframework.ws.transport.support.DefaultStrategiesHelper;
052:
053: /**
054: * Central dispatcher for use within Spring-WS, dispatching Web service messages to registered endpoints.
055: * <p/>
056: * This dispatcher is quite similar to Spring MVCs {@link DispatcherServlet}. Just like its counterpart, this dispatcher
057: * is very flexible. This class is SOAP agnostic; in typical SOAP Web Services, the {@link SoapMessageDispatcher}
058: * subclass is used. <ul> <li>It can use any {@link EndpointMapping} implementation - whether standard, or provided as
059: * part of an application - to control the routing of request messages to endpoint objects. Endpoint mappings can be
060: * registered using the <code>endpointMappings</code> property.</li> <li>It can use any {@link EndpointAdapter}; this
061: * allows one to use any endpoint interface or form. Defaults to the {@link MessageEndpointAdapter} and {@link
062: * PayloadEndpointAdapter}, for {@link MessageEndpoint} and {@link PayloadEndpoint}, respectively, and the {@link
063: * MessageMethodEndpointAdapter} and {@link PayloadMethodEndpointAdapter}. Additional endpoint adapters can be added
064: * through the <code>endpointAdapters</code> property.</li> <li>Its exception resolution strategy can be specified via a
065: * {@link EndpointExceptionResolver}, for example mapping certain exceptions to SOAP Faults. Default is none. Additional
066: * exception resolvers can be added through the <code>endpointExceptionResolvers</code> property.</li> </ul>
067: *
068: * @author Arjen Poutsma
069: * @see EndpointMapping
070: * @see EndpointAdapter
071: * @see EndpointExceptionResolver
072: * @see org.springframework.web.servlet.DispatcherServlet
073: * @since 1.0.0
074: */
075: public class MessageDispatcher implements WebServiceMessageReceiver,
076: BeanNameAware, ApplicationContextAware {
077:
078: /** Log category to use when no mapped endpoint is found for a request. */
079: public static final String ENDPOINT_NOT_FOUND_LOG_CATEGORY = "org.springframework.ws.EndpointNotFound";
080:
081: /** Additional logger to use when no mapped endpoint is found for a request. */
082: protected static final Log endpointNotFoundLogger = LogFactory
083: .getLog(MessageDispatcher.ENDPOINT_NOT_FOUND_LOG_CATEGORY);
084:
085: /** Logger available to subclasses. */
086: protected final Log logger = LogFactory.getLog(getClass());
087:
088: private final DefaultStrategiesHelper defaultStrategiesHelper;
089:
090: /** The registered bean name for this dispatcher. */
091: private String beanName;
092:
093: /** List of EndpointAdapters used in this dispatcher. */
094: private List endpointAdapters;
095:
096: /** List of EndpointExceptionResolvers used in this dispatcher. */
097: private List endpointExceptionResolvers;
098:
099: /** List of EndpointMappings used in this dispatcher. */
100: private List endpointMappings;
101:
102: /** Initializes a new instance of the <code>MessageDispatcher</code>. */
103: public MessageDispatcher() {
104: Resource resource = new ClassPathResource(ClassUtils
105: .getShortName(getClass())
106: + ".properties", getClass());
107: defaultStrategiesHelper = new DefaultStrategiesHelper(resource);
108: }
109:
110: /** Returns the <code>EndpointAdapter</code>s to use by this <code>MessageDispatcher</code>. */
111: public List getEndpointAdapters() {
112: return endpointAdapters;
113: }
114:
115: /** Sets the <code>EndpointAdapter</code>s to use by this <code>MessageDispatcher</code>. */
116: public void setEndpointAdapters(List endpointAdapters) {
117: this .endpointAdapters = endpointAdapters;
118: }
119:
120: /** Returns the <code>EndpointExceptionResolver</code>s to use by this <code>MessageDispatcher</code>. */
121: public List getEndpointExceptionResolvers() {
122: return endpointExceptionResolvers;
123: }
124:
125: /** Sets the <code>EndpointExceptionResolver</code>s to use by this <code>MessageDispatcher</code>. */
126: public void setEndpointExceptionResolvers(
127: List endpointExceptionResolvers) {
128: this .endpointExceptionResolvers = endpointExceptionResolvers;
129: }
130:
131: /** Returns the <code>EndpointMapping</code>s to use by this <code>MessageDispatcher</code>. */
132: public List getEndpointMappings() {
133: return endpointMappings;
134: }
135:
136: /** Sets the <code>EndpointMapping</code>s to use by this <code>MessageDispatcher</code>. */
137: public void setEndpointMappings(List endpointMappings) {
138: this .endpointMappings = endpointMappings;
139: }
140:
141: public final void setBeanName(String beanName) {
142: this .beanName = beanName;
143: }
144:
145: public void setApplicationContext(
146: ApplicationContext applicationContext)
147: throws BeansException {
148: initEndpointAdapters(applicationContext);
149: initEndpointExceptionResolvers(applicationContext);
150: initEndpointMappings(applicationContext);
151: }
152:
153: public void receive(MessageContext messageContext) throws Exception {
154: if (logger.isTraceEnabled()) {
155: ByteArrayOutputStream os = new ByteArrayOutputStream();
156: messageContext.getRequest().writeTo(os);
157: logger.trace("MessageDispatcher with name '" + beanName
158: + "' received request [" + os.toString("UTF-8")
159: + "]");
160: } else if (logger.isDebugEnabled()) {
161: logger.debug("MessageDispatcher with name '" + beanName
162: + "' received request ["
163: + messageContext.getRequest() + "]");
164: }
165: dispatch(messageContext);
166: if (messageContext.hasResponse()) {
167: if (logger.isTraceEnabled()) {
168: ByteArrayOutputStream requestStream = new ByteArrayOutputStream();
169: messageContext.getRequest().writeTo(requestStream);
170: ByteArrayOutputStream responseStream = new ByteArrayOutputStream();
171: messageContext.getResponse().writeTo(responseStream);
172: logger.trace("MessageDispatcher with name '" + beanName
173: + "' sends response ["
174: + responseStream.toString("UTF-8")
175: + "] for request ["
176: + requestStream.toString("UTF-8") + "]");
177: } else if (logger.isDebugEnabled()) {
178: logger.debug("MessageDispatcher with name '" + beanName
179: + "' sends response ["
180: + messageContext.getResponse()
181: + "] for request ["
182: + messageContext.getRequest() + "]");
183: }
184: } else if (logger.isDebugEnabled()) {
185: logger.debug("MessageDispatcher with name '" + beanName
186: + "' sends no response for request ["
187: + messageContext.getRequest() + "]");
188: }
189: }
190:
191: /**
192: * Dispatches the request in the given MessageContext according to the configuration.
193: *
194: * @param messageContext the message context
195: * @throws org.springframework.ws.NoEndpointFoundException
196: * thrown when an endpoint cannot be resolved for the incoming message
197: */
198: protected final void dispatch(MessageContext messageContext)
199: throws Exception {
200: EndpointInvocationChain mappedEndpoint = null;
201: int interceptorIndex = -1;
202: try {
203: // Determine endpoint for the current context
204: mappedEndpoint = getEndpoint(messageContext);
205: if (mappedEndpoint == null
206: || mappedEndpoint.getEndpoint() == null) {
207: throw new NoEndpointFoundException(messageContext
208: .getRequest());
209: }
210: if (!handleRequest(mappedEndpoint, messageContext)) {
211: return;
212: }
213: // Apply handleRequest of registered interceptors
214: if (mappedEndpoint.getInterceptors() != null) {
215: for (int i = 0; i < mappedEndpoint.getInterceptors().length; i++) {
216: EndpointInterceptor interceptor = mappedEndpoint
217: .getInterceptors()[i];
218: interceptorIndex = i;
219: if (!interceptor.handleRequest(messageContext,
220: mappedEndpoint.getEndpoint())) {
221: triggerHandleResponse(mappedEndpoint,
222: interceptorIndex, messageContext);
223: return;
224: }
225: }
226: }
227: // Acutally invoke the endpoint
228: EndpointAdapter endpointAdapter = getEndpointAdapter(mappedEndpoint
229: .getEndpoint());
230: endpointAdapter.invoke(messageContext, mappedEndpoint
231: .getEndpoint());
232:
233: // Apply handleResponse methods of registered interceptors
234: triggerHandleResponse(mappedEndpoint, interceptorIndex,
235: messageContext);
236: } catch (NoEndpointFoundException ex) {
237: // No triggering of interceptors if no endpoint is found
238: if (endpointNotFoundLogger.isWarnEnabled()) {
239: endpointNotFoundLogger
240: .warn("No endpoint mapping found for ["
241: + messageContext.getRequest() + "]");
242: }
243: throw ex;
244: } catch (Exception ex) {
245: Object endpoint = mappedEndpoint != null ? mappedEndpoint
246: .getEndpoint() : null;
247: processEndpointException(messageContext, endpoint, ex);
248: triggerHandleResponse(mappedEndpoint, interceptorIndex,
249: messageContext);
250: }
251: }
252:
253: /**
254: * Returns the endpoint for this request. All endpoint mappings are tried, in order.
255: *
256: * @return the <code>EndpointInvocationChain</code>, or <code>null</code> if no endpoint could be found.
257: */
258: protected EndpointInvocationChain getEndpoint(
259: MessageContext messageContext) throws Exception {
260: for (Iterator iterator = endpointMappings.iterator(); iterator
261: .hasNext();) {
262: EndpointMapping endpointMapping = (EndpointMapping) iterator
263: .next();
264: EndpointInvocationChain endpoint = endpointMapping
265: .getEndpoint(messageContext);
266: if (endpoint != null) {
267: if (logger.isDebugEnabled()) {
268: logger.debug("Endpoint mapping [" + endpointMapping
269: + "] maps request to endpoint ["
270: + endpoint.getEndpoint() + "]");
271: }
272: return endpoint;
273: } else if (logger.isDebugEnabled()) {
274: logger.debug("Endpoint mapping [" + endpointMapping
275: + "] has no mapping for request");
276: }
277: }
278: return null;
279: }
280:
281: /**
282: * Returns the <code>EndpointAdapter</code> for the given endpoint.
283: *
284: * @param endpoint the endpoint to find an adapter for
285: * @return the adapter
286: */
287: protected EndpointAdapter getEndpointAdapter(Object endpoint) {
288: for (Iterator iterator = endpointAdapters.iterator(); iterator
289: .hasNext();) {
290: EndpointAdapter endpointAdapter = (EndpointAdapter) iterator
291: .next();
292: if (logger.isDebugEnabled()) {
293: logger.debug("Testing endpoint adapter ["
294: + endpointAdapter + "]");
295: }
296: if (endpointAdapter.supports(endpoint)) {
297: return endpointAdapter;
298: }
299: }
300: throw new IllegalStateException(
301: "No adapter for endpoint ["
302: + endpoint
303: + "]: Does your endpoint implement a "
304: + "supported interface like MessageHandler or PayloadEndpoint?");
305: }
306:
307: /**
308: * Callback for pre-processing of given invocation chain and message context. Gets called before invocation of
309: * <code>handleRequest</code> on the interceptors.
310: * <p/>
311: * Default implementation does nothing, and returns <code>true</code>.
312: *
313: * @param mappedEndpoint the mapped <code>EndpointInvocationChain</code>
314: * @param messageContext the message context
315: * @return <code>true</code> if processing should continue; <code>false</code> otherwise
316: */
317: protected boolean handleRequest(
318: EndpointInvocationChain mappedEndpoint,
319: MessageContext messageContext) {
320: return true;
321: }
322:
323: /**
324: * Determine an error <code>SOAPMessage</code> respone via the registered <code>EndpointExceptionResolvers</code>.
325: * Most likely, the response contains a <code>SOAPFault</code>. If no suitable resolver was found, the exception is
326: * rethrown.
327: *
328: * @param messageContext current SOAPMessage request
329: * @param endpoint the executed endpoint, or null if none chosen at the time of the exception
330: * @param ex the exception that got thrown during handler execution
331: * @throws Exception if no suitable resolver is found
332: */
333: protected void processEndpointException(
334: MessageContext messageContext, Object endpoint, Exception ex)
335: throws Exception {
336: for (Iterator iterator = endpointExceptionResolvers.iterator(); iterator
337: .hasNext();) {
338: EndpointExceptionResolver resolver = (EndpointExceptionResolver) iterator
339: .next();
340: if (logger.isDebugEnabled()) {
341: logger.debug("Testing endpoint exception resolver ["
342: + resolver + "]");
343: }
344: if (resolver.resolveException(messageContext, endpoint, ex)) {
345: logger
346: .warn(
347: "Endpoint invocation resulted in exception - responding with SOAP Fault",
348: ex);
349: return;
350: }
351: }
352: // exception not resolved
353: throw ex;
354: }
355:
356: /**
357: * Trigger handleResponse or handleFault on the mapped EndpointInterceptors. Will just invoke said method on all
358: * interceptors whose handleRequest invocation returned <code>true</code>, in addition to the last interceptor who
359: * returned <code>false</code>.
360: *
361: * @param mappedEndpoint the mapped EndpointInvocationChain
362: * @param interceptorIndex index of last interceptor that was called
363: * @param messageContext the message context, whose request and response are filled
364: * @see EndpointInterceptor#handleResponse(MessageContext,Object)
365: */
366: private void triggerHandleResponse(
367: EndpointInvocationChain mappedEndpoint,
368: int interceptorIndex, MessageContext messageContext)
369: throws Exception {
370: if (mappedEndpoint != null
371: && messageContext.hasResponse()
372: && !ObjectUtils.isEmpty(mappedEndpoint
373: .getInterceptors())) {
374: boolean hasFault = false;
375: WebServiceMessage response = messageContext.getResponse();
376: if (response instanceof FaultAwareWebServiceMessage) {
377: hasFault = ((FaultAwareWebServiceMessage) response)
378: .hasFault();
379: }
380: boolean resume = true;
381: for (int i = interceptorIndex; resume && i >= 0; i--) {
382: EndpointInterceptor interceptor = mappedEndpoint
383: .getInterceptors()[i];
384: if (!hasFault) {
385: resume = interceptor.handleResponse(messageContext,
386: mappedEndpoint.getEndpoint());
387: } else {
388: resume = interceptor.handleFault(messageContext,
389: mappedEndpoint.getEndpoint());
390: }
391: }
392: }
393: }
394:
395: /**
396: * Initialize the <code>EndpointAdapters</code> used by this class. If no adapter beans are explictely set by using
397: * the <code>endpointAdapters</code> property, we use the default strategies.
398: *
399: * @see #setEndpointAdapters(java.util.List)
400: */
401: private void initEndpointAdapters(
402: ApplicationContext applicationContext)
403: throws BeansException {
404: if (endpointAdapters == null) {
405: Map matchingBeans = BeanFactoryUtils
406: .beansOfTypeIncludingAncestors(applicationContext,
407: EndpointAdapter.class, true, false);
408: if (!matchingBeans.isEmpty()) {
409: endpointAdapters = new ArrayList(matchingBeans.values());
410: Collections.sort(endpointAdapters,
411: new OrderComparator());
412: } else {
413: endpointAdapters = defaultStrategiesHelper
414: .getDefaultStrategies(EndpointAdapter.class,
415: applicationContext);
416: if (logger.isDebugEnabled()) {
417: logger
418: .debug("No EndpointAdapters found, using defaults");
419: }
420: }
421: }
422: }
423:
424: /**
425: * Initialize the <code>EndpointExceptionResolver</code> used by this class. If no resolver beans are explictely set
426: * by using the <code>endpointExceptionResolvers</code> property, we use the default strategies.
427: *
428: * @see #setEndpointExceptionResolvers(java.util.List)
429: */
430: private void initEndpointExceptionResolvers(
431: ApplicationContext applicationContext)
432: throws BeansException {
433: if (endpointExceptionResolvers == null) {
434: Map matchingBeans = BeanFactoryUtils
435: .beansOfTypeIncludingAncestors(applicationContext,
436: EndpointExceptionResolver.class, true,
437: false);
438: if (!matchingBeans.isEmpty()) {
439: endpointExceptionResolvers = new ArrayList(
440: matchingBeans.values());
441: Collections.sort(endpointExceptionResolvers,
442: new OrderComparator());
443: } else {
444: endpointExceptionResolvers = defaultStrategiesHelper
445: .getDefaultStrategies(
446: EndpointExceptionResolver.class,
447: applicationContext);
448: if (logger.isDebugEnabled()) {
449: logger
450: .debug("No EndpointExceptionResolvers found, using defaults");
451: }
452: }
453: }
454: }
455:
456: /**
457: * Initialize the <code>EndpointMappings</code> used by this class. If no mapping beans are explictely set by using
458: * the <code>endpointMappings</code> property, we use the default strategies.
459: *
460: * @see #setEndpointMappings(java.util.List)
461: */
462: private void initEndpointMappings(
463: ApplicationContext applicationContext)
464: throws BeansException {
465: if (endpointMappings == null) {
466: Map matchingBeans = BeanFactoryUtils
467: .beansOfTypeIncludingAncestors(applicationContext,
468: EndpointMapping.class, true, false);
469: if (!matchingBeans.isEmpty()) {
470: endpointMappings = new ArrayList(matchingBeans.values());
471: Collections.sort(endpointMappings,
472: new OrderComparator());
473: } else {
474: endpointMappings = defaultStrategiesHelper
475: .getDefaultStrategies(EndpointMapping.class,
476: applicationContext);
477: if (logger.isDebugEnabled()) {
478: logger
479: .debug("No EndpointMappings found, using defaults");
480: }
481: }
482: }
483: }
484: }
|