View Javadoc

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