View Javadoc

1   /*
2    * $Id: AbstractConnectable.java 19500 2010-09-09 17:29:17Z dzapata $
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 AbstractConnectable<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 AbstractConnectable(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, AbstractConnectable.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 //                retryTemplate.execute(new RetryCallback()
371 //                {
372 //                    public void doWork(RetryContext context) throws InterruptedException, MuleException
373 //                    {
374 //                        callDoStartWhenItIsConnected();
375 //                    }
376 //
377 //                    public String getWorkDescription()
378 //                    {
379 //                        return "starting " + getConnectionDescription();
380 //                    }
381 //                }, getWorkManager());
382             }
383             catch (MuleException e)
384             {
385                 throw e;
386             }
387             catch (Exception e)
388             {
389                 throw new StartException(CoreMessages.failedToStart("Connectable: " + this), e, AbstractConnectable.this);
390             }
391         }
392     }
393 
394     /**
395      * This method will call {@link #connect()} after setting {@link #startOnConnect}
396      * in true. This will make the {@link #connect()} method call {@link #start()}
397      * after finishing establishing connection.
398      *
399      * @throws LifecycleException
400      */
401     protected void connectAndThenStart() throws LifecycleException
402     {
403         startOnConnect = true;
404 
405         // Make sure we are connected
406         try
407         {
408             connect();
409         }
410         catch (Exception e)
411         {
412             throw new LifecycleException(e, this);
413         }
414     }
415 
416     /**
417      * This method will block until {@link #connected} is true and then will call
418      * {@link #doStart()}.
419      *
420      * @throws InterruptedException if the thread is interrupted while waiting for
421      *                              {@link #connected} to be true.
422      * @throws MuleException        this is just a propagation of any {@link MuleException}
423      *                              that {@link #doStart()} may throw.
424      */
425     protected void callDoStartWhenItIsConnected() throws InterruptedException, MuleException
426     {
427         try
428         {
429             connected.whenTrue(new Runnable()
430             {
431                 public void run()
432                 {
433                     try
434                     {
435                         lifecycleManager.fireStartPhase(new LifecycleCallback<O>()
436                         {
437                             public void onTransition(String phaseName, O object) throws MuleException
438                             {
439                                 doStart();
440                             }
441                         });
442                     }
443                     catch (MuleException e)
444                     {
445                         throw new MuleRuntimeException(
446                                 CoreMessages.createStaticMessage("wrapper exception for a MuleException"), e);
447                     }
448                 }
449             });
450         }
451         catch (MuleRuntimeException e)
452         {
453             if (e.getCause() instanceof MuleException)
454             {
455                 throw (MuleException) e.getCause();
456             }
457             else
458             {
459                 throw e;
460             }
461         }
462     }
463 
464     public final void stop() throws MuleException
465     {
466         try
467         {
468             if (connected.get())
469             {
470                 disconnect();
471             }
472         }
473         catch (Exception e)
474         {
475             logger.error(e.getMessage(), e);
476         }
477 
478         lifecycleManager.fireStopPhase(new LifecycleCallback<O>()
479         {
480             public void onTransition(String phaseName, O object) throws MuleException
481             {
482                 try
483                 {
484                     doStop();
485                 }
486                 catch (MuleException e)
487                 {
488                     logger.error(e.getMessage(), e);
489                 }
490             }
491         });
492 
493     }
494 
495     protected void doInitialise() throws InitialisationException
496     {
497         // nothing to do by default
498     }
499 
500     protected void doDispose()
501     {
502         // nothing to do by default
503     }
504 
505     protected void doConnect() throws Exception
506     {
507         // nothing to do by default
508     }
509 
510     protected void doDisconnect() throws Exception
511     {
512         // nothing to do by default
513     }
514 
515     protected void doStart() throws MuleException
516     {
517         // nothing to do by default
518     }
519 
520     protected void doStop() throws MuleException
521     {
522         // nothing to do by default
523     }
524 
525     @Override
526     public String toString()
527     {
528         final StringBuffer sb = new StringBuffer(80);
529         sb.append(ClassUtils.getSimpleName(this.getClass()));
530         sb.append("{this=").append(Integer.toHexString(System.identityHashCode(this)));
531         sb.append(", endpoint=").append(endpoint.getEndpointURI());
532         sb.append(", disposed=").append(getLifecycleState().isDisposed());
533         sb.append('}');
534         return sb.toString();
535     }
536 
537     // TODO MULE-4871 Endpoint should not be mutable
538 
539     public void setEndpoint(ImmutableEndpoint endpoint)
540     {
541         if (endpoint == null)
542         {
543             throw new IllegalArgumentException("Endpoint cannot be null");
544         }
545         this.endpoint = endpoint;
546     }
547 
548     abstract protected WorkManager getWorkManager() throws MuleException;
549 
550     public boolean isStarted()
551     {
552         return getLifecycleState().isStarted();
553     }
554 
555     /**
556      * This method uses the connector's <code>createMuleMessageFactory</code> method to create
557      * a new {@link MuleMessageFactory}. Subclasses may need to override this method in order to
558      * perform additional initialization on the message factory before it's actually used.
559      */
560     protected MuleMessageFactory createMuleMessageFactory() throws CreateException
561     {
562         return connector.createMuleMessageFactory();
563     }
564 
565     /**
566      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
567      * The payload of the new message will be taken from <code>transportMessage</code>, all
568      * message properties will be copied from <code>previousMessage</code>.
569      */
570     public MuleMessage createMuleMessage(Object transportMessage, MuleMessage previousMessage,
571                                          String encoding) throws MuleException
572     {
573         try
574         {
575             return muleMessageFactory.create(transportMessage, previousMessage, encoding);
576         }
577         catch (Exception e)
578         {
579             throw new CreateException(CoreMessages.failedToCreate("MuleMessage"), e);
580         }
581     }
582 
583     /**
584      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
585      * This is the designated way to build {@link MuleMessage}s from the transport specific message.
586      */
587     public MuleMessage createMuleMessage(Object transportMessage, String encoding) throws MuleException
588     {
589         try
590         {
591             return muleMessageFactory.create(transportMessage, encoding);
592         }
593         catch (Exception e)
594         {
595             throw new CreateException(CoreMessages.failedToCreate("MuleMessage"), e, this);
596         }
597     }
598 
599     /**
600      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
601      * Uses the default encoding.
602      *
603      * @see MuleConfiguration#getDefaultEncoding()
604      */
605     public MuleMessage createMuleMessage(Object transportMessage) throws MuleException
606     {
607         String encoding = endpoint.getMuleContext().getConfiguration().getDefaultEncoding();
608         return createMuleMessage(transportMessage, encoding);
609     }
610 
611     /**
612      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
613      * Rather than passing in a transport message instance, {@link NullPayload} is used instead.
614      * Uses the default encoding.
615      */
616     protected MuleMessage createNullMuleMessage() throws MuleException
617     {
618         return createMuleMessage(null);
619     }
620 
621     /**
622      * @return true if doStart() must come strictly after the completion of doConnect()
623      */
624     protected boolean isDoStartMustFollowDoConnect()
625     {
626         return false;
627     }
628 }