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.transport;
8   
9   import org.mule.api.MuleException;
10  import org.mule.api.MuleMessage;
11  import org.mule.api.config.MuleConfiguration;
12  import org.mule.api.context.WorkManager;
13  import org.mule.api.endpoint.ImmutableEndpoint;
14  import org.mule.api.lifecycle.CreateException;
15  import org.mule.api.lifecycle.InitialisationException;
16  import org.mule.api.lifecycle.LifecycleCallback;
17  import org.mule.api.lifecycle.LifecycleState;
18  import org.mule.api.lifecycle.LifecycleStateEnabled;
19  import org.mule.api.retry.RetryContext;
20  import org.mule.api.retry.RetryPolicyTemplate;
21  import org.mule.api.transport.Connectable;
22  import org.mule.api.transport.Connector;
23  import org.mule.api.transport.MuleMessageFactory;
24  import org.mule.config.i18n.CoreMessages;
25  import org.mule.config.i18n.Message;
26  import org.mule.config.i18n.MessageFactory;
27  import org.mule.context.notification.ConnectionNotification;
28  import org.mule.util.ClassUtils;
29  
30  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  /**
36   * Provide a default dispatch (client) support for handling threads lifecycle and validation.
37   */
38  public abstract class AbstractTransportMessageHandler<O> implements Connectable, LifecycleStateEnabled
39  {
40      protected transient Log logger = LogFactory.getLog(getClass());
41  
42      protected ImmutableEndpoint endpoint;
43      protected final AbstractConnector connector;
44      protected RetryPolicyTemplate retryTemplate;
45      protected MuleMessageFactory muleMessageFactory = null;
46  
47      protected ConnectableLifecycleManager<O> lifecycleManager;
48      // TODO This state info. needs to be incorporated into the ConnectableLifecycleManager
49      protected final AtomicBoolean connected = new AtomicBoolean(false);
50      
51      public AbstractTransportMessageHandler(ImmutableEndpoint endpoint)
52      {
53          this.endpoint = endpoint;
54          this.connector = (AbstractConnector) endpoint.getConnector();
55          this.lifecycleManager = createLifecycleManager();
56      }
57  
58      protected abstract ConnectableLifecycleManager<O> createLifecycleManager();
59  
60      public LifecycleState getLifecycleState()
61      {
62          return lifecycleManager.getState();
63      }
64  
65      protected void disposeAndLogException()
66      {
67          try
68          {
69              dispose();
70          }
71          catch (Throwable t)
72          {
73              logger.error("Could not dispose of the message dispatcher!", t);
74          }
75      }
76  
77      public boolean validate()
78      {
79          // by default a dispatcher/requester can be used unless disposed
80          return !getLifecycleState().isDisposed();
81      }
82  
83      public void activate()
84      {
85          // nothing to do by default
86      }
87  
88      public void passivate()
89      {
90          // nothing to do by default
91      }
92  
93      public void initialise() throws InitialisationException
94      {
95          try
96          {
97              lifecycleManager.fireInitialisePhase(new LifecycleCallback<O>()
98              {
99                  public void onTransition(String phaseName, O object) throws MuleException
100                 {
101                     initializeRetryPolicy();
102                     initializeMessageFactory();
103                     doInitialise();
104                 }
105             });
106         }
107         catch (InitialisationException e)
108         {
109             throw e;
110         }
111         catch (MuleException e)
112         {
113             throw new InitialisationException(e, this);
114         }
115 
116     }
117 
118     protected void initializeRetryPolicy()
119     {
120         if (endpoint.getRetryPolicyTemplate() != null)
121         {
122             retryTemplate = endpoint.getRetryPolicyTemplate();
123         }
124         else
125         {
126             retryTemplate = connector.getRetryPolicyTemplate();
127         }
128     }
129 
130     /**
131      * Subclasses can override this method to create a custom {@link MuleMessageFactory} instead
132      * of re-using the instance from the connector.
133      */
134     protected void initializeMessageFactory() throws InitialisationException
135     {
136         try
137         {
138             muleMessageFactory = connector.getMuleMessageFactory();
139         }
140         catch (CreateException ce)
141         {
142             Message message = MessageFactory.createStaticMessage(ce.getMessage());
143             throw new InitialisationException(message, ce, this);
144         }
145     }
146 
147     /**
148      * Template method to destroy any resources held by the Message Dispatcher
149      */
150     public synchronized void dispose()
151     {
152         try
153         {
154             if (isStarted())
155             {
156                 stop();
157             }
158             if (isConnected())
159             {
160                 disconnect();
161             }
162         }
163         catch (Exception e)
164         {
165             logger.warn(e.getMessage(), e);
166         }
167 
168         try
169         {
170             lifecycleManager.fireDisposePhase(new LifecycleCallback<O>() 
171             {
172                 public void onTransition(String phaseName, O object) throws MuleException
173                 {
174                     doDispose();
175                 }
176             });
177         }
178         catch (MuleException e)
179         {
180             logger.warn(e.getMessage(), e);
181         }
182     }
183 
184     public Connector getConnector()
185     {
186         return connector;
187     }
188 
189     public ImmutableEndpoint getEndpoint()
190     {
191         return endpoint;
192     }
193 
194     public final synchronized void connect() throws Exception
195     {
196         // This method may be called to ensure transport is connected, if it is
197         // already connected then just return.
198         if (connected.get())
199         {
200             return;
201         }
202 
203         if (getLifecycleState().isDisposed())
204         {
205             throw new IllegalStateException(
206                     "Requester/dispatcher has been disposed; cannot connect to resource:" + this);
207         }
208 
209         if (logger.isDebugEnabled())
210         {
211             logger.debug("Connecting: " + this);
212         }
213 
214         doConnect();
215         connected.set(true);
216 
217         if (logger.isDebugEnabled())
218         {
219             logger.debug("Connected: " + getConnectionDescription());
220         }
221     }
222 
223     public RetryContext validateConnection(RetryContext retryContext)
224     {
225         retryContext.setOk();
226         return retryContext;
227     }
228 
229     public final synchronized void disconnect() throws Exception
230     {
231         if (isStarted())
232         {
233             stop();
234         }
235         
236         if (logger.isDebugEnabled())
237         {
238             logger.debug("Disconnecting: " + this);
239         }
240 
241         doDisconnect();
242         connected.set(false);
243 
244         if (logger.isDebugEnabled())
245         {
246             logger.debug("Disconnected: " + this);
247         }
248         connector.fireNotification(new ConnectionNotification(this, getConnectEventId(endpoint),
249                 ConnectionNotification.CONNECTION_DISCONNECTED));
250     }
251 
252     protected String getConnectEventId(ImmutableEndpoint endpoint)
253     {
254         return connector.getName() + ".dispatcher(" + endpoint.getEndpointURI().getUri() + ")";
255     }
256 
257     public final boolean isConnected()
258     {
259         return connected.get();
260     }
261 
262     protected boolean isDoThreading()
263     {
264         return connector.getDispatcherThreadingProfile().isDoThreading();
265     }
266 
267     /**
268      * Returns a string identifying the underlying resource
269      */
270     public String getConnectionDescription()
271     {
272         return "endpoint.outbound." + endpoint.getEndpointURI().toString();
273     }
274 
275     /**
276      * This method will start the connectable, calling {@link #connect()} if it is
277      * needed.
278      * <p/>
279      * This method is synchronous or not depending on how the {@link #retryTemplate}
280      * behaves.
281      * <p/>
282      * This method ensures that {@link #doStart()} will be called at most one time
283      * and will return without error if the component is already started.
284      */
285     public final void start() throws MuleException
286     {
287         if (isStarted() || isStarting())
288         {
289             return;
290         }
291 
292         if (!isConnected())
293         {
294             try
295             {
296                 connect();
297             }
298             catch (MuleException me)
299             {
300                 throw me;
301             }
302             catch (Exception e)
303             {
304                 throw new ConnectException(e, this);
305             }
306         }
307 
308         lifecycleManager.fireStartPhase(new LifecycleCallback<O>()
309         {
310             public void onTransition(String phaseName, O object) throws MuleException
311             {
312                 doStart();
313             }
314         });
315     }
316 
317     public final void stop() throws MuleException
318     {
319         lifecycleManager.fireStopPhase(new LifecycleCallback<O>()
320         {
321             public void onTransition(String phaseName, O object) throws MuleException
322             {
323                 try
324                 {
325                     doStop();
326                 }
327                 catch (MuleException e)
328                 {
329                     logger.error(e.getMessage(), e);
330                 }
331             }
332         });
333 
334     }
335 
336     protected void doInitialise() throws InitialisationException
337     {
338         // nothing to do by default
339     }
340 
341     protected void doDispose()
342     {
343         // nothing to do by default
344     }
345 
346     protected void doConnect() throws Exception
347     {
348         // nothing to do by default
349     }
350 
351     protected void doDisconnect() throws Exception
352     {
353         // nothing to do by default
354     }
355 
356     protected void doStart() throws MuleException
357     {
358         // nothing to do by default
359     }
360 
361     protected void doStop() throws MuleException
362     {
363         // nothing to do by default
364     }
365 
366     @Override
367     public String toString()
368     {
369         final StringBuffer sb = new StringBuffer(80);
370         sb.append(ClassUtils.getSimpleName(this.getClass()));
371         sb.append("{this=").append(Integer.toHexString(System.identityHashCode(this)));
372         sb.append(", endpoint=").append(endpoint.getEndpointURI());
373         sb.append(", disposed=").append(getLifecycleState().isDisposed());
374         sb.append('}');
375         return sb.toString();
376     }
377 
378     // TODO MULE-4871 Endpoint should not be mutable
379 
380     public void setEndpoint(ImmutableEndpoint endpoint)
381     {
382         if (endpoint == null)
383         {
384             throw new IllegalArgumentException("Endpoint cannot be null");
385         }
386         this.endpoint = endpoint;
387     }
388 
389     abstract protected WorkManager getWorkManager() throws MuleException;
390 
391     public boolean isStarted()
392     {
393         return getLifecycleState().isStarted();
394     }
395 
396     public boolean isStarting()
397     {
398         return getLifecycleState().isStarting();
399     }
400 
401     public boolean isStopped()
402     {
403         return getLifecycleState().isStopped();
404     }
405 
406     public boolean isStopping()
407     {
408         return getLifecycleState().isStopping();
409     }
410 
411     /**
412      * This method uses the connector's <code>createMuleMessageFactory</code> method to create
413      * a new {@link MuleMessageFactory}. Subclasses may need to override this method in order to
414      * perform additional initialization on the message factory before it's actually used.
415      */
416     protected MuleMessageFactory createMuleMessageFactory() throws CreateException
417     {
418         return connector.createMuleMessageFactory();
419     }
420 
421     /**
422      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
423      * The payload of the new message will be taken from <code>transportMessage</code>, all
424      * message properties will be copied from <code>previousMessage</code>.
425      */
426     public MuleMessage createMuleMessage(Object transportMessage, MuleMessage previousMessage,
427                                          String encoding) throws MuleException
428     {
429         try
430         {
431             return muleMessageFactory.create(transportMessage, previousMessage, encoding);
432         }
433         catch (Exception e)
434         {
435             throw new CreateException(CoreMessages.failedToCreate("MuleMessage"), e);
436         }
437     }
438 
439     /**
440      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
441      * This is the designated way to build {@link MuleMessage}s from the transport specific message.
442      */
443     public MuleMessage createMuleMessage(Object transportMessage, String encoding) throws MuleException
444     {
445         try
446         {
447             return muleMessageFactory.create(transportMessage, encoding);
448         }
449         catch (Exception e)
450         {
451             throw new CreateException(CoreMessages.failedToCreate("MuleMessage"), e, this);
452         }
453     }
454 
455     /**
456      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
457      * Uses the default encoding.
458      *
459      * @see MuleConfiguration#getDefaultEncoding()
460      */
461     public MuleMessage createMuleMessage(Object transportMessage) throws MuleException
462     {
463         String encoding = endpoint.getMuleContext().getConfiguration().getDefaultEncoding();
464         return createMuleMessage(transportMessage, encoding);
465     }
466 
467     /**
468      * Uses this object's {@link MuleMessageFactory} to create a new {@link MuleMessage} instance.
469      * Rather than passing in a transport message instance, {@link NullPayload} is used instead.
470      * Uses the default encoding.
471      */
472     protected MuleMessage createNullMuleMessage() throws MuleException
473     {
474         return createMuleMessage(null);
475     }
476 }