View Javadoc

1   /*
2    * $Id: AbstractMessageDispatcher.java 7963 2007-08-21 08:53:15Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.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.providers;
12  
13  import org.mule.MuleRuntimeException;
14  import org.mule.config.MuleProperties;
15  import org.mule.config.i18n.CoreMessages;
16  import org.mule.impl.OptimizedRequestContext;
17  import org.mule.impl.RequestContext;
18  import org.mule.impl.internal.notifications.ConnectionNotification;
19  import org.mule.impl.internal.notifications.MessageNotification;
20  import org.mule.impl.internal.notifications.SecurityNotification;
21  import org.mule.transaction.TransactionCoordination;
22  import org.mule.umo.TransactionException;
23  import org.mule.umo.UMOEvent;
24  import org.mule.umo.UMOException;
25  import org.mule.umo.UMOMessage;
26  import org.mule.umo.UMOTransaction;
27  import org.mule.umo.endpoint.UMOImmutableEndpoint;
28  import org.mule.umo.manager.UMOWorkManager;
29  import org.mule.umo.provider.DispatchException;
30  import org.mule.umo.provider.ReceiveException;
31  import org.mule.umo.provider.UMOConnector;
32  import org.mule.umo.provider.UMOMessageDispatcher;
33  import org.mule.util.ClassUtils;
34  
35  import java.beans.ExceptionListener;
36  
37  import javax.resource.spi.work.Work;
38  import javax.resource.spi.work.WorkManager;
39  
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  
43  /**
44   * <p/> <code>AbstractMessageDispatcher</code> provides a default dispatch (client)
45   * support for handling threads lifecycle and validation.
46   */
47  public abstract class AbstractMessageDispatcher implements UMOMessageDispatcher, ExceptionListener
48  {
49      /**
50       * logger used by this class
51       */
52      protected transient Log logger = LogFactory.getLog(getClass());
53  
54      /**
55       * Thread pool of Connector sessions
56       */
57      protected UMOWorkManager workManager = null;
58  
59      protected final UMOImmutableEndpoint endpoint;
60      protected final AbstractConnector connector;
61  
62      protected boolean disposed = false;
63  
64      protected ConnectionStrategy connectionStrategy;
65  
66      protected volatile boolean connecting = false;
67      protected volatile boolean connected = false;
68  
69      public AbstractMessageDispatcher(UMOImmutableEndpoint endpoint)
70      {
71          this.endpoint = endpoint;
72          this.connector = (AbstractConnector) endpoint.getConnector();
73  
74          connectionStrategy = connector.getConnectionStrategy();
75          if (connectionStrategy instanceof AbstractConnectionStrategy)
76          {
77              // We don't want to do threading in the dispatcher because we're either
78              // already running in a worker thread (asynchronous) or we need to
79              // complete the operation in a single thread
80              final AbstractConnectionStrategy connStrategy = (AbstractConnectionStrategy) connectionStrategy;
81              if (connStrategy.isDoThreading())
82              {
83                  if (logger.isDebugEnabled())
84                  {
85                      logger.debug("Overriding doThreading to false on " + connStrategy);
86                  }
87                  connStrategy.setDoThreading(false);
88              }
89          }
90  
91          if (isDoThreading())
92          {
93              try
94              {
95                  workManager = connector.getDispatcherWorkManager();
96              }
97              catch (UMOException e)
98              {
99                  dispose();
100                 throw new MuleRuntimeException(CoreMessages.failedToStart("WorkManager"), e);
101             }
102         }
103     }
104 
105     /*
106      * (non-Javadoc)
107      * 
108      * @see org.mule.umo.provider.UMOMessageDispatcher#dispatch(org.mule.umo.UMOEvent)
109      */
110     public final void dispatch(UMOEvent event) throws DispatchException
111     {
112         event.setSynchronous(false);
113         event.getMessage().setProperty(MuleProperties.MULE_ENDPOINT_PROPERTY,
114             event.getEndpoint().getEndpointURI().toString());
115         event = OptimizedRequestContext.criticalSetEvent(event); // MULE-2112
116 
117         // Apply Security filter if one is set
118         UMOImmutableEndpoint endpoint = event.getEndpoint();
119         if (endpoint.getSecurityFilter() != null)
120         {
121             try
122             {
123                 endpoint.getSecurityFilter().authenticate(event);
124             }
125             catch (org.mule.umo.security.SecurityException e)
126             {
127                 // TODO MULE-863: Do we need this warning?
128                 logger.warn("Outbound Request was made but was not authenticated: " + e.getMessage(), e);
129                 connector.fireNotification(new SecurityNotification(e,
130                     SecurityNotification.ADMIN_EVENT_ACTION_START_RANGE));
131                 connector.handleException(e);
132                 return;
133             }
134             catch (UMOException e)
135             {
136                 dispose();
137                 throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
138             }
139         }
140         // the security filter may update the payload so we need to get the
141         // latest event again
142         event = RequestContext.getEvent();
143 
144         try
145         {
146             UMOTransaction tx = TransactionCoordination.getInstance().getTransaction();
147             if (isDoThreading() && !event.isSynchronous() && tx == null)
148             {
149                 workManager.scheduleWork(new Worker(event), WorkManager.INDEFINITE, null, connector);
150             }
151             else
152             {
153                 // Make sure we are connected
154                 connectionStrategy.connect(this);
155                 doDispatch(event);
156                 if (connector.isEnableMessageEvents())
157                 {
158                     String component = null;
159                     if (event.getComponent() != null)
160                     {
161                         component = event.getComponent().getDescriptor().getName();
162                     }
163                     connector.fireNotification(new MessageNotification(event.getMessage(), event
164                         .getEndpoint(), component, MessageNotification.MESSAGE_DISPATCHED));
165                 }
166             }
167         }
168         catch (DispatchException e)
169         {
170             dispose();
171             throw e;
172         }
173         catch (Exception e)
174         {
175             dispose();
176             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
177         }
178     }
179 
180     public final UMOMessage send(UMOEvent event) throws DispatchException
181     {
182         // No point continuing if the component has rolledback the transaction
183         if (isTransactionRollback())
184         {
185             return event.getMessage();
186         }
187 
188         event.setSynchronous(true);
189         event.getMessage().setProperty(MuleProperties.MULE_ENDPOINT_PROPERTY,
190             event.getEndpoint().getEndpointURI().toString());
191         event = OptimizedRequestContext.unsafeSetEvent(event);
192 
193         // Apply Security filter if one is set
194         UMOImmutableEndpoint endpoint = event.getEndpoint();
195         if (endpoint.getSecurityFilter() != null)
196         {
197             try
198             {
199                 endpoint.getSecurityFilter().authenticate(event);
200             }
201             catch (org.mule.umo.security.SecurityException e)
202             {
203                 logger.warn("Outbound Request was made but was not authenticated: " + e.getMessage(), e);
204                 connector.fireNotification(new SecurityNotification(e,
205                     SecurityNotification.SECURITY_AUTHENTICATION_FAILED));
206                 connector.handleException(e);
207                 return event.getMessage();
208             }
209             catch (UMOException e)
210             {
211                 dispose();
212                 throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
213             }
214         }
215         // the security filter may update the payload so we need to get the
216         // latest event again
217         event = RequestContext.getEvent();
218 
219         try
220         {
221             // Make sure we are connected
222             connectionStrategy.connect(this);
223 
224             UMOMessage result = doSend(event);
225             if (connector.isEnableMessageEvents())
226             {
227                 String component = null;
228                 if (event.getComponent() != null)
229                 {
230                     component = event.getComponent().getDescriptor().getName();
231                 }
232                 connector.fireNotification(new MessageNotification(event.getMessage(), event.getEndpoint(),
233                     component, MessageNotification.MESSAGE_SENT));
234             }
235 
236             // Once a dispatcher has done its work we need to remove this property
237             // so that it is not propagated to the next request
238             if (result != null)
239             {
240                 result.removeProperty(MuleProperties.MULE_REMOTE_SYNC_PROPERTY);
241             }
242             return result;
243         }
244         catch (DispatchException e)
245         {
246             dispose();
247             throw e;
248         }
249         catch (Exception e)
250         {
251             dispose();
252             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
253         }
254     }
255 
256     /**
257      * Make a specific request to the underlying transport
258      * 
259      * @param timeout the maximum time the operation should block before returning.
260      *            The call should return immediately if there is data available. If
261      *            no data becomes available before the timeout elapses, null will be
262      *            returned
263      * @return the result of the request wrapped in a UMOMessage object. Null will be
264      *         returned if no data was avaialable
265      * @throws Exception if the call to the underlying protocal cuases an exception
266      */
267     public final UMOMessage receive(long timeout) throws Exception
268     {
269         try
270         {
271             // Make sure we are connected
272             connectionStrategy.connect(this);
273             UMOMessage result = doReceive(timeout);
274             if (result != null && connector.isEnableMessageEvents())
275             {
276                 connector.fireNotification(new MessageNotification(result, endpoint, null,
277                     MessageNotification.MESSAGE_RECEIVED));
278             }
279             return result;
280         }
281         catch (DispatchException e)
282         {
283             dispose();
284             throw e;
285         }
286         catch (Exception e)
287         {
288             dispose();
289             throw new ReceiveException(endpoint, timeout, e);
290         }
291     }
292 
293     /*
294      * (non-Javadoc)
295      * 
296      * @see org.mule.util.ExceptionListener#onException(java.lang.Throwable)
297      */
298     public void exceptionThrown(Exception e)
299     {
300         try
301         {
302             getConnector().handleException(e);
303         }
304         finally
305         {
306             dispose();
307         }
308     }
309 
310     public boolean validate()
311     {
312         // by default a dispatcher can be used unless disposed
313         return !disposed;
314     }
315 
316     public void activate()
317     {
318         // nothing to do by default
319     }
320 
321     public void passivate()
322     {
323         // nothing to do by default
324     }
325 
326     /**
327      * Template method to destroy any resources held by the Message Dispatcher
328      */
329     public final synchronized void dispose()
330     {
331         if (!disposed)
332         {
333             try
334             {
335                 try
336                 {
337                     this.disconnect();
338                 }
339                 catch (Exception e)
340                 {
341                     // TODO MULE-863: What should we really do?
342                     logger.warn(e.getMessage(), e);
343                 }
344 
345                 this.doDispose();
346 
347                 if (workManager != null)
348                 {
349                     workManager.dispose();
350                 }
351             }
352             finally
353             {
354                 disposed = true;
355             }
356         }
357     }
358 
359     public UMOConnector getConnector()
360     {
361         return connector;
362     }
363 
364     public UMOImmutableEndpoint getEndpoint()
365     {
366         return endpoint;
367     }
368 
369     /**
370      * RemoteSync causes the message dispatch to wait for a response to an event on a
371      * response channel after it sends the event. The following rules apply to
372      * RemoteSync 1. The connector has to support remoteSync. Some transports do not
373      * have the notion of a response channel 2. Check if the endpoint has been
374      * configured for remoteSync 3. Check if the REMOTE_SYNC message header has been
375      * set 4. Finally, if the current component has a response router configured,
376      * that the router will handle the response channel event and we should not try
377      * and receive a response in the Message dispatcher If remotesync should not be
378      * used we must remove the REMOTE_SYNC header Note the MuleClient will
379      * automatically set the REMOTE_SYNC header when client.send(..) is called so
380      * that results are returned from remote invocations too.
381      * 
382      * @param event the current event
383      * @return true if a response channel should be used to get a resposne from the
384      *         event dispatch.
385      */
386     protected boolean useRemoteSync(UMOEvent event)
387     {
388         boolean remoteSync = false;
389         if (event.getEndpoint().getConnector().isRemoteSyncEnabled())
390         {
391             remoteSync = event.getEndpoint().isRemoteSync()
392                             || event.getMessage().getBooleanProperty(
393                                 MuleProperties.MULE_REMOTE_SYNC_PROPERTY, false);
394             if (remoteSync)
395             {
396                 // component will be null for client calls
397                 if (event.getComponent() != null)
398                 {
399                     remoteSync = event.getComponent().getDescriptor().getResponseRouter() == null;
400                 }
401             }
402         }
403         if (!remoteSync)
404         {
405             event.getMessage().removeProperty(MuleProperties.MULE_REMOTE_SYNC_PROPERTY);
406         }
407         return remoteSync;
408     }
409 
410     public synchronized void connect() throws Exception
411     {
412         if (disposed)
413         {
414             throw new IllegalStateException("Dispatcher has been disposed; cannot connect to resource");
415         }
416 
417         if (connected)
418         {
419             return;
420         }
421 
422         if (!connecting)
423         {
424             connecting = true;
425 
426             if (logger.isDebugEnabled())
427             {
428                 logger.debug("Connecting: " + this);
429             }
430 
431             connectionStrategy.connect(this);
432 
433             logger.info("Connected: " + this);
434             return;
435         }
436 
437         try
438         {
439             this.doConnect();
440             connected = true;
441             connecting = false;
442 
443             connector.fireNotification(new ConnectionNotification(this, getConnectEventId(endpoint),
444                 ConnectionNotification.CONNECTION_CONNECTED));
445         }
446         catch (Exception e)
447         {
448             connected = false;
449             connecting = false;
450 
451             connector.fireNotification(new ConnectionNotification(this, getConnectEventId(endpoint),
452                 ConnectionNotification.CONNECTION_FAILED));
453 
454             if (e instanceof ConnectException)
455             {
456                 throw (ConnectException) e;
457             }
458             else
459             {
460                 throw new ConnectException(e, this);
461             }
462         }
463     }
464 
465     public synchronized void disconnect() throws Exception
466     {
467         if (!connected)
468         {
469             return;
470         }
471 
472         if (logger.isDebugEnabled())
473         {
474             logger.debug("Disconnecting: " + this);
475         }
476 
477         this.doDisconnect();
478         connected = false;
479 
480         logger.info("Disconnected: " + this);
481 
482         connector.fireNotification(new ConnectionNotification(this, getConnectEventId(endpoint),
483             ConnectionNotification.CONNECTION_DISCONNECTED));
484     }
485 
486     protected String getConnectEventId(UMOImmutableEndpoint endpoint)
487     {
488         return connector.getName() + ".dispatcher(" + endpoint.getEndpointURI() + ")";
489     }
490 
491     public final boolean isConnected()
492     {
493         return connected;
494     }
495 
496     protected boolean isDoThreading ()
497     {
498         return connector.getDispatcherThreadingProfile().isDoThreading();
499     }
500 
501     /**
502      * Returns a string identifying the underlying resource
503      * 
504      * @return
505      */
506     public String getConnectionDescription()
507     {
508         return endpoint.getEndpointURI().toString();
509     }
510 
511     public synchronized void reconnect() throws Exception
512     {
513         disconnect();
514         connect();
515     }
516 
517     protected abstract void doDispose();
518 
519     protected abstract void doDispatch(UMOEvent event) throws Exception;
520 
521     protected abstract UMOMessage doSend(UMOEvent event) throws Exception;
522 
523     protected abstract void doConnect() throws Exception;
524 
525     protected abstract void doDisconnect() throws Exception;
526 
527     /**
528      * Make a specific request to the underlying transport
529      * 
530      * @param timeout the maximum time the operation should block before returning.
531      *            The call should return immediately if there is data available. If
532      *            no data becomes available before the timeout elapses, null will be
533      *            returned
534      * @return the result of the request wrapped in a UMOMessage object. Null will be
535      *         returned if no data was avaialable
536      * @throws Exception if the call to the underlying protocal cuases an exception
537      */
538     protected abstract UMOMessage doReceive(long timeout) throws Exception;
539 
540     private class Worker implements Work
541     {
542         private UMOEvent event;
543 
544         public Worker(UMOEvent event)
545         {
546             this.event = event;
547         }
548 
549         /*
550          * (non-Javadoc)
551          * 
552          * @see java.lang.Runnable#run()
553          */
554         public void run()
555         {
556             try
557             {
558                 event = OptimizedRequestContext.criticalSetEvent(event);
559                 // Make sure we are connected
560                 connectionStrategy.connect(AbstractMessageDispatcher.this);
561                 AbstractMessageDispatcher.this.doDispatch(event);
562 
563                 if (connector.isEnableMessageEvents())
564                 {
565                     String component = null;
566                     if (event.getComponent() != null)
567                     {
568                         component = event.getComponent().getDescriptor().getName();
569                     }
570 
571                     connector.fireNotification(new MessageNotification(event.getMessage(), event
572                         .getEndpoint(), component, MessageNotification.MESSAGE_DISPATCHED));
573                 }
574             }
575             catch (Exception e)
576             {
577                 AbstractMessageDispatcher.this.getConnector().handleException(e);
578             }
579         }
580 
581         public void release()
582         {
583             // nothing to do
584         }
585     }
586 
587     /**
588      * Checks to see if the current transaction has been rolled back
589      * 
590      * @return
591      */
592     protected boolean isTransactionRollback()
593     {
594         try
595         {
596             UMOTransaction tx = TransactionCoordination.getInstance().getTransaction();
597             if (tx != null && tx.isRollbackOnly())
598             {
599                 return true;
600             }
601         }
602         catch (TransactionException e)
603         {
604             // TODO MULE-863: What should we really do?
605             logger.warn(e.getMessage());
606         }
607         return false;
608     }
609 
610     //  @Override
611     public String toString()
612     {
613         final StringBuffer sb = new StringBuffer(80);
614         sb.append(ClassUtils.getSimpleName(this.getClass()));
615         sb.append("{this=").append(Integer.toHexString(System.identityHashCode(this)));
616         sb.append(", endpoint=").append(endpoint.getEndpointURI());
617         sb.append('}');
618         return sb.toString();
619     }
620 }