View Javadoc

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