View Javadoc

1   /*
2    * $Id: AbstractEventAggregator.java 7963 2007-08-21 08:53:15Z 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.routing.inbound;
12  
13  import org.mule.impl.MuleEvent;
14  import org.mule.impl.endpoint.MuleEndpoint;
15  import org.mule.routing.AggregationException;
16  import org.mule.umo.MessagingException;
17  import org.mule.umo.UMOEvent;
18  import org.mule.umo.UMOMessage;
19  import org.mule.umo.endpoint.UMOEndpoint;
20  
21  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
22  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentMap;
23  
24  /**
25   * <code>AbstractEventAggregator</code> will aggregate a set of messages into a
26   * single message.
27   */
28  
29  public abstract class AbstractEventAggregator extends SelectiveConsumer
30  {
31      public static final String NO_CORRELATION_ID = "no-id";
32  
33      private final ConcurrentMap eventGroups = new ConcurrentHashMap();
34  
35      // @Override
36      public UMOEvent[] process(UMOEvent event) throws MessagingException
37      {
38          UMOEvent[] result = null;
39  
40          if (this.isMatch(event))
41          {
42              // indicates interleaved EventGroup removal (very rare)
43              boolean miss = false;
44  
45              // match event to its group
46              final Object groupId = this.getEventGroupIdForEvent(event);
47  
48              // spinloop for the EventGroup lookup
49              while (true)
50              {
51                  if (miss)
52                  {
53                      try
54                      {
55                          // recommended over Thread.yield()
56                          Thread.sleep(1);
57                      }
58                      catch (InterruptedException interrupted)
59                      {
60                          Thread.currentThread().interrupt();
61                      }
62                  }
63  
64                  // check for an existing group first
65                  EventGroup group = this.getEventGroup(groupId);
66  
67                  // does the group exist?
68                  if (group == null)
69                  {
70                      // ..apparently not, so create a new one & add it
71                      group = this.addEventGroup(this.createEventGroup(event, groupId));
72                  }
73  
74                  // ensure that only one thread at a time evaluates this EventGroup
75                  synchronized (group)
76                  {
77                      // make sure no other thread removed the group in the meantime
78                      if (group != this.getEventGroup(groupId))
79                      {
80                          // if that is the (rare) case, spin
81                          miss = true;
82                          continue;
83                      }
84  
85                      // add the incoming event to the group
86                      group.addEvent(event);
87  
88                      if (this.shouldAggregateEvents(group))
89                      {
90                          UMOMessage returnMessage = this.aggregateEvents(group);
91                          UMOEndpoint endpoint = new MuleEndpoint(event.getEndpoint());
92                          endpoint.setTransformer(null);
93                          endpoint.setName(this.getClass().getName());
94                          UMOEvent returnEvent = new MuleEvent(returnMessage, endpoint, event.getComponent(),
95                              event);
96                          result = new UMOEvent[]{returnEvent};
97                          this.removeEventGroup(group);
98                      }
99  
100                     // result or not: exit spinloop
101                     break;
102                 }
103             }
104         }
105 
106         return result;
107     }
108 
109     /**
110      * Create a new EventGroup with the specified groupId.
111      * 
112      * @param event the event that caused creation of this group; can be used for
113      *            additional information
114      * @param groupId the id to use for the new EventGroup
115      * @return a new EventGroup
116      */
117     protected EventGroup createEventGroup(UMOEvent event, Object groupId)
118     {
119         return new EventGroup(groupId);
120     }
121 
122     /**
123      * Returns the identifier by which events will be correlated. By default this is
124      * the value as returned by {@link org.mule.umo.provider.UMOMessageAdapter#getCorrelationId()}.
125      * 
126      * @param event the event use for determining the correlation group id
127      * @return the id used to correlate related events
128      */
129     protected Object getEventGroupIdForEvent(UMOEvent event)
130     {
131         String groupId = event.getMessage().getCorrelationId();
132 
133         if (groupId == null)
134         {
135             groupId = NO_CORRELATION_ID;
136         }
137 
138         return groupId;
139     }
140 
141     /**
142      * Look up the existing EventGroup with the given id.
143      * 
144      * @param groupId
145      * @return the EventGroup with the given id or <code>null</code> if the group
146      *         does not exist.
147      */
148     protected EventGroup getEventGroup(Object groupId)
149     {
150         return (EventGroup) eventGroups.get(groupId);
151     }
152 
153     /**
154      * Add the given EventGroup to this aggregator's "group store". Currently this is
155      * only a ConcurrentHashMap, and the group's id as returned by
156      * {@link EventGroup#getGroupId()} is used to match the group. Since group
157      * creation/lookup/storage can happen fully concurrent, we return the stored
158      * group. Callers are required to switch their method-local references when a
159      * different group is returned.
160      * 
161      * @param group the EventGroup to "store"
162      * @return the stored EventGroup (may be different from the one passed as
163      *         argument)
164      */
165     protected EventGroup addEventGroup(EventGroup group)
166     {
167         // a parallel thread might have removed the EventGroup already,
168         // therefore we need to validate our current reference
169         EventGroup previous = (EventGroup) eventGroups.putIfAbsent(group.getGroupId(), group);
170         return (previous != null ? previous : group);
171     }
172 
173     /**
174      * Remove the group from this aggregator's "store". The group's id as returned by
175      * {@link EventGroup#getGroupId()} is used to match the group.
176      * 
177      * @param group the EventGroup to remove
178      */
179     protected void removeEventGroup(EventGroup group)
180     {
181         eventGroups.remove(group.getGroupId());
182     }
183 
184     /**
185      * Determines if the event group is ready to be aggregated. if the group is ready
186      * to be aggregated (this is entirely up to the application. it could be
187      * determined by volume, last modified time or some oher criteria based on the
188      * last event received).
189      * 
190      * @param events
191      * @return true if the group is ready for aggregation
192      */
193     protected abstract boolean shouldAggregateEvents(EventGroup events);
194 
195     /**
196      * This method is invoked if the shouldAggregate method is called and returns
197      * true. Once this method returns an aggregated message, the event group is
198      * removed from the router.
199      * 
200      * @param events the event group for this request
201      * @return an aggregated message
202      * @throws AggregationException if the aggregation fails. in this scenario the
203      *             whole event group is removed and passed to the exception handler
204      *             for this componenet
205      */
206     protected abstract UMOMessage aggregateEvents(EventGroup events) throws AggregationException;
207 
208 }