View Javadoc

1   /*
2    * $Id: DefaultMuleEvent.java 20794 2010-12-17 02:05:11Z 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;
12  
13  import org.mule.api.DefaultMuleException;
14  import org.mule.api.MuleContext;
15  import org.mule.api.MuleEvent;
16  import org.mule.api.MuleException;
17  import org.mule.api.MuleMessage;
18  import org.mule.api.MuleSession;
19  import org.mule.api.ThreadSafeAccess;
20  import org.mule.api.config.MuleProperties;
21  import org.mule.api.construct.FlowConstruct;
22  import org.mule.api.endpoint.EndpointBuilder;
23  import org.mule.api.endpoint.EndpointURI;
24  import org.mule.api.endpoint.ImmutableEndpoint;
25  import org.mule.api.endpoint.InboundEndpoint;
26  import org.mule.api.registry.ServiceType;
27  import org.mule.api.security.Credentials;
28  import org.mule.api.transformer.DataType;
29  import org.mule.api.transformer.Transformer;
30  import org.mule.api.transformer.TransformerException;
31  import org.mule.config.i18n.CoreMessages;
32  import org.mule.endpoint.DefaultEndpointFactory;
33  import org.mule.endpoint.MuleEndpointURI;
34  import org.mule.management.stats.ProcessingTime;
35  import org.mule.security.MuleCredentials;
36  import org.mule.session.DefaultMuleSession;
37  import org.mule.transformer.types.DataTypeFactory;
38  import org.mule.transport.service.TransportServiceDescriptor;
39  import org.mule.util.UUID;
40  import org.mule.util.store.DeserializationPostInitialisable;
41  
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  
45  import java.io.IOException;
46  import java.io.ObjectInputStream;
47  import java.io.ObjectOutputStream;
48  import java.io.OutputStream;
49  import java.util.EventObject;
50  import java.util.HashMap;
51  import java.util.Iterator;
52  import java.util.LinkedList;
53  import java.util.List;
54  import java.util.Map;
55  
56  /**
57   * <code>DefaultMuleEvent</code> represents any data event occurring in the Mule
58   * environment. All data sent or received within the Mule environment will be passed
59   * between components as an MuleEvent. <p/> The MuleEvent holds some data and provides
60   * helper methods for obtaining the data in a format that the receiving Mule component
61   * understands. The event can also maintain any number of properties that can be set
62   * and retrieved by Mule components.
63   */
64  
65  public class DefaultMuleEvent extends EventObject implements MuleEvent, ThreadSafeAccess, DeserializationPostInitialisable
66  {
67      /**
68       * Serial version
69       */
70      private static final long serialVersionUID = 1L;
71  
72      /**
73       * logger used by this class
74       */
75      private static Log logger = LogFactory.getLog(DefaultMuleEvent.class);
76  
77      /**
78       * The endpoint associated with the event
79       */
80      private transient ImmutableEndpoint endpoint = null;
81  
82      /**
83       * the Universally Unique ID for the event
84       */
85      private String id = null;
86  
87      /**
88       * The payload message used to read the payload of the event
89       */
90      private MuleMessage message = null;
91  
92      private MuleSession session;
93  
94      private boolean stopFurtherProcessing = false;
95  
96      private int timeout = TIMEOUT_NOT_SET_VALUE;
97  
98      private transient ResponseOutputStream outputStream = null;
99  
100     private transient Object transformedMessage = null;
101 
102     private Credentials credentials = null;
103 
104     protected String[] ignoredPropertyOverrides = new String[]{MuleProperties.MULE_METHOD_PROPERTY};
105 
106     private transient Map<String, Object> serializedData = null;
107 
108     private final ProcessingTime processingTime;
109 
110     /**
111      * Properties cache that only reads properties once from the inbound message and
112      * merges them with any properties on the endpoint. The message properties take
113      * precedence over the endpoint properties
114      *
115      * @param message
116      * @param endpoint
117      * @param service
118      * @param previousEvent
119      */
120     public DefaultMuleEvent(MuleMessage message,
121                             ImmutableEndpoint endpoint,
122                             FlowConstruct service,
123                             MuleEvent previousEvent)
124     {
125         super(message.getPayload());
126         this.message = message;
127         this.id = generateEventId();
128         this.session = previousEvent.getSession();
129         session.setFlowConstruct(service);
130         this.endpoint = endpoint;
131         this.timeout = previousEvent.getTimeout();
132         this.outputStream = (ResponseOutputStream) previousEvent.getOutputStream();
133         this.processingTime = ProcessingTime.newInstance(this.session, message.getMuleContext());
134         fillProperties(previousEvent);
135     }
136 
137     public DefaultMuleEvent(MuleMessage message,
138                             ImmutableEndpoint endpoint,
139                             MuleEvent previousEvent,
140                             MuleSession session)
141     {
142         super(message.getPayload());
143         this.message = message;
144         this.id = previousEvent.getId();
145         this.session = session;
146         this.endpoint = endpoint;
147         this.timeout = previousEvent.getTimeout();
148         this.outputStream = (ResponseOutputStream) previousEvent.getOutputStream();
149         this.processingTime = ProcessingTime.newInstance(this.session, message.getMuleContext());
150         fillProperties(previousEvent);
151     }
152 
153     public DefaultMuleEvent(MuleMessage message,
154                             ImmutableEndpoint endpoint,
155                             MuleSession session)
156     {
157         this(message, endpoint, session, null, null);
158     }
159 
160     public DefaultMuleEvent(MuleMessage message,
161                             ImmutableEndpoint endpoint,
162                             MuleSession session,
163                             ProcessingTime time)
164     {
165         this(message, endpoint, session, null, time);
166     }
167 
168     public DefaultMuleEvent(MuleMessage message,
169                             ImmutableEndpoint endpoint,
170                             MuleSession session,
171                             ResponseOutputStream outputStream)
172     {
173         this(message, endpoint, session, outputStream, null);
174     }
175 
176     public DefaultMuleEvent(MuleMessage message,
177                             ImmutableEndpoint endpoint,
178                             MuleSession session,
179                             ResponseOutputStream outputStream,
180                             ProcessingTime time)
181     {
182         super(message.getPayload());
183         this.message = message;
184         this.endpoint = endpoint;
185         this.session = session;
186         this.id = generateEventId();
187         this.outputStream = outputStream;
188         fillProperties(null);
189         this.processingTime = time != null ? time : ProcessingTime.newInstance(this.session, message.getMuleContext());
190     }
191     
192     /**
193      * A helper constructor used to rewrite an event payload
194      *
195      * @param message The message to use as the current payload of the event
196      * @param rewriteEvent the previous event that will be used as a template for this event
197      */
198     public DefaultMuleEvent(MuleMessage message, MuleEvent rewriteEvent)
199     {
200         super(message.getPayload());
201         this.message = message;
202         this.id = rewriteEvent.getId();
203         this.session = rewriteEvent.getSession();
204         session.setFlowConstruct(rewriteEvent.getFlowConstruct());
205         this.endpoint = rewriteEvent.getEndpoint();
206         this.timeout = rewriteEvent.getTimeout();
207         this.outputStream = (ResponseOutputStream) rewriteEvent.getOutputStream();
208         if (rewriteEvent instanceof DefaultMuleEvent)
209         {
210             this.transformedMessage = ((DefaultMuleEvent) rewriteEvent).getCachedMessage();
211             this.processingTime = ((DefaultMuleEvent)rewriteEvent).processingTime;
212         }
213         else
214         {
215             this.processingTime = ProcessingTime.newInstance(this.session, message.getMuleContext());
216         }
217         fillProperties(rewriteEvent);
218     }
219 
220     protected void fillProperties(MuleEvent previousEvent)
221     {
222         if (endpoint != null && endpoint.getProperties() != null)
223         {
224             for (Iterator iterator = endpoint.getProperties().keySet().iterator(); iterator.hasNext();)
225             {
226                 String prop = (String) iterator.next();
227                 Object value = endpoint.getProperties().get(prop);
228                 // don't overwrite property on the message
229                 if (!ignoreProperty(prop))
230                 {
231                     //inbound endpoint properties are in the invocation scope
232                     message.setInvocationProperty(prop, value);
233                 }
234             }
235         }
236 
237         setCredentials();
238     }
239 
240     /**
241      * This method is used to determine if a property on the previous event should be
242      * ignored for the next event. This method is here because we don't have proper
243      * scoped handling of meta data yet The rules are
244      * <ol>
245      * <li>If a property is already set on the current event don't overwrite with the previous event value
246      * <li>If the property name appears in the ignoredPropertyOverrides list, then we always set it on the new event
247      * </ol>
248      *
249      * @param key The name of the property to ignore
250      * @return true if the property should be ignored, false otherwise
251      */
252     protected boolean ignoreProperty(String key)
253     {
254         if (key == null)
255         {
256             return true;
257         }
258 
259         for (int i = 0; i < ignoredPropertyOverrides.length; i++)
260         {
261             if (key.equals(ignoredPropertyOverrides[i]))
262             {
263                 return false;
264             }
265         }
266 
267         return null != message.getOutboundProperty(key);
268     }
269 
270     protected void setCredentials()
271     {
272         if (null != endpoint && null != endpoint.getEndpointURI() && null != endpoint.getEndpointURI().getUserInfo())
273         {
274             final String userName = endpoint.getEndpointURI().getUser();
275             final String password = endpoint.getEndpointURI().getPassword();
276             if (password != null && userName != null)
277             {
278                 credentials = new MuleCredentials(userName, password.toCharArray());
279             }
280         }
281     }
282 
283     public Credentials getCredentials()
284     {
285         MuleCredentials creds = message.getOutboundProperty(MuleProperties.MULE_CREDENTIALS_PROPERTY);
286         return (credentials != null ? credentials : creds);
287     }
288 
289     Object getCachedMessage()
290     {
291         return transformedMessage;
292     }
293 
294     public MuleMessage getMessage()
295     {
296         return message;
297     }
298 
299     public byte[] getMessageAsBytes() throws DefaultMuleException
300     {
301         try
302         {
303             return message.getPayloadAsBytes();
304         }
305         catch (Exception e)
306         {
307             throw new DefaultMuleException(
308                     CoreMessages.cannotReadPayloadAsBytes(message.getPayload().getClass().getName()), e);
309         }
310     }
311 
312     @SuppressWarnings("unchecked")
313     public <T> T transformMessage(Class<T> outputType) throws TransformerException
314     {
315         return (T) transformMessage(DataTypeFactory.create(outputType));
316     }
317 
318     public <T> T transformMessage(DataType<T> outputType) throws TransformerException
319     {
320         if (outputType == null)
321         {
322             throw new TransformerException(CoreMessages.objectIsNull("outputType"));
323         }
324         return message.getPayload(outputType);
325     }
326 
327     /**
328      * This method will attempt to convert the transformed message into an array of
329      * bytes It will first check if the result of the transformation is a byte array
330      * and return that. Otherwise if the the result is a string it will serialized
331      * the CONTENTS of the string not the String object. finally it will check if the
332      * result is a Serializable object and convert that to an array of bytes.
333      *
334      * @return a byte[] representation of the message
335      * @throws TransformerException if an unsupported encoding is being used or if
336      *                              the result message is not a String byte[] or Seializable object
337      * @deprecated use {@link #transformMessage(org.mule.api.transformer.DataType)} instead
338      */
339     @Deprecated
340     public byte[] transformMessageToBytes() throws TransformerException
341     {
342         return transformMessage(DataType.BYTE_ARRAY_DATA_TYPE);
343     }
344 
345     /**
346      * Returns the message transformed into it's recognised or expected format and
347      * then into a String. The transformer used is the one configured on the endpoint
348      * through which this event was received.
349      *
350      * @return the message transformed into it's recognised or expected format as a
351      *         Strings.
352      * @throws org.mule.api.transformer.TransformerException
353      *          if a failure occurs in
354      *          the transformer
355      * @see org.mule.api.transformer.Transformer
356      */
357     public String transformMessageToString() throws TransformerException
358     {
359         return transformMessage(DataTypeFactory.createWithEncoding(String.class, getEncoding()));
360     }
361 
362     public String getMessageAsString() throws MuleException
363     {
364         return getMessageAsString(getEncoding());
365     }
366 
367     /**
368      * Returns the message contents for logging
369      *
370      * @param encoding the encoding to use when converting bytes to a string, if necessary
371      * @return the message contents as a string
372      * @throws org.mule.api.MuleException if the message cannot be converted into a
373      *                                    string
374      */
375     public String getMessageAsString(String encoding) throws MuleException
376     {
377         try
378         {
379             return message.getPayloadForLogging(encoding);
380         }
381         catch (Exception e)
382         {
383             throw new DefaultMuleException(
384                     CoreMessages.cannotReadPayloadAsString(message.getClass().getName()), e);
385         }
386     }
387 
388     public String getId()
389     {
390         return id;
391     }
392 
393     /**
394      * @see #getMessage()
395      * @deprecated use appropriate scope-aware calls on the MuleMessage (via event.getMessage())
396      */
397     @Deprecated
398     public Object getProperty(String name)
399     {
400         throw new UnsupportedOperationException("Method's behavior has changed in Mule 3, use " +
401                                                 "event.getMessage() and suitable scope-aware property access " +
402                                                 "methods on it");
403     }
404 
405     /**
406      * @see #getMessage()
407      * @deprecated use appropriate scope-aware calls on the MuleMessage (via event.getMessage())
408      */
409     @Deprecated
410     public Object getProperty(String name, Object defaultValue)
411     {
412         throw new UnsupportedOperationException("Method's behavior has changed in Mule 3, use " +
413                                                 "event.getMessage() and suitable scope-aware property access " +
414                                                 "methods on it");
415     }
416 
417     public ImmutableEndpoint getEndpoint()
418     {
419         return endpoint;
420     }
421 
422     @Override
423     public String toString()
424     {
425         StringBuffer buf = new StringBuffer(64);
426         buf.append("MuleEvent: ").append(getId());
427         buf.append(", stop processing=").append(isStopFurtherProcessing());
428         buf.append(", ").append(endpoint);
429 
430         return buf.toString();
431     }
432 
433     protected String generateEventId()
434     {
435         return UUID.getUUID();
436     }
437 
438     public MuleSession getSession()
439     {
440         return session;
441     }
442 
443     void setSession(MuleSession session)
444     {
445         this.session = session;
446     }
447 
448     /**
449      * Gets the recipient service of this event
450      */
451     public FlowConstruct getFlowConstruct()
452     {
453         return session.getFlowConstruct();
454     }
455 
456     /**
457      * Determines whether the default processing for this event will be executed
458      *
459      * @return Returns the stopFurtherProcessing.
460      */
461     public boolean isStopFurtherProcessing()
462     {
463         return stopFurtherProcessing;
464     }
465 
466     /**
467      * Setting this parameter will stop the Mule framework from processing this event
468      * in the standard way. This allow for client code to override default behaviour.
469      * The common reasons for doing this are - 1. The service has more than one send
470      * endpoint configured; the service must dispatch to other prviders
471      * programmatically by using the service on the current event 2. The service doesn't
472      * send the current event out through a endpoint. i.e. the processing of the
473      * event stops in the uMO.
474      *
475      * @param stopFurtherProcessing The stopFurtherProcessing to set.
476      */
477     public void setStopFurtherProcessing(boolean stopFurtherProcessing)
478     {
479         this.stopFurtherProcessing = stopFurtherProcessing;
480     }
481 
482     @Override
483     public boolean equals(Object o)
484     {
485         if (this == o)
486         {
487             return true;
488         }
489         if (!(o instanceof DefaultMuleEvent))
490         {
491             return false;
492         }
493 
494         final DefaultMuleEvent event = (DefaultMuleEvent) o;
495 
496         if (message != null ? !message.equals(event.message) : event.message != null)
497         {
498             return false;
499         }
500         return id.equals(event.id);
501     }
502 
503     @Override
504     public int hashCode()
505     {
506         return 29 * id.hashCode() + (message != null ? message.hashCode() : 0);
507     }
508 
509     public int getTimeout()
510     {
511         if (timeout == TIMEOUT_NOT_SET_VALUE)
512         {
513             // If this is not set it will use the default timeout value
514             timeout = endpoint.getResponseTimeout();
515         }
516         return timeout;
517     }
518 
519     public void setTimeout(int timeout)
520     {
521         this.timeout = timeout;
522     }
523 
524     /**
525      * An output stream can optionally be used to write response data to an incoming
526      * message.
527      *
528      * @return an output strem if one has been made available by the message receiver
529      *         that received the message
530      */
531     public OutputStream getOutputStream()
532     {
533         return outputStream;
534     }
535 
536     private void writeObject(ObjectOutputStream out) throws IOException
537     {
538         out.defaultWriteObject();
539         out.writeInt(endpoint.hashCode());
540         out.writeBoolean(endpoint instanceof InboundEndpoint);
541         out.writeObject(endpoint.getEndpointBuilderName());
542         
543         // make sure to write out the connector's name along with the endpoint URI. Omitting the
544         // connector will fail rebuilding the endpoint when this event is read back in and there
545         // is more than one connector for the protocol.
546         String uri = endpoint.getEndpointURI().getUri().toString();
547         String connectorName = endpoint.getConnector().getName();
548         out.writeObject(uri + "?connector=" + connectorName);
549 
550         // write number of Transformers
551         out.writeInt(endpoint.getTransformers().size());
552 
553         // write transformer names if necessary
554         if (endpoint.getTransformers().size() > 0)
555         {
556             for (Transformer transformer : endpoint.getTransformers())
557             {
558                 out.writeObject(transformer.getName());
559             }
560         }
561     }
562 
563     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException, MuleException
564     {
565         in.defaultReadObject();
566         serializedData = new HashMap<String, Object>();
567         serializedData.put("endpointHashcode", in.readInt());
568         serializedData.put("isInboundEndpoint", in.readBoolean());
569         serializedData.put("endpointBuilderName", in.readObject());
570         serializedData.put("endpointUri", in.readObject());
571         int count = in.readInt();
572 
573         List<String> transformerNames = new LinkedList<String>();
574         if (count > 0)
575         {
576             while (--count > 0)
577             {
578                 transformerNames.add((String) in.readObject());
579             }
580         }
581         serializedData.put("transformers", transformerNames);
582     }
583 
584     /**
585      * Invoked after deserialization. This is called when the marker interface
586      * {@link org.mule.util.store.DeserializationPostInitialisable} is used. This will get invoked
587      * after the object has been deserialized passing in the current MuleContext when using either
588      * {@link org.mule.transformer.wire.SerializationWireFormat},
589      * {@link org.mule.transformer.wire.SerializedMuleMessageWireFormat} or the
590      * {@link org.mule.transformer.simple.ByteArrayToSerializable} transformer.
591      *
592      * @param muleContext the current muleContext instance
593      * @throws MuleException if there is an error initializing
594      */
595     @SuppressWarnings({"unused", "unchecked"})
596     private void initAfterDeserialisation(MuleContext muleContext) throws MuleException
597     {
598         if (session instanceof DefaultMuleSession)
599         {
600             ((DefaultMuleSession) session).initAfterDeserialisation(muleContext);
601         }
602         if (message instanceof DefaultMuleMessage)
603         {
604             ((DefaultMuleMessage) message).initAfterDeserialisation(muleContext);
605         }
606         int endpointHashcode = (Integer) serializedData.get("endpointHashcode");
607         boolean isInboundEndpoint = (Boolean) serializedData.get("isInboundEndpoint");
608         String endpointBuilderName = (String) serializedData.get("endpointBuilderName");
609         String endpointUri = (String) serializedData.get("endpointUri");
610         List<String> transformerNames = (List<String>) serializedData.get("transformers");
611 
612         // 1) First attempt to get same endpoint instance from registry using
613         // hashcode, this will work if registry hasn't been disposed.
614         endpoint = (ImmutableEndpoint) muleContext.getRegistry().lookupObject(
615                 DefaultEndpointFactory.ENDPOINT_REGISTRY_PREFIX + endpointHashcode);
616 
617         // Registry has been disposed so we need to recreate endpoint
618         if (endpoint == null)
619         {
620             // 2) If endpoint references it's builder and this is available then use
621             // the builder to recreate the endpoint
622             if ((endpointBuilderName != null)
623                     && muleContext.getRegistry().lookupEndpointBuilder(endpointBuilderName) != null)
624             {
625                 if (isInboundEndpoint)
626                 {
627                     endpoint = muleContext.getEndpointFactory().getInboundEndpoint(
628                             endpointBuilderName);
629                 }
630                 else
631                 {
632                     endpoint = muleContext.getEndpointFactory().getOutboundEndpoint(
633                             endpointBuilderName);
634                 }
635             }
636             // 3) Otherwise recreate using endpoint uri string and transformers. (As in 1.4)
637             else
638             {
639                 List<Transformer> transformers = new LinkedList<Transformer>();
640                 for (String name : transformerNames)
641                 {
642                     Transformer next = muleContext.getRegistry().lookupTransformer(name);
643                     if (next == null)
644                     {
645                         throw new IllegalStateException(CoreMessages.objectNotFound(name).toString());
646                     }
647                     else
648                     {
649                         transformers.add(next);
650                     }
651                 }
652                 EndpointURI uri = new MuleEndpointURI(endpointUri, muleContext);
653 
654                 TransportServiceDescriptor tsd = (TransportServiceDescriptor) muleContext.getRegistry().lookupServiceDescriptor(ServiceType.TRANSPORT, uri.getFullScheme(), null);
655                 EndpointBuilder endpointBuilder = tsd.createEndpointBuilder(endpointUri);
656                 endpointBuilder.setTransformers(transformers);
657 
658                 if (isInboundEndpoint)
659                 {
660                     endpoint = muleContext.getEndpointFactory().getInboundEndpoint(
661                             endpointBuilder);
662                 }
663                 else
664                 {
665                     endpoint = muleContext.getEndpointFactory().getOutboundEndpoint(
666                             endpointBuilder);
667                 }
668             }
669         }
670 
671         serializedData = null;
672     }
673 
674     /**
675      * Gets the encoding for this message. First it looks to see if encoding has been
676      * set on the endpoint, if not it will check the message itself and finally it
677      * will fall back to the Mule global configuration for encoding which cannot be
678      * null.
679      *
680      * @return the encoding for the event
681      */
682     public String getEncoding()
683     {
684         String encoding = message.getEncoding();
685         if (encoding == null)
686         {
687             encoding = endpoint.getEncoding();
688         }
689 
690         return encoding;
691     }
692 
693     public MuleContext getMuleContext()
694     {
695         return message.getMuleContext();
696     }
697 
698     public ThreadSafeAccess newThreadCopy()
699     {
700         if (message instanceof ThreadSafeAccess)
701         {
702             DefaultMuleEvent copy = new DefaultMuleEvent((MuleMessage) ((ThreadSafeAccess) message).newThreadCopy(), this);
703             copy.resetAccessControl();
704             return copy;
705         }
706         else
707         {
708             return this;
709         }
710     }
711 
712     public void resetAccessControl()
713     {
714         if (message instanceof ThreadSafeAccess)
715         {
716             ((ThreadSafeAccess) message).resetAccessControl();
717         }
718     }
719 
720     public void assertAccess(boolean write)
721     {
722         if (message instanceof ThreadSafeAccess)
723         {
724             ((ThreadSafeAccess) message).assertAccess(write);
725         }
726     }
727 
728     @Deprecated
729     public Object transformMessage() throws TransformerException
730     {
731         logger.warn("Deprecation warning: MuleEvent.transformMessage does nothing in Mule 3.x.  The message is already transformed before the event reaches a component");
732         return message.getPayload();
733     }
734 
735     public ProcessingTime getProcessingTime()
736     {
737         return processingTime;
738     }
739 
740 }