View Javadoc

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