View Javadoc

1   /*
2    * $Id: AbstractTransportMessageHandler.java 20358 2010-11-26 20:15:18Z tcarlson $
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.transport;
12  
13  import org.mule.api.MuleException;
14  import org.mule.api.MuleMessage;
15  import org.mule.api.MuleRuntimeException;
16  import org.mule.api.config.MuleConfiguration;
17  import org.mule.api.context.WorkManager;
18  import org.mule.api.endpoint.ImmutableEndpoint;
19  import org.mule.api.lifecycle.CreateException;
20  import org.mule.api.lifecycle.InitialisationException;
21  import org.mule.api.lifecycle.LifecycleCallback;
22  import org.mule.api.lifecycle.LifecycleException;
23  import org.mule.api.lifecycle.LifecycleState;
24  import org.mule.api.lifecycle.LifecycleStateEnabled;
25  import org.mule.api.lifecycle.StartException;
26  import org.mule.api.retry.RetryCallback;
27  import org.mule.api.retry.RetryContext;
28  import org.mule.api.retry.RetryPolicyTemplate;
29  import org.mule.api.transport.Connectable;
30  import org.mule.api.transport.Connector;
31  import org.mule.api.transport.MuleMessageFactory;
32  import org.mule.config.i18n.CoreMessages;
33  import org.mule.config.i18n.Message;
34  import org.mule.config.i18n.MessageFactory;
35  import org.mule.context.notification.ConnectionNotification;
36  import org.mule.util.ClassUtils;
37  import org.mule.util.concurrent.WaitableBoolean;
38  
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  /**
43   * Provide a default dispatch (client) support for handling threads lifecycle and validation.
44   */
45  public abstract class AbstractTransportMessageHandler<O> implements Connectable, LifecycleStateEnabled
46  {
47      protected transient Log logger = LogFactory.getLog(getClass());
48  
49      protected ImmutableEndpoint endpoint;
50      protected final AbstractConnector connector;
51      protected RetryPolicyTemplate retryTemplate;
52      protected MuleMessageFactory muleMessageFactory = null;
53  
54      protected final WaitableBoolean connected = new WaitableBoolean(false);
55      protected final WaitableBoolean connecting = new WaitableBoolean(false);
56  
57      /**
58       * Indicates whether the receiver/dispatcher/requester should start upon connecting.
59       * This is necessary to support asynchronous retry policies, otherwise the start()
60       * method would block until connection is successful.
61       */
62      protected volatile boolean startOnConnect = false;
63  
64      protected ConnectableLifecycleManager<O> lifecycleManager;
65  
66      public AbstractTransportMessageHandler(ImmutableEndpoint endpoint)
67      {
68          this.endpoint = endpoint;
69          this.connector = (AbstractConnector) endpoint.getConnector();
70          this.lifecycleManager = createLifecycleManager();
71      }
72  
73      protected abstract ConnectableLifecycleManager<O> createLifecycleManager();
74  
75      public LifecycleState getLifecycleState()
76      {
77          return lifecycleManager.getState();
78      }
79  
80      protected void disposeAndLogException()
81      {
82          try
83          {
84              dispose();
85          }
86          catch (Throwable t)
87          {
88              logger.error("Could not dispose of the message dispatcher!", t);
89          }
90      }
91  
92      public boolean validate()
93      {
94          // by default a dispatcher/requester can be used unless disposed
95          return !getLifecycleState().isDisposed();
96      }
97  
98      public void activate()
99      {
100         // nothing to do by default
101     }
102 
103     public void passivate()
104     {
105         // nothing to do by default
106     }
107 
108     public void initialise() throws InitialisationException
109     {
110         try
111         {
112             lifecycleManager.fireInitialisePhase(new LifecycleCallback<O>()
113             {
114                 public void onTransition(String phaseName, O object) throws MuleException
115                 {
116                     initializeRetryPolicy();
117                     initializeMessageFactory();
118                     doInitialise();
119                 }
120             });
121         }
122         catch (InitialisationException e)
123         {
124             throw e;
125         }
126         catch (MuleException e)
127         {
128             throw new InitialisationException(e, this);
129         }
130 
131     }
132 
133     protected void initializeRetryPolicy()
134     {
135         if (endpoint.getRetryPolicyTemplate() != null)
136         {
137             retryTemplate = endpoint.getRetryPolicyTemplate();
138         }
139         else
140         {
141             retryTemplate = connector.getRetryPolicyTemplate();
142         }
143     }
144 
145     /**
146      * Subclasses can override this method to create a custom {@link MuleMessageFactory} instead
147      * of re-using the instance from the connector.
148      */
149     protected void initializeMessageFactory() throws InitialisationException
150     {
151         try
152         {
153             muleMessageFactory = connector.getMuleMessageFactory();
154         }
155         catch (CreateException ce)
156         {
157             Message message = MessageFactory.createStaticMessage(ce.getMessage());
158             throw new InitialisationException(message, ce, this);
159         }
160     }
161 
162     /**
163      * Template method to destroy any resources held by the Message Dispatcher
164      */
165     public synchronized void dispose()
166     {
167         try
168         {
169             try
170             {
171                 disconnect();
172             }
173             catch (Exception e)
174             {
175                 logger.warn(e.getMessage(), e);
176             }
177             if (isStarted())
178             {
179                 stop();
180             }
181             
182             //Nothing to do when disposing, just transition
183             lifecycleManager.fireDisposePhase(new LifecycleCallback<O>() {
184                 public void onTransition(String phaseName, O object) throws MuleException
185                 {
186                     doDispose();
187                 }
188             });
189         }
190         catch (Exception e)
191         {
192             logger.warn(e.getMessage(), e);
193         }
194     }
195 
196     public Connector getConnector()
197     {
198         return connector;
199     }
200 
201     public ImmutableEndpoint getEndpoint()
202     {
203         return endpoint;
204     }
205 
206     public final synchronized void connect() throws Exception
207     {
208         // This method may be called to ensure transport is connected, if it is
209         // already connected then just return.
210         if (connected.get() || connecting.get())
211         {
212             return;
213         }
214 
215         if (getLifecycleState().isDisposed())
216         {
217             throw new IllegalStateException(
218                     "Requester/dispatcher has been disposed; cannot connect to resource:" + this);
219         }
220 
221         if (!connecting.compareAndSet(false, true))
222         {
223             return;
224         }
225 
226         if (logger.isDebugEnabled())
227         {
228             logger.debug("Connecting: " + this);
229         }
230 
231         retryTemplate.execute(
232                 new RetryCallback()
233                 {
234                     public void doWork(RetryContext context) throws Exception
235                     {
236                         try
237                         {
238                             doConnect();
239                             connected.set(true);
240                             connecting.set(false);
241 
242                             if (logger.isDebugEnabled())
243                             {
244                                 logger.debug("Connected: " + getWorkDescription());
245                             }
246                             // TODO Make this work somehow inside the RetryTemplate
247                             //connector.fireNotification(new ConnectionNotification(this, getConnectEventId(endpoint),
248                             //    ConnectionNotification.CONNECTION_CONNECTED));
249 
250                             if (startOnConnect)
251                             {
252                                 start();
253                             }
254                         }
255                         catch (Exception e)
256                         {
257                             if (logger.isDebugEnabled())
258                             {
259                                 logger.debug("exception in doWork", e);
260                             }
261                             throw e;
262                         }
263                     }
264 
265                     public String getWorkDescription()
266                     {
267                         return getConnectionDescription();
268                     }
269                 },
270                 getWorkManager()
271         );
272     }
273 
274     public RetryContext validateConnection(RetryContext retryContext)
275     {
276         retryContext.setOk();
277         return retryContext;
278     }
279 
280     public final synchronized void disconnect() throws Exception
281     {
282         if (!connected.get())
283         {
284             return;
285         }
286 
287         if (logger.isDebugEnabled())
288         {
289             logger.debug("Disconnecting: " + this);
290         }
291 
292         this.doDisconnect();
293         connected.set(false);
294 
295         if (logger.isDebugEnabled())
296         {
297             logger.debug("Disconnected: " + this);
298         }
299         connector.fireNotification(new ConnectionNotification(this, getConnectEventId(endpoint),
300                 ConnectionNotification.CONNECTION_DISCONNECTED));
301     }
302 
303     protected String getConnectEventId(ImmutableEndpoint endpoint)
304     {
305         return connector.getName() + ".dispatcher(" + endpoint.getEndpointURI().getUri() + ")";
306     }
307 
308     public final boolean isConnected()
309     {
310         return connected.get();
311     }
312 
313     public final boolean isConnecting()
314     {
315         return connecting.get();
316     }
317 
318     protected boolean isDoThreading()
319     {
320         return connector.getDispatcherThreadingProfile().isDoThreading();
321     }
322 
323     /**
324      * Returns a string identifying the underlying resource
325      */
326     public String getConnectionDescription()
327     {
328         return "endpoint.outbound." + endpoint.getEndpointURI().toString();
329     }
330 
331     /**
332      * This method will start the connectable, calling {@link #connect()} if it is
333      * needed.
334      * <p/>
335      * This method is synchronous or not depending on how the {@link #retryTemplate}
336      * behaves.
337      * <p/>
338      * This method ensures that {@link #doStart()} will be called at most one time
339      * and will return without error if the component is already {@link #started}.
340      */
341     public final void start() throws MuleException
342     {
343         //We only fire start lifecycle once we are connected, see {@link #callDoStartWhenItIsConnected}
344         if (!connected.get() && !connecting.get())
345         {
346             connectAndThenStart();
347         }
348         else if (connecting.get() && isDoStartMustFollowDoConnect())
349         {
350             try
351             {
352                 callDoStartWhenItIsConnected();
353             }
354             catch (InterruptedException e)
355             {
356                 throw new StartException(CoreMessages.failedToStart("Connectable: " + this), e, this);
357             }
358         }
359         else
360         {
361             try
362             {
363                 lifecycleManager.fireStartPhase(new LifecycleCallback<O>()
364                 {
365                     public void onTransition(String phaseName, O object) throws MuleException
366                     {
367                         doStart();
368                     }
369                 });
370             }
371             catch (MuleException e)
372             {
373                 throw e;
374             }
375             catch (Exception e)
376             {
377                 throw new StartException(CoreMessages.failedToStart("Connectable: " + this), e, this);
378             }
379         }
380     }
381 
382     /**
383      * This method will call {@link #connect()} after setting {@link #startOnConnect}
384      * in true. This will make the {@link #connect()} method call {@link #start()}
385      * after finishing establishing connection.
386      *
387      * @throws LifecycleException
388      */
389     protected void connectAndThenStart() throws LifecycleException
390     {
391         startOnConnect = true;
392 
393         // Make sure we are connected
394         try
395         {
396             connect();
397         }
398         catch (Exception e)
399         {
400             throw new LifecycleException(e, this);
401         }
402     }
403 
404     /**
405      * This method will block until {@link #connected} is true and then will call
406      * {@link #doStart()}.
407      *
408      * @throws InterruptedException if the thread is interrupted while waiting for
409      *                              {@link #connected} to be true.
410      * @throws MuleException        this is just a propagation of any {@link MuleException}
411      *                              that {@link #doStart()} may throw.
412      */
413     protected void callDoStartWhenItIsConnected() throws InterruptedException, MuleException
414     {
415         try
416         {
417             connected.whenTrue(new Runnable()
418             {
419                 public void run()
420                 {
421                     try
422                     {
423                         lifecycleManager.fireStartPhase(new LifecycleCallback<O>()
424                         {
425                             public void onTransition(String phaseName, O object) throws MuleException
426                             {
427                                 doStart();
428                             }
429                         });
430                     }
431                     catch (MuleException e)
432                     {
433                         throw new MuleRuntimeException(
434                                 CoreMessages.createStaticMessage("wrapper exception for a MuleException"), e);
435                     }
436                 }
437             });
438         }
439         catch (MuleRuntimeException e)
440         {
441             if (e.getCause() instanceof MuleException)
442             {
443                 throw (MuleException) e.getCause();
444             }
445             else
446             {
447                 throw e;
448             }
449         }
450     }
451 
452     public final void stop() throws MuleException
453     {
454         try
455         {
456             if (connected.get())
457             {
458                 disconnect();
459             }
460         }
461         catch (Exception e)
462         {
463             logger.error(e.getMessage(), e);
464         }
465 
466         lifecycleManager.fireStopPhase(new LifecycleCallback<O>()
467         {
468             public void onTransition(String phaseName, O object) throws MuleException
469             {
470                 try
471                 {
472                     doStop();
473                 }
474                 catch (MuleException e)
475                 {
476                     logger.error(e.getMessage(), e);
477                 }
478             }
479         });
480 
481     }
482 
483     protected void doInitialise() throws InitialisationException
484     {
485         // nothing to do by default
486     }
487 
488     protected void doDispose()
489     {
490         // nothing to do by default
491     }
492 
493     protected void doConnect() throws Exception
494     {
495         // nothing to do by default
496     }
497 
498     protected void doDisconnect() throws Exception
499     {
500         // nothing to do by default
501     }
502 
503     protected void doStart() throws MuleException
504     {
505         // nothing to do by default
506     }
507 
508     protected void doStop() throws MuleException
509     {
510         // nothing to do by default
511     }
512 
513     @Override
514     public String toString()
515     {
516         final StringBuffer sb = new StringBuffer(80);
517         sb.append(ClassUtils.getSimpleName(this.getClass()));
518         sb.append("{this=").append(Integer.toHexString(System.identityHashCode(this)));
519         sb.append(", endpoint=").append(endpoint.getEndpointURI());
520         sb.append(", disposed=").append(getLifecycleState().isDisposed());
521         sb.append('}');
522         return sb.toString();
523     }
524 
525     // TODO MULE-4871 Endpoint should not be mutable
526 
527     public void setEndpoint(ImmutableEndpoint endpoint)
528     {
529         if (endpoint == null)
530         {
531             throw new IllegalArgumentException("Endpoint cannot be null");
532         }
533         this.endpoint = endpoint;
534     }
535 
536     abstract protected WorkManager getWorkManager() throws MuleException;
537 
538     public boolean isStarted()
539     {
540         return getLifecycleState().isStarted();
541     }
542 
543     /**
544      * This method uses the connector's <code>createMuleMessageFactory</code> method to create
545      * a new {@link MuleMessageFactory}. Subclasses may need to override this method in order to
546      * perform additional initialization on the message factory before it's actually used.
547      */
548     protected MuleMessageFactory createMuleMessageFactory() throws CreateException
549     {
550         return connector.createMuleMessageFactory();
551     }
552 
553     /**
554      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
555      * The payload of the new message will be taken from <code>transportMessage</code>, all
556      * message properties will be copied from <code>previousMessage</code>.
557      */
558     public MuleMessage createMuleMessage(Object transportMessage, MuleMessage previousMessage,
559                                          String encoding) throws MuleException
560     {
561         try
562         {
563             return muleMessageFactory.create(transportMessage, previousMessage, encoding);
564         }
565         catch (Exception e)
566         {
567             throw new CreateException(CoreMessages.failedToCreate("MuleMessage"), e);
568         }
569     }
570 
571     /**
572      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
573      * This is the designated way to build {@link MuleMessage}s from the transport specific message.
574      */
575     public MuleMessage createMuleMessage(Object transportMessage, String encoding) throws MuleException
576     {
577         try
578         {
579             return muleMessageFactory.create(transportMessage, encoding);
580         }
581         catch (Exception e)
582         {
583             throw new CreateException(CoreMessages.failedToCreate("MuleMessage"), e, this);
584         }
585     }
586 
587     /**
588      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
589      * Uses the default encoding.
590      *
591      * @see MuleConfiguration#getDefaultEncoding()
592      */
593     public MuleMessage createMuleMessage(Object transportMessage) throws MuleException
594     {
595         String encoding = endpoint.getMuleContext().getConfiguration().getDefaultEncoding();
596         return createMuleMessage(transportMessage, encoding);
597     }
598 
599     /**
600      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
601      * Rather than passing in a transport message instance, {@link NullPayload} is used instead.
602      * Uses the default encoding.
603      */
604     protected MuleMessage createNullMuleMessage() throws MuleException
605     {
606         return createMuleMessage(null);
607     }
608 
609     /**
610      * @return true if doStart() must come strictly after the completion of doConnect()
611      */
612     protected boolean isDoStartMustFollowDoConnect()
613     {
614         return false;
615     }
616 }