View Javadoc

1   /*
2    * $Id: EventGroup.java 22751 2011-08-26 00:42:38Z 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.routing;
12  
13  import org.mule.DefaultMessageCollection;
14  import org.mule.DefaultMuleEvent;
15  import org.mule.api.MuleContext;
16  import org.mule.api.MuleEvent;
17  import org.mule.api.MuleException;
18  import org.mule.api.MuleMessageCollection;
19  import org.mule.api.config.MuleProperties;
20  import org.mule.api.store.ListableObjectStore;
21  import org.mule.api.store.ObjectStoreException;
22  import org.mule.api.store.ObjectStoreManager;
23  import org.mule.util.ClassUtils;
24  import org.mule.util.MuleLogger;
25  import org.mule.util.store.DeserializationPostInitialisable;
26  
27  import java.io.Serializable;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  import org.apache.commons.collections.IteratorUtils;
32  
33  /**
34   * <code>EventGroup</code> is a holder over events grouped by a common group Id. This
35   * can be used by components such as routers to managed related events.
36   */
37  // @ThreadSafe
38  public class EventGroup implements Comparable<EventGroup>, Serializable, DeserializationPostInitialisable
39  {
40      /**
41       * Serial version
42       */
43      private static final long serialVersionUID = 953739659615692697L;
44  
45      public static final MuleEvent[] EMPTY_EVENTS_ARRAY = new MuleEvent[0];
46  
47      transient private ObjectStoreManager objectStoreManager = null;
48  
49      private final Object groupId;
50      transient ListableObjectStore<MuleEvent> events;
51      private final long created;
52      private final int expectedSize;
53      transient private MuleContext muleContext;
54      private final String storePrefix;
55      private String commonRootId = null;
56      private static boolean hasNoCommonRootId = false;
57  
58      public static final String DEFAULT_STORE_PREFIX = "DEFAULT_STORE";
59  
60      public EventGroup(Object groupId, MuleContext muleContext)
61      {
62          this(groupId, muleContext, -1, false, DEFAULT_STORE_PREFIX);
63      }
64  
65      public EventGroup(Object groupId,
66                        MuleContext muleContext,
67                        int expectedSize,
68                        boolean storeIsPersistent,
69                        String storePrefix)
70      {
71          super();
72          this.created = System.nanoTime();
73          this.muleContext = muleContext;
74          this.storePrefix = storePrefix;
75  
76          String storeKey = storePrefix + ".eventGroup." + groupId;
77          this.events = getObjectStoreManager().getObjectStore(storeKey, storeIsPersistent);
78  
79          this.expectedSize = expectedSize;
80          this.groupId = groupId;
81      }
82  
83      /**
84       * Compare this EventGroup to another one. If the receiver and the argument both
85       * have groupIds that are {@link Comparable}, they are used for the comparison;
86       * otherwise - since the id can be any object - the group creation time stamp is
87       * used as fallback. Older groups are considered "smaller".
88       *
89       * @see java.lang.Comparable#compareTo(java.lang.Object)
90       */
91      @Override
92      @SuppressWarnings("unchecked")
93      public int compareTo(EventGroup other)
94      {
95          Object otherId = other.getGroupId();
96  
97          if (groupId instanceof Comparable<?> && otherId instanceof Comparable<?>)
98          {
99              return ((Comparable<Object>) groupId).compareTo(otherId);
100         }
101         else
102         {
103             long diff = created - other.getCreated();
104             return (diff > 0 ? 1 : (diff < 0 ? -1 : 0));
105         }
106     }
107 
108     /**
109      * Compares two EventGroups for equality. EventGroups are considered equal if
110      * their groupIds (as returned by {@link #getGroupId()}) are equal.
111      *
112      * @see java.lang.Object#equals(Object)
113      */
114     @Override
115     public boolean equals(Object obj)
116     {
117         if (this == obj)
118         {
119             return true;
120         }
121 
122         if (!(obj instanceof EventGroup))
123         {
124             return false;
125         }
126 
127         final EventGroup other = (EventGroup) obj;
128         if (groupId == null)
129         {
130             return (other.groupId == null);
131         }
132 
133         return groupId.equals(other.groupId);
134     }
135 
136     /**
137      * The hashCode of an EventGroup is derived from the object returned by
138      * {@link #getGroupId()}.
139      *
140      * @see java.lang.Object#hashCode()
141      */
142     @Override
143     public int hashCode()
144     {
145         return groupId.hashCode();
146     }
147 
148     /**
149      * Returns an identifier for this EventGroup. It is recommended that this id is
150      * unique and {@link Comparable} e.g. a UUID.
151      *
152      * @return the id of this event group
153      */
154     public Object getGroupId()
155     {
156         return groupId;
157     }
158 
159     /**
160      * Returns an iterator over a snapshot copy of this group's collected events. If
161      * you need to iterate over the group and e.g. remove select events, do so via
162      * {@link #removeEvent(MuleEvent)}. If you need to do so atomically in order to
163      * prevent e.g. concurrent reception/aggregation of the group during iteration,
164      * wrap the iteration in a synchronized block on the group instance.
165      *
166      * @return an iterator over collected {@link MuleEvent}s.
167      * @throws ObjectStoreException
168      */
169     @SuppressWarnings("unchecked")
170     public Iterator<MuleEvent> iterator() throws ObjectStoreException
171     {
172         synchronized (events)
173         {
174             if (events.allKeys().isEmpty())
175             {
176                 return IteratorUtils.emptyIterator();
177             }
178             else
179             {
180                 return IteratorUtils.arrayIterator(this.toArray());
181             }
182         }
183     }
184 
185     /**
186      * Returns a snapshot of collected events in this group.
187      *
188      * @return an array of collected {@link MuleEvent}s.
189      * @throws ObjectStoreException
190      */
191     public MuleEvent[] toArray() throws ObjectStoreException
192     {
193         synchronized (events)
194         {
195             if (events.allKeys().isEmpty())
196             {
197                 return EMPTY_EVENTS_ARRAY;
198             }
199             List<Serializable> keys = events.allKeys();
200             MuleEvent[] eventArray = new MuleEvent[keys.size()];
201             for (int i = 0; i < keys.size(); i++)
202             {
203                 eventArray[i] = events.retrieve(keys.get(i));
204             }
205             return eventArray;
206         }
207     }
208 
209     /**
210      * Add the given event to this group.
211      *
212      * @param event the event to add
213      * @throws ObjectStoreException
214      */
215     public void addEvent(MuleEvent event) throws ObjectStoreException
216     {
217         synchronized (events)
218         {
219             //Using both event ID and CorrelationSequence since in certain instances
220             //when an event is split up, the same event IDs are used.
221             Serializable key=event.getId()+event.getMessage().getCorrelationSequence();            
222             events.store(key, event);
223 
224             if (!hasNoCommonRootId)
225             {
226                 String rootId = event.getMessage().getMessageRootId();
227                 if (commonRootId == null)
228                 {
229                     commonRootId = rootId;
230                 }
231                 else if (!commonRootId.equals(rootId))
232                 {
233                     hasNoCommonRootId = true;
234                     commonRootId = null;
235                 }
236             }
237         }
238     }
239 
240 
241     /**
242      * Remove the given event from the group.
243      *
244      * @param event the evnt to remove
245      * @throws ObjectStoreException
246      */
247     public void removeEvent(MuleEvent event) throws ObjectStoreException
248     {
249         synchronized (events)
250         {
251             events.remove(event.getId());
252         }
253     }
254 
255     /**
256      * Return the creation timestamp of the current group in nanoseconds.
257      *
258      * @return the timestamp when this group was instantiated.
259      */
260     public long getCreated()
261     {
262         return created;
263     }
264 
265     /**
266      * Returns the number of events collected so far.
267      *
268      * @return number of events in this group or 0 if the group is empty.
269      */
270     public int size()
271     {
272         synchronized (events)
273         {
274             try
275             {
276                 return events.allKeys().size();
277             }
278             catch (ObjectStoreException e)
279             {
280                 // TODO Check if this is ok.
281                 return -1;
282             }
283         }
284     }
285 
286     /**
287      * Returns the number of events that this EventGroup is expecting before
288      * correlation can proceed.
289      *
290      * @return expected number of events or -1 if no expected size was specified.
291      */
292     public int expectedSize()
293     {
294         return expectedSize;
295     }
296 
297     /**
298      * Removes all events from this group.
299      *
300      * @throws ObjectStoreException
301      */
302     public void clear() throws ObjectStoreException
303     {
304         getObjectStoreManager().disposeStore(events);
305     }
306 
307     @Override
308     public String toString()
309     {
310         StringBuffer buf = new StringBuffer(80);
311         buf.append(ClassUtils.getSimpleName(this.getClass()));
312         buf.append(" {");
313         buf.append("id=").append(groupId);
314         buf.append(", expected size=").append(expectedSize);
315 
316         try
317         {
318             synchronized (events)
319             {
320                 int currentSize;
321 
322                 currentSize = events.allKeys().size();
323 
324                 buf.append(", current events=").append(currentSize);
325 
326                 if (currentSize > 0)
327                 {
328                     buf.append(" [");
329                     Iterator<Serializable> i = events.allKeys().iterator();
330                     while (i.hasNext())
331                     {
332                         Serializable id = i.next();
333                         buf.append(events.retrieve(id).getMessage().getUniqueId());
334                         if (i.hasNext())
335                         {
336                             buf.append(", ");
337                         }
338                     }
339                     buf.append(']');
340                 }
341             }
342         }
343         catch (ObjectStoreException e)
344         {
345             buf.append("ObjectStoreException " + e + " caught:" + e.getMessage());
346         }
347 
348         buf.append('}');
349 
350         return buf.toString();
351     }
352 
353     public MuleMessageCollection toMessageCollection() throws ObjectStoreException
354     {
355         MuleMessageCollection col;
356         synchronized (events)
357         {
358             if (events.allKeys().isEmpty())
359             {
360                 col = new DefaultMessageCollection(null);
361             }
362             col = new DefaultMessageCollection(muleContext);
363 
364             for (Serializable id : events.allKeys())
365             {
366                 col.addMessage(events.retrieve(id).getMessage());
367             }
368         }
369         return col;
370     }
371 
372     public String getCommonRootId()
373     {
374         return commonRootId;
375     }
376 
377     public MuleEvent getMessageCollectionEvent()
378     {
379         try
380         {
381             if (size() > 0)
382             {
383 
384                 DefaultMuleEvent muleEvent = new DefaultMuleEvent(toMessageCollection(),
385                     events.retrieve(events.allKeys().get(0)));
386                 if (getCommonRootId() != null)
387                 {
388                     muleEvent.getMessage().setMessageRootId(commonRootId);
389                 }
390                 return muleEvent;
391             }
392             else
393             {
394                 return null;
395             }
396         }
397         catch (ObjectStoreException e)
398         {
399             // Nothing to do...
400             return null;
401         }
402     }
403 
404     private ObjectStoreManager getObjectStoreManager()
405     {
406         if (objectStoreManager == null)
407         {
408             objectStoreManager = (ObjectStoreManager) muleContext.getRegistry().get(
409                 MuleProperties.OBJECT_STORE_MANAGER);
410         }
411         return objectStoreManager;
412     }
413 
414     public void initAfterDeserialisation(MuleContext context) throws MuleException
415     {
416         this.muleContext = context;
417 
418         String storeKey = storePrefix + ".eventGroup." + groupId;
419         this.events = getObjectStoreManager().getObjectStore(storeKey, true);
420     }
421 }