1 /*
2 * $Id: AbstractEventAggregator.java 7976 2007-08-21 14:26:13Z 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 }