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