View Javadoc

1   /*
2    * $Id: AbstractMessageReceiver.java 12269 2008-07-10 04:19:03Z dfeist $
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.transport;
12  
13  import org.mule.DefaultMuleEvent;
14  import org.mule.DefaultMuleSession;
15  import org.mule.NullSessionHandler;
16  import org.mule.OptimizedRequestContext;
17  import org.mule.RequestContext;
18  import org.mule.ResponseOutputStream;
19  import org.mule.api.MuleEvent;
20  import org.mule.api.MuleException;
21  import org.mule.api.MuleMessage;
22  import org.mule.api.MuleSession;
23  import org.mule.api.config.MuleProperties;
24  import org.mule.api.context.WorkManager;
25  import org.mule.api.endpoint.EndpointURI;
26  import org.mule.api.endpoint.InboundEndpoint;
27  import org.mule.api.lifecycle.CreateException;
28  import org.mule.api.lifecycle.InitialisationException;
29  import org.mule.api.security.SecurityException;
30  import org.mule.api.service.Service;
31  import org.mule.api.transaction.Transaction;
32  import org.mule.api.transport.ConnectionStrategy;
33  import org.mule.api.transport.Connector;
34  import org.mule.api.transport.InternalMessageListener;
35  import org.mule.api.transport.MessageReceiver;
36  import org.mule.config.ExceptionHelper;
37  import org.mule.config.i18n.CoreMessages;
38  import org.mule.context.notification.ConnectionNotification;
39  import org.mule.context.notification.EndpointMessageNotification;
40  import org.mule.context.notification.SecurityNotification;
41  import org.mule.transaction.TransactionCoordination;
42  import org.mule.util.ClassUtils;
43  import org.mule.util.StringMessageUtils;
44  import org.mule.util.concurrent.WaitableBoolean;
45  
46  import java.io.OutputStream;
47  
48  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
49  
50  import org.apache.commons.logging.Log;
51  import org.apache.commons.logging.LogFactory;
52  
53  /**
54   * <code>AbstractMessageReceiver</code> provides common methods for all Message
55   * Receivers provided with Mule. A message receiver enables an endpoint to receive a
56   * message from an external system.
57   */
58  public abstract class AbstractMessageReceiver implements MessageReceiver
59  {
60      /** logger used by this class */
61      protected final Log logger = LogFactory.getLog(getClass());
62  
63      /** The Service with which this receiver is associated with */
64      protected Service service = null;
65  
66      /** The endpoint descriptor which is associated with this receiver */
67      protected InboundEndpoint endpoint = null;
68  
69      private InternalMessageListener listener;
70  
71      /** the connector associated with this receiver */
72      protected AbstractConnector connector = null;
73  
74      protected final AtomicBoolean disposing = new AtomicBoolean(false);
75  
76      protected final WaitableBoolean connected = new WaitableBoolean(false);
77  
78      protected final WaitableBoolean stopped = new WaitableBoolean(true);
79  
80      protected final AtomicBoolean connecting = new AtomicBoolean(false);
81  
82      /**
83       * Stores the key to this receiver, as used by the Connector to
84       * store the receiver.
85       */
86      protected String receiverKey = null;
87  
88      /**
89       * Stores the endpointUri that this receiver listens on. This enpoint can be
90       * different to the endpointUri in the endpoint stored on the receiver as
91       * endpoint endpointUri may get rewritten if this endpointUri is a wildcard
92       * endpointUri such as jms.*
93       */
94      private EndpointURI endpointUri;
95  
96      private WorkManager workManager;
97  
98      protected ConnectionStrategy connectionStrategy;
99  
100     protected boolean responseEndpoint = false;
101 
102     
103     /**
104      * Creates the Message Receiver
105      *
106      * @param connector the endpoint that created this listener
107      * @param service the service to associate with the receiver. When data is
108      *                  received the service <code>dispatchEvent</code> or
109      *                  <code>sendEvent</code> is used to dispatch the data to the
110      *                  relivant UMO.
111      * @param endpoint  the provider contains the endpointUri on which the receiver
112      *                  will listen on. The endpointUri can be anything and is specific to
113      *                  the receiver implementation i.e. an email address, a directory, a
114      *                  jms destination or port address.
115      * @see Service
116      * @see InboundEndpoint
117      */
118     public AbstractMessageReceiver(Connector connector, Service service, InboundEndpoint endpoint)
119             throws CreateException
120     {
121         setConnector(connector);
122         setService(service);
123         setEndpoint(endpoint);
124         if (service.getResponseRouter() != null && service.getResponseRouter().getEndpoints().contains(endpoint))
125         {
126             responseEndpoint = true;
127         }
128     }
129 
130     /**
131      * Method used to perform any initialisation work. If a fatal error occurs during
132      * initialisation an <code>InitialisationException</code> should be thrown,
133      * causing the Mule instance to shutdown. If the error is recoverable, say by
134      * retrying to connect, a <code>RecoverableException</code> should be thrown.
135      * There is no guarantee that by throwing a Recoverable exception that the Mule
136      * instance will not shut down.
137      *
138      * @throws org.mule.api.lifecycle.InitialisationException
139      *          if a fatal error occurs causing the Mule
140      *          instance to shutdown
141      * @throws org.mule.api.lifecycle.RecoverableException
142      *          if an error occurs that can be recovered from
143      */
144     public void initialise() throws InitialisationException
145     {
146         listener = new DefaultInternalMessageListener();
147         endpointUri = endpoint.getEndpointURI();
148 
149         try
150         {
151             workManager = this.connector.getReceiverWorkManager("receiver");
152         }
153         catch (MuleException e)
154         {
155             throw new InitialisationException(e, this);
156         }
157 
158         connectionStrategy = this.endpoint.getConnectionStrategy();
159         doInitialise();
160     }
161 
162     /*
163     * (non-Javadoc)
164     *
165     * @see org.mule.api.transport.MessageReceiver#getEndpointName()
166     */
167     public InboundEndpoint getEndpoint()
168     {
169         return endpoint;
170     }
171 
172     /*
173      * (non-Javadoc)
174      * 
175      * @see org.mule.api.transport.MessageReceiver#getExceptionListener()
176      */
177     public void handleException(Exception exception)
178     {
179         if (exception instanceof ConnectException)
180         {
181             logger.info("Exception caught is a ConnectException, disconnecting receiver and invoking ReconnectStrategy");
182             try
183             {
184                 disconnect();
185             }
186             catch (Exception e)
187             {
188                 connector.getExceptionListener().exceptionThrown(e);
189             }
190         }
191         connector.getExceptionListener().exceptionThrown(exception);
192         if (exception instanceof ConnectException)
193         {
194             try
195             {
196                 logger.warn("Reconnecting after exception: " + exception.getMessage(), exception);
197                 connectionStrategy.connect(this);
198             }
199             catch (MuleException e)
200             {
201                 connector.getExceptionListener().exceptionThrown(e);
202             }
203         }
204     }
205 
206     /**
207      * This method is used to set any additional aand possibly transport specific
208      * information on the return message where it has an exception payload.
209      *
210      * @param message
211      * @param exception
212      */
213     protected void setExceptionDetails(MuleMessage message, Throwable exception)
214     {
215         String propName = ExceptionHelper.getErrorCodePropertyName(connector.getProtocol());
216         // If we dont find a error code property we can assume there are not
217         // error code mappings for this connector
218         if (propName != null)
219         {
220             String code = ExceptionHelper.getErrorMapping(connector.getProtocol(), exception.getClass());
221             if (logger.isDebugEnabled())
222             {
223                 logger.debug("Setting error code for: " + connector.getProtocol() + ", " + propName + "="
224                         + code);
225             }
226             message.setProperty(propName, code);
227         }
228     }
229 
230     public Connector getConnector()
231     {
232         return connector;
233     }
234 
235     public void setConnector(Connector connector)
236     {
237         if (connector != null)
238         {
239             if (connector instanceof AbstractConnector)
240             {
241                 this.connector = (AbstractConnector) connector;
242             }
243             else
244             {
245                 throw new IllegalArgumentException(CoreMessages.propertyIsNotSupportedType(
246                         "connector", AbstractConnector.class, connector.getClass()).getMessage());
247             }
248         }
249         else
250         {
251             throw new IllegalArgumentException(CoreMessages.objectIsNull("connector").getMessage());
252         }
253     }
254 
255     public Service getService()
256     {
257         return service;
258     }
259 
260     public final MuleMessage routeMessage(MuleMessage message) throws MuleException
261     {
262         return routeMessage(message, (endpoint.isSynchronous() || TransactionCoordination.getInstance()
263                 .getTransaction() != null));
264     }
265 
266     public final MuleMessage routeMessage(MuleMessage message, boolean synchronous) throws MuleException
267     {
268         Transaction tx = TransactionCoordination.getInstance().getTransaction();
269         return routeMessage(message, tx, tx != null || synchronous, null);
270     }
271 
272     public final MuleMessage routeMessage(MuleMessage message, Transaction trans, boolean synchronous)
273             throws MuleException
274     {
275         return routeMessage(message, trans, synchronous, null);
276     }
277 
278     public final MuleMessage routeMessage(MuleMessage message, OutputStream outputStream) throws MuleException
279     {
280         return routeMessage(message, endpoint.isSynchronous(), outputStream);
281     }
282 
283     public final MuleMessage routeMessage(MuleMessage message, boolean synchronous, OutputStream outputStream)
284             throws MuleException
285     {
286         Transaction tx = TransactionCoordination.getInstance().getTransaction();
287         return routeMessage(message, tx, tx != null || synchronous, outputStream);
288     }
289 
290     public final MuleMessage routeMessage(MuleMessage message,
291                                          Transaction trans,
292                                          boolean synchronous,
293                                          OutputStream outputStream) throws MuleException
294     {
295 
296         if (connector.isEnableMessageEvents())
297         {
298             connector.fireNotification(
299                     new EndpointMessageNotification(message, endpoint, service.getName(), EndpointMessageNotification.MESSAGE_RECEIVED));
300         }
301 
302         //IF REMOTE_SYNCis set on the endpoint, we need to set it on the message
303         if (endpoint.isRemoteSync())
304         {
305             message.setBooleanProperty(MuleProperties.MULE_REMOTE_SYNC_PROPERTY, true);
306         }
307         //Enforce a sync endpoint if remote sync is set
308         if (message.getBooleanProperty(MuleProperties.MULE_REMOTE_SYNC_PROPERTY, false))
309         {
310             synchronous = true;
311         }
312 
313         if (logger.isDebugEnabled())
314         {
315             logger.debug("Message Received from: " + endpoint.getEndpointURI());
316         }
317         if (logger.isTraceEnabled())
318         {
319             try
320             {
321                 logger.trace("Message Payload: \n"
322                         + StringMessageUtils.truncate(StringMessageUtils.toString(message.getPayload()),
323                         200, false));
324             }
325             catch (Exception e)
326             {
327                 // ignore
328             }
329         }
330 
331         // Apply the endpoint filter if one is configured
332         if (endpoint.getFilter() != null)
333         {
334             if (!endpoint.getFilter().accept(message))
335             {
336                 //TODO RM* This ain't pretty, we don't yet have an event context since the message hasn't gon to the 
337                 //message listener yet. So we need to create a new context so that EventAwareTransformers can be applied
338                 //to response messages where the filter denied the message
339                 //Maybe the filter should be checked in the MessageListener...
340                 message = handleUnacceptedFilter(message);
341                 RequestContext.setEvent(new DefaultMuleEvent(message, endpoint,
342                         new DefaultMuleSession(message, new NullSessionHandler(), connector.getMuleContext()), synchronous));
343                 return message;
344             }
345         }
346         return listener.onMessage(message, trans, synchronous, outputStream);
347     }
348 
349     protected MuleMessage handleUnacceptedFilter(MuleMessage message)
350     {
351         String messageId;
352         messageId = message.getUniqueId();
353 
354         if (logger.isDebugEnabled())
355         {
356             logger.debug("Message " + messageId + " failed to pass filter on endpoint: " + endpoint
357                     + ". Message is being ignored");
358         }
359 
360         return message;
361     }
362 
363     /*
364      * (non-Javadoc)
365      * 
366      * @see org.mule.api.transport.MessageReceiver#setEndpoint(org.mule.api.endpoint.Endpoint)
367      */
368     public void setEndpoint(InboundEndpoint endpoint)
369     {
370         if (endpoint == null)
371         {
372             throw new IllegalArgumentException("Endpoint cannot be null");
373         }
374         this.endpoint = endpoint;
375     }
376 
377     /*
378      * (non-Javadoc)
379      * 
380      * @see org.mule.api.transport.MessageReceiver#setSession(org.mule.api.MuleSession)
381      */
382     public void setService(Service service)
383     {
384         if (service == null)
385         {
386             throw new IllegalArgumentException("Service cannot be null");
387         }
388         this.service = service;
389     }
390 
391     public final void dispose()
392     {
393         stop();
394         disposing.set(true);
395         doDispose();
396     }
397 
398     public EndpointURI getEndpointURI()
399     {
400         return endpointUri;
401     }
402 
403     protected WorkManager getWorkManager()
404     {
405         return workManager;
406     }
407 
408     protected void setWorkManager(WorkManager workManager)
409     {
410         this.workManager = workManager;
411     }
412 
413     public void connect() throws Exception
414     {
415         if (connected.get())
416         {
417             return;
418         }
419 
420         if (connecting.compareAndSet(false, true))
421         {
422             if (logger.isDebugEnabled())
423             {
424                 logger.debug("Connecting: " + this);
425             }
426 
427             connectionStrategy.connect(this);
428 
429             logger.info("Connected: " + this);
430             return;
431         }
432 
433         try
434         {
435             //Make sure the connector has connected. If it is connected, this method does nothing
436             connectionStrategy.connect(connector);
437 
438             this.doConnect();
439             connected.set(true);
440             connecting.set(false);
441 
442             connector.fireNotification(new ConnectionNotification(this, getConnectEventId(),
443                     ConnectionNotification.CONNECTION_CONNECTED));
444         }
445         catch (Exception e)
446         {
447             connected.set(false);
448             connecting.set(false);
449 
450             connector.fireNotification(new ConnectionNotification(this, getConnectEventId(),
451                     ConnectionNotification.CONNECTION_FAILED));
452 
453             if (e instanceof ConnectException)
454             {
455                 throw (ConnectException) e;
456             }
457             else
458             {
459                 throw new ConnectException(e, this);
460             }
461         }
462     }
463 
464     public void disconnect() throws Exception
465     {
466         if (logger.isDebugEnabled())
467         {
468             logger.debug("Disconnecting: " + this);
469         }
470 
471         this.doDisconnect();
472         connected.set(false);
473 
474         logger.info("Disconnected: " + this);
475 
476         connector.fireNotification(new ConnectionNotification(this, getConnectEventId(),
477                 ConnectionNotification.CONNECTION_DISCONNECTED));
478     }
479 
480     public String getConnectionDescription()
481     {
482         return endpoint.getEndpointURI().toString();
483     }
484 
485     public final void start() throws MuleException
486     {
487         if (stopped.compareAndSet(true, false))
488         {
489             if (!connected.get())
490             {
491                 connectionStrategy.connect(this);
492             }
493             doStart();
494         }
495     }
496 
497     public final void stop()
498     {
499         try
500         {
501             if (connected.get())
502             {
503                 disconnect();
504             }
505         }
506         catch (Exception e)
507         {
508             // TODO MULE-863: What should we really do?
509             logger.error(e.getMessage(), e);
510         }
511 
512         if (stopped.compareAndSet(false, true))
513         {
514             try
515             {
516                 doStop();
517             }
518             catch (MuleException e)
519             {
520                 // TODO MULE-863: What should we really do?
521                 logger.error(e.getMessage(), e);
522             }
523 
524         }
525     }
526 
527     public final boolean isConnected()
528     {
529         return connected.get();
530     }
531 
532     public InternalMessageListener getListener()
533     {
534         return listener;
535     }
536 
537     public void setListener(InternalMessageListener listener)
538     {
539         this.listener = listener;
540     }
541 
542     private class DefaultInternalMessageListener implements InternalMessageListener
543     {
544 
545         public MuleMessage onMessage(MuleMessage message,
546                                     Transaction trans,
547                                     boolean synchronous,
548                                     OutputStream outputStream) throws MuleException
549         {
550 
551             MuleMessage resultMessage = null;
552             ResponseOutputStream ros = null;
553             if (outputStream != null)
554             {
555                 if (outputStream instanceof ResponseOutputStream)
556                 {
557                     ros = (ResponseOutputStream) outputStream;
558                 }
559                 else
560                 {
561                     ros = new ResponseOutputStream(outputStream);
562                 }
563             }
564             MuleSession session = new DefaultMuleSession(message, connector.getSessionHandler(), service, connector.getMuleContext());
565             MuleEvent muleEvent = new DefaultMuleEvent(message, endpoint, session, synchronous, ros);
566             muleEvent = OptimizedRequestContext.unsafeSetEvent(muleEvent);
567 
568             // Apply Security filter if one is set
569             boolean authorised = false;
570             if (endpoint.getSecurityFilter() != null)
571             {
572                 try
573                 {
574                     endpoint.getSecurityFilter().authenticate(muleEvent);
575                     authorised = true;
576                 }
577                 catch (SecurityException e)
578                 {
579                     logger.warn("Request was made but was not authenticated: " + e.getMessage(), e);
580                     connector.fireNotification(new SecurityNotification(e,
581                             SecurityNotification.SECURITY_AUTHENTICATION_FAILED));
582                     handleException(e);
583                     resultMessage = RequestContext.getEvent().getMessage();
584                 }
585             }
586             else
587             {
588                 authorised = true;
589             }
590 
591             if (authorised)
592             {
593                 // This is a replyTo event for a current request
594                 if (responseEndpoint)
595                 {
596                     // Transform response message before it is processed by response router(s)
597                     muleEvent.transformMessage();
598                     service.getResponseRouter().route(muleEvent);
599                     return null;
600                 }
601                 else
602                 {
603                     resultMessage = service.getInboundRouter().route(muleEvent);
604                 }
605             }
606             if (resultMessage != null)
607             {
608                 if (resultMessage.getExceptionPayload() != null)
609                 {
610                     setExceptionDetails(resultMessage, resultMessage.getExceptionPayload().getException());
611                 }
612                 resultMessage.applyTransformers(endpoint.getResponseTransformers());
613             }
614             return resultMessage;
615         }
616     }
617 
618     protected String getConnectEventId()
619     {
620         return connector.getName() + ".receiver (" + endpoint.getEndpointURI() + ")";
621     }
622 
623     public void setReceiverKey(String receiverKey)
624     {
625         this.receiverKey = receiverKey;
626     }
627 
628     public String getReceiverKey()
629     {
630         return receiverKey;
631     }
632 
633     public String toString()
634     {
635         final StringBuffer sb = new StringBuffer(80);
636         sb.append(ClassUtils.getSimpleName(this.getClass()));
637         sb.append("{this=").append(Integer.toHexString(System.identityHashCode(this)));
638         sb.append(", receiverKey=").append(receiverKey);
639         sb.append(", endpoint=").append(endpoint.getEndpointURI());
640         sb.append('}');
641         return sb.toString();
642     }
643 
644     protected void doInitialise() throws InitialisationException
645     {
646         //nothing to do
647         //TODO this was addd to complete the lifecycle phases on the message receivers however, we ened to
648         //review each receiver to move logic from the contstructor to the init method. The Connector will
649         //call this method when the receiver is created. see MULE-2113 for more information about lifecycle clean up
650     }
651 
652     protected abstract void doStart() throws MuleException;
653 
654     protected abstract void doStop() throws MuleException;
655 
656     protected abstract void doConnect() throws Exception;
657 
658     protected abstract void doDisconnect() throws Exception;
659 
660     protected abstract void doDispose();
661 
662 }