View Javadoc

1   /*
2    * $Id: AbstractOutboundRouter.java 20555 2010-12-09 17:54:31Z aperepel $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.routing.outbound;
12  
13  import org.mule.DefaultMuleEvent;
14  import org.mule.DefaultMuleMessage;
15  import org.mule.api.MessagingException;
16  import org.mule.api.MuleContext;
17  import org.mule.api.MuleEvent;
18  import org.mule.api.MuleException;
19  import org.mule.api.MuleMessage;
20  import org.mule.api.config.MuleProperties;
21  import org.mule.api.construct.FlowConstruct;
22  import org.mule.api.construct.FlowConstructAware;
23  import org.mule.api.context.MuleContextAware;
24  import org.mule.api.endpoint.ImmutableEndpoint;
25  import org.mule.api.endpoint.OutboundEndpoint;
26  import org.mule.api.lifecycle.Disposable;
27  import org.mule.api.lifecycle.Initialisable;
28  import org.mule.api.lifecycle.InitialisationException;
29  import org.mule.api.lifecycle.Startable;
30  import org.mule.api.lifecycle.Stoppable;
31  import org.mule.api.processor.MessageProcessor;
32  import org.mule.api.routing.OutboundRouter;
33  import org.mule.api.routing.RouterResultsHandler;
34  import org.mule.api.routing.RoutingException;
35  import org.mule.api.transaction.TransactionCallback;
36  import org.mule.api.transaction.TransactionConfig;
37  import org.mule.api.transport.DispatchException;
38  import org.mule.config.i18n.CoreMessages;
39  import org.mule.management.stats.RouterStatistics;
40  import org.mule.processor.AbstractMessageProcessorOwner;
41  import org.mule.routing.CorrelationMode;
42  import org.mule.routing.DefaultRouterResultsHandler;
43  import org.mule.transaction.TransactionTemplate;
44  import org.mule.util.StringMessageUtils;
45  import org.mule.util.SystemUtils;
46  
47  import java.util.Arrays;
48  import java.util.Collections;
49  import java.util.List;
50  
51  import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
52  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
53  
54  import org.apache.commons.logging.Log;
55  import org.apache.commons.logging.LogFactory;
56  
57  /**
58   * <code>AbstractOutboundRouter</code> is a base router class that tracks statistics about message processing
59   * through the router.
60   */
61  public abstract class AbstractOutboundRouter extends AbstractMessageProcessorOwner implements OutboundRouter
62  {
63      /**
64       * These properties are automatically propagated by Mule from inbound to outbound
65       */
66      protected static List<String> magicProperties = Arrays.asList(
67          MuleProperties.MULE_CORRELATION_ID_PROPERTY, MuleProperties.MULE_CORRELATION_ID_PROPERTY,
68          MuleProperties.MULE_CORRELATION_GROUP_SIZE_PROPERTY,
69          MuleProperties.MULE_CORRELATION_SEQUENCE_PROPERTY, MuleProperties.MULE_SESSION_PROPERTY);
70  
71      /**
72       * logger used by this class
73       */
74      protected transient Log logger = LogFactory.getLog(getClass());
75  
76      @SuppressWarnings("unchecked")
77      protected List<MessageProcessor> routes = new CopyOnWriteArrayList();
78  
79      protected String replyTo = null;
80  
81      /**
82       * Determines if Mule stamps outgoing message with a correlation ID or not.
83       */
84      protected CorrelationMode enableCorrelation = CorrelationMode.IF_NOT_SET;
85  
86      protected TransactionConfig transactionConfig;
87  
88      protected RouterResultsHandler resultsHandler = new DefaultRouterResultsHandler();
89  
90      private RouterStatistics routerStatistics;
91  
92      protected AtomicBoolean initialised = new AtomicBoolean(false);
93      protected AtomicBoolean started = new AtomicBoolean(false);
94  
95      public MuleEvent process(final MuleEvent event) throws MuleException
96      {
97          TransactionTemplate<MuleEvent> tt = new TransactionTemplate<MuleEvent>(getTransactionConfig(),
98              muleContext);
99  
100         TransactionCallback<MuleEvent> cb = new TransactionCallback<MuleEvent>()
101         {
102             public MuleEvent doInTransaction() throws Exception
103             {
104                 return route(event);
105             }
106         };
107         try
108         {
109             return tt.execute(cb);
110         }
111         catch (RoutingException e)
112         {
113             throw e;
114         }
115         catch (Exception e)
116         {
117             throw new RoutingException(event, this, e);
118         }
119     }
120 
121     protected abstract MuleEvent route(MuleEvent event) throws MessagingException;
122 
123     protected final MuleEvent sendRequest(final MuleEvent routedEvent,
124                                           final MuleMessage message,
125                                           final MessageProcessor route,
126                                           boolean awaitResponse) throws MuleException
127     {
128         if (awaitResponse && replyTo != null)
129         {
130             logger.debug("event was dispatched synchronously, but there is a ReplyTo route set, so using asynchronous dispatch");
131             awaitResponse = false;
132         }
133 
134         setMessageProperties(routedEvent.getSession().getFlowConstruct(), message, route);
135 
136         if (logger.isDebugEnabled())
137         {
138             if (route instanceof OutboundEndpoint)
139             {
140                 logger.debug("Message being sent to: " + ((OutboundEndpoint) route).getEndpointURI());
141             }
142             logger.debug(message);
143         }
144 
145         if (logger.isTraceEnabled())
146         {
147             try
148             {
149                 logger.trace("Request payload: \n"
150                              + StringMessageUtils.truncate(message.getPayloadForLogging(), 100, false));
151                 if (route instanceof OutboundEndpoint)
152                 {
153                     logger.trace("outbound transformer is: " + ((OutboundEndpoint) route).getTransformers());
154                 }
155             }
156             catch (Exception e)
157             {
158                 logger.trace("Request payload: \n(unable to retrieve payload: " + e.getMessage());
159                 if (route instanceof OutboundEndpoint)
160                 {
161                     logger.trace("outbound transformer is: " + ((OutboundEndpoint) route).getTransformers());
162                 }
163             }
164         }
165 
166         MuleEvent result;
167         try
168         {
169             result = sendRequestEvent(routedEvent, message, route, awaitResponse);
170         }
171         catch (MessagingException me)
172         {
173             throw me;
174         }
175         catch (Exception e)
176         {
177             throw new RoutingException(routedEvent, null, e);
178         }
179 
180         if (getRouterStatistics() != null)
181         {
182             if (getRouterStatistics().isEnabled())
183             {
184                 getRouterStatistics().incrementRoutedMessage(route);
185             }
186         }
187 
188         if (result != null)
189         {
190             MuleMessage resultMessage = result.getMessage();
191             if (logger.isTraceEnabled())
192             {
193                 if (resultMessage != null)
194                 {
195                     try
196                     {
197                         logger.trace("Response payload: \n"
198                                      + StringMessageUtils.truncate(resultMessage.getPayloadForLogging(), 100,
199                                          false));
200                     }
201                     catch (Exception e)
202                     {
203                         logger.trace("Response payload: \n(unable to retrieve payload: " + e.getMessage());
204                     }
205                 }
206             }
207         }
208 
209         return result;
210     }
211 
212     protected void setMessageProperties(FlowConstruct service, MuleMessage message, MessageProcessor route)
213     {
214         if (replyTo != null)
215         {
216             // if replyTo is set we'll probably want the correlationId set as
217             // well
218             message.setReplyTo(replyTo);
219             message.setOutboundProperty(MuleProperties.MULE_REPLY_TO_REQUESTOR_PROPERTY, service.getName());
220             if (logger.isDebugEnabled() && route instanceof OutboundEndpoint)
221             {
222                 logger.debug("Setting replyTo=" + replyTo + " for outbound route: "
223                              + ((OutboundEndpoint) route).getEndpointURI());
224             }
225         }
226         if (enableCorrelation != CorrelationMode.NEVER)
227         {
228             boolean correlationSet = message.getCorrelationId() != null;
229             if (correlationSet && (enableCorrelation == CorrelationMode.IF_NOT_SET))
230             {
231                 if (logger.isDebugEnabled())
232                 {
233                     logger.debug("CorrelationId is already set to '" + message.getCorrelationId()
234                                  + "' , not setting it again");
235                 }
236                 return;
237             }
238             else if (correlationSet)
239             {
240                 if (logger.isDebugEnabled())
241                 {
242                     logger.debug("CorrelationId is already set to '" + message.getCorrelationId()
243                                  + "', but router is configured to overwrite it");
244                 }
245             }
246             else
247             {
248                 if (logger.isDebugEnabled())
249                 {
250                     logger.debug("No CorrelationId is set on the message, will set a new Id");
251                 }
252             }
253 
254             String correlation;
255             correlation = service.getMessageInfoMapping().getCorrelationId(message);
256             if (logger.isDebugEnabled())
257             {
258                 logger.debug("Extracted correlation Id as: " + correlation);
259             }
260 
261             if (logger.isDebugEnabled())
262             {
263                 StringBuffer buf = new StringBuffer();
264                 buf.append("Setting Correlation info on Outbound router");
265                 if (route instanceof OutboundEndpoint)
266                 {
267                     buf.append(" for endpoint: ").append(((OutboundEndpoint) route).getEndpointURI());
268                 }
269                 buf.append(SystemUtils.LINE_SEPARATOR).append("Id=").append(correlation);
270                 // buf.append(", ").append("Seq=").append(seq);
271                 // buf.append(", ").append("Group Size=").append(group);
272                 logger.debug(buf.toString());
273             }
274             message.setCorrelationId(correlation);
275             // message.setCorrelationGroupSize(group);
276             // message.setCorrelationSequence(seq);
277         }
278     }
279 
280     public List<MessageProcessor> getRoutes()
281     {
282         return routes;
283     }
284 
285     /*
286      * For spring access
287      */
288     // TODO Use spring factory bean
289     @Deprecated
290     public void setMessageProcessors(List<MessageProcessor> routes) throws MuleException
291     {
292         setRoutes(routes);
293     }
294 
295     public void setRoutes(List<MessageProcessor> routes) throws MuleException
296     {
297         this.routes.clear();
298         for (MessageProcessor route : routes)
299         {
300             addRoute(route);
301         }
302     }
303 
304     public synchronized void addRoute(MessageProcessor route) throws MuleException
305     {
306         if (initialised.get())
307         {
308             if (route instanceof MuleContextAware)
309             {
310                 ((MuleContextAware) route).setMuleContext(muleContext);
311             }
312             if (route instanceof FlowConstructAware)
313             {
314                 ((FlowConstructAware) route).setFlowConstruct(flowConstruct);
315             }
316             if (route instanceof Initialisable)
317             {
318                 ((Initialisable) route).initialise();
319             }
320         }
321         if (started.get())
322         {
323             if (route instanceof Startable)
324             {
325                 ((Startable) route).start();
326             }
327         }
328         routes.add(route);
329     }
330 
331     public synchronized void removeRoute(MessageProcessor route) throws MuleException
332     {
333         if (started.get())
334         {
335             if (route instanceof Stoppable)
336             {
337                 ((Stoppable) route).stop();
338             }
339         }
340         if (initialised.get())
341         {
342             if (route instanceof Disposable)
343             {
344                 ((Disposable) route).dispose();
345             }
346         }
347         routes.remove(route);
348     }
349 
350     public String getReplyTo()
351     {
352         return replyTo;
353     }
354 
355     public void setReplyTo(String replyTo)
356     {
357         this.replyTo = replyTo;
358     }
359 
360     public CorrelationMode getEnableCorrelation()
361     {
362         return enableCorrelation;
363     }
364 
365     public void setEnableCorrelation(CorrelationMode enableCorrelation)
366     {
367         this.enableCorrelation = enableCorrelation;
368     }
369 
370     public void setEnableCorrelationAsString(String enableCorrelation)
371     {
372         if (enableCorrelation != null)
373         {
374             if (enableCorrelation.equals("ALWAYS"))
375             {
376                 this.enableCorrelation = CorrelationMode.ALWAYS;
377             }
378             else if (enableCorrelation.equals("NEVER"))
379             {
380                 this.enableCorrelation = CorrelationMode.NEVER;
381             }
382             else if (enableCorrelation.equals("IF_NOT_SET"))
383             {
384                 this.enableCorrelation = CorrelationMode.IF_NOT_SET;
385             }
386             else
387             {
388                 throw new IllegalArgumentException("Value for enableCorrelation not recognised: "
389                                                    + enableCorrelation);
390             }
391         }
392     }
393 
394     public TransactionConfig getTransactionConfig()
395     {
396         return transactionConfig;
397     }
398 
399     public void setTransactionConfig(TransactionConfig transactionConfig)
400     {
401         this.transactionConfig = transactionConfig;
402     }
403 
404     public boolean isDynamicRoutes()
405     {
406         return false;
407     }
408 
409     /**
410      * @param name the route identifier
411      * @return the route or null if the endpoint's Uri is not registered
412      */
413     public MessageProcessor getRoute(String name)
414     {
415         for (MessageProcessor route : routes)
416         {
417             if (route instanceof OutboundEndpoint)
418             {
419                 OutboundEndpoint endpoint = (OutboundEndpoint) route;
420                 if (endpoint.getName().equals(name))
421                 {
422                     return endpoint;
423                 }
424             }
425         }
426         return null;
427     }
428 
429     public RouterResultsHandler getResultsHandler()
430     {
431         return resultsHandler;
432     }
433 
434     public void setResultsHandler(RouterResultsHandler resultsHandler)
435     {
436         this.resultsHandler = resultsHandler;
437     }
438 
439     /**
440      * Send message event to destination.
441      */
442     protected MuleEvent sendRequestEvent(MuleEvent routedEvent,
443                                          MuleMessage message,
444                                          MessageProcessor route,
445                                          boolean awaitResponse) throws MuleException
446     {
447         if (route == null)
448         {
449             throw new DispatchException(CoreMessages.objectIsNull("Outbound Endpoint"), routedEvent, null);
450         }
451 
452         MuleEvent event = createEventToRoute(routedEvent, message, route);
453 
454         if (awaitResponse)
455         {
456             int timeout = message.getOutboundProperty(MuleProperties.MULE_EVENT_TIMEOUT_PROPERTY, -1);
457             if (timeout >= 0)
458             {
459                 event.setTimeout(timeout);
460             }
461         }
462 
463         return route.process(event);
464     }
465 
466     /**
467      * Create a new event to be routed to the target MP
468      */
469     protected MuleEvent createEventToRoute(MuleEvent routedEvent, MuleMessage message, MessageProcessor route)
470     {
471         ImmutableEndpoint endpoint = (route instanceof ImmutableEndpoint) ? (ImmutableEndpoint) route : routedEvent.getEndpoint();
472         MuleEvent event = new DefaultMuleEvent(message, endpoint, routedEvent.getSession(), routedEvent.getProcessingTime());
473         return event;
474     }
475 
476     /**
477      * Create a fresh copy of a message.
478      */
479     protected MuleMessage cloneMessage(MuleMessage message)
480     {
481         MuleMessage clonedMessage = new DefaultMuleMessage(message.getPayload(), message, muleContext);
482         return clonedMessage;
483     }
484 
485     /**
486      * Propagates a number of internal system properties to handle correlation, session, etc. Note that in and
487      * out params can be the same message object when not dealing with replies.
488      * 
489      * @see #magicProperties
490      */
491     protected void propagateMagicProperties(MuleMessage in, MuleMessage out)
492     {
493         for (String name : magicProperties)
494         {
495             Object value = in.getInboundProperty(name);
496             if (value != null)
497             {
498                 out.setOutboundProperty(name, value);
499             }
500         }
501     }
502 
503     public void initialise() throws InitialisationException
504     {
505         synchronized (routes)
506         {
507             super.initialise();
508             initialised.set(true);
509         }
510     }
511 
512     public void dispose()
513     {
514         synchronized (routes)
515         {
516             super.dispose();
517             routes = Collections.<MessageProcessor> emptyList();
518             initialised.set(false);
519         }
520     }
521 
522     public void start() throws MuleException
523     {
524         synchronized (routes)
525         {
526             super.start();
527             started.set(true);
528         }
529     }
530 
531     public void stop() throws MuleException
532     {
533         synchronized (routes)
534         {
535             super.stop();
536             started.set(false);
537         }
538     }
539 
540     public MuleContext getMuleContext()
541     {
542         return muleContext;
543     }
544 
545     public void setRouterStatistics(RouterStatistics stats)
546     {
547         this.routerStatistics = stats;
548     }
549 
550     public RouterStatistics getRouterStatistics()
551     {
552         return routerStatistics;
553     }
554 
555     @Override
556     protected List<MessageProcessor> getOwnedMessageProcessors()
557     {
558         return routes;
559     }
560 }