1
2
3
4
5
6
7
8
9
10 package org.mule.routing;
11
12 import org.mule.api.MuleContext;
13 import org.mule.api.MuleEvent;
14 import org.mule.api.MuleMessage;
15 import org.mule.api.routing.MessageInfoMapping;
16 import org.mule.api.routing.ResponseTimeoutException;
17 import org.mule.api.routing.RoutingException;
18 import org.mule.config.i18n.CoreMessages;
19 import org.mule.context.notification.RoutingNotification;
20 import org.mule.routing.inbound.EventGroup;
21 import org.mule.util.MapUtils;
22 import org.mule.util.concurrent.Latch;
23
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29
30 import javax.resource.spi.work.Work;
31 import javax.resource.spi.work.WorkException;
32
33 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
34 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentMap;
35 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
36 import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
37
38 import org.apache.commons.collections.buffer.BoundedFifoBuffer;
39 import org.apache.commons.logging.Log;
40 import org.apache.commons.logging.LogFactory;
41
42
43
44
45 public class EventCorrelator
46 {
47
48
49
50 protected transient final Log logger = LogFactory.getLog(EventCorrelator.class);
51
52 public static final String NO_CORRELATION_ID = "no-id";
53
54 public static final int MAX_PROCESSED_GROUPS = 50000;
55
56
57
58
59
60 protected final ConcurrentMap eventGroups = new ConcurrentHashMap();
61
62
63
64
65 protected final ConcurrentMap locks = new ConcurrentHashMap();
66
67
68
69
70
71 protected final ConcurrentMap responseMessages = new ConcurrentHashMap();
72
73 protected final BoundedFifoBuffer processedGroups = new BoundedFifoBuffer(MAX_PROCESSED_GROUPS);
74
75 private int timeout = -1;
76
77 private boolean failOnTimeout = true;
78
79 private MessageInfoMapping messageInfoMapping;
80
81 private MuleContext context;
82
83 private EventCorrelatorCallback callback;
84
85 private AtomicBoolean timerStarted = new AtomicBoolean(false);
86
87
88 public EventCorrelator(EventCorrelatorCallback callback, MessageInfoMapping messageInfoMapping, MuleContext context)
89 {
90 if (callback == null)
91 {
92 throw new IllegalArgumentException(CoreMessages.objectIsNull("EventCorrelatorCallback").getMessage());
93 }
94 if (messageInfoMapping == null)
95 {
96 throw new IllegalArgumentException(CoreMessages.objectIsNull("MessageInfoMapping").getMessage());
97 }
98 if (context == null)
99 {
100 throw new IllegalArgumentException(CoreMessages.objectIsNull("MuleContext").getMessage());
101 }
102 this.callback = callback;
103 this.messageInfoMapping = messageInfoMapping;
104 this.context = context;
105
106
107 }
108
109 public void enableTimeoutMonitor() throws WorkException
110 {
111 if (!timerStarted.get())
112 {
113
114 this.context.getWorkManager().scheduleWork(new Work()
115 {
116 public void release()
117 {
118
119 }
120
121
122 public void run()
123 {
124 while (true)
125 {
126 List expired = new ArrayList(1);
127 for (Iterator iterator = eventGroups.values().iterator(); iterator.hasNext();)
128 {
129 EventGroup group = (EventGroup) iterator.next();
130 if ((group.getCreated() + getTimeout()) < System.currentTimeMillis())
131 {
132 expired.add(group);
133 }
134 }
135 if (expired.size() > 0)
136 {
137 for (Iterator iterator = expired.iterator(); iterator.hasNext();)
138 {
139 EventGroup group = (EventGroup) iterator.next();
140 eventGroups.remove(group.getGroupId());
141 locks.remove(group.getGroupId());
142
143 context.fireNotification(new RoutingNotification(group.toMessageCollection(), null,
144 RoutingNotification.CORRELATION_TIMEOUT));
145
146
147
148 group.toArray()[0].getService().getExceptionListener().exceptionThrown(
149 new CorrelationTimeoutException(CoreMessages.correlationTimedOut(group.getGroupId()),
150 group.toMessageCollection()));
151
152
153
154
155
156 }
157 }
158 try
159 {
160 Thread.sleep(100);
161 }
162 catch (InterruptedException e)
163 {
164 break;
165 }
166 }
167 }
168 }
169
170 );
171 }
172 }
173
174
175
176
177
178 public Map getResponseMessages()
179 {
180 return Collections.unmodifiableMap(responseMessages);
181 }
182
183 public MuleMessage process(MuleEvent event) throws RoutingException
184 {
185 addEvent(event);
186 Object correlationId = messageInfoMapping.getCorrelationId(event.getMessage());
187 if (locks.get(correlationId) != null)
188 {
189 locks.remove(correlationId);
190 return (MuleMessage) responseMessages.remove(correlationId);
191 }
192 else
193 {
194 return null;
195 }
196 }
197
198 public void addEvent(MuleEvent event) throws RoutingException
199 {
200
201 final Object groupId = messageInfoMapping.getCorrelationId(event.getMessage());
202
203 if (groupId == null || groupId.equals("-1"))
204 {
205 throw new RoutingException(CoreMessages.noCorrelationId(), event.getMessage(), event
206 .getEndpoint());
207 }
208
209
210 boolean lookupMiss = false;
211
212
213 while (true)
214 {
215 if (lookupMiss)
216 {
217 try
218 {
219
220 Thread.sleep(1);
221 }
222 catch (InterruptedException interrupted)
223 {
224 Thread.currentThread().interrupt();
225 }
226 }
227
228 if (isGroupAlreadyProcessed(groupId))
229 {
230 if (logger.isDebugEnabled())
231 {
232 logger.debug("An event was received for an event group that has already been processed, " +
233 "this is probably because the async-reply timed out. Correlation Id is: " + groupId +
234 ". Dropping event");
235 }
236
237 context.fireNotification(new RoutingNotification(event.getMessage(),
238 event.getEndpoint().getEndpointURI().toString(),
239 RoutingNotification.MISSED_ASYNC_REPLY));
240 return;
241 }
242
243 EventGroup group = this.getEventGroup(groupId);
244
245
246 if (group == null)
247 {
248
249 group = this.addEventGroup(callback.createEventGroup(event, groupId));
250 }
251
252
253 synchronized (group)
254 {
255
256 if (group != this.getEventGroup(groupId))
257 {
258
259 lookupMiss = true;
260 continue;
261 }
262
263 if (logger.isDebugEnabled())
264 {
265 logger.debug("Adding event to response aggregator group: " + groupId);
266 }
267
268
269 group.addEvent(event);
270
271
272 if (callback.shouldAggregateEvents(group))
273 {
274
275 MuleMessage returnMessage = callback.aggregateEvents(group);
276
277
278
279 this.removeEventGroup(group);
280
281
282
283 MuleMessage previousResult = (MuleMessage) responseMessages.putIfAbsent(groupId,
284 returnMessage);
285 if (previousResult != null)
286 {
287
288
289
290 throw new IllegalStateException(
291 "Detected duplicate aggregation result message with id: " + groupId);
292 }
293
294
295
296
297 Latch l = (Latch) locks.get(groupId);
298 if (l == null)
299 {
300 if (logger.isDebugEnabled())
301 {
302 logger.debug("Creating latch for " + groupId + " in " + this);
303 }
304
305 l = new Latch();
306 Latch previous = (Latch) locks.putIfAbsent(groupId, l);
307 if (previous != null)
308 {
309 l = previous;
310 }
311 }
312
313 l.countDown();
314 }
315
316
317 break;
318 }
319 }
320 }
321
322
323
324
325 protected EventGroup getEventGroup(Object groupId)
326 {
327 return (EventGroup) eventGroups.get(groupId);
328 }
329
330
331
332
333 protected EventGroup addEventGroup(EventGroup group)
334 {
335 EventGroup previous = (EventGroup) eventGroups.putIfAbsent(group.getGroupId(), group);
336
337
338 return (previous != null ? previous : group);
339 }
340
341
342
343
344 protected void removeEventGroup(EventGroup group)
345 {
346 eventGroups.remove(group.getGroupId());
347 addProcessedGroup(group.getGroupId());
348 }
349
350 protected void addProcessedGroup(Object id)
351 {
352 if (processedGroups.isFull())
353 {
354 processedGroups.remove();
355 }
356 processedGroups.add(id);
357 }
358
359 protected boolean isGroupAlreadyProcessed(Object id)
360 {
361 return processedGroups.contains(id);
362 }
363
364
365
366
367
368
369
370
371
372 public MuleMessage getResponse(MuleMessage message) throws RoutingException
373 {
374 return getResponse(message, getTimeout());
375 }
376
377
378
379
380
381
382
383
384
385 public MuleMessage getResponse(MuleMessage message, int timeout) throws RoutingException
386 {
387 Object responseId = messageInfoMapping.getMessageId(message);
388
389 if (logger.isDebugEnabled())
390 {
391 logger.debug("Waiting for response for message id: " + responseId + " in " + this);
392 }
393
394 Latch l = (Latch) locks.get(responseId);
395 if (l == null)
396 {
397 if (logger.isDebugEnabled())
398 {
399 logger.debug("Got response but no one is waiting for it yet. Creating latch for "
400 + responseId + " in " + this);
401 }
402
403 l = new Latch();
404 Latch previous = (Latch) locks.putIfAbsent(responseId, l);
405 if (previous != null)
406 {
407 l = previous;
408 }
409 }
410
411 if (logger.isDebugEnabled())
412 {
413 logger.debug("Got latch for message: " + responseId);
414 }
415
416
417 MuleMessage result;
418
419
420
421 boolean resultAvailable = false;
422
423
424
425 boolean interruptedWhileWaiting = false;
426
427 try
428 {
429 if (logger.isDebugEnabled())
430 {
431 logger.debug("Waiting for response to message: " + responseId);
432 }
433
434
435 if (this.getTimeout() <= 0)
436 {
437 l.await();
438 resultAvailable = true;
439 }
440 else
441 {
442 resultAvailable = l.await(timeout, TimeUnit.MILLISECONDS);
443 }
444 }
445 catch (InterruptedException e)
446 {
447 interruptedWhileWaiting = true;
448 }
449 finally
450 {
451 locks.remove(responseId);
452 result = (MuleMessage) responseMessages.remove(responseId);
453
454 if (interruptedWhileWaiting)
455 {
456 Thread.currentThread().interrupt();
457 }
458 }
459
460 if (!resultAvailable)
461 {
462 if (isFailOnTimeout())
463 {
464 if (logger.isTraceEnabled())
465 {
466 logger.trace("Current responses are: \n" + MapUtils.toString(responseMessages, true));
467 }
468 context.fireNotification(new RoutingNotification(message, null,
469 RoutingNotification.ASYNC_REPLY_TIMEOUT));
470
471 throw new ResponseTimeoutException(
472 CoreMessages.responseTimedOutWaitingForId(
473 this.getTimeout(), responseId), message, null);
474 }
475 else
476 {
477 EventGroup group = this.getEventGroup(responseId);
478 if (group == null)
479 {
480
481 if (logger.isTraceEnabled())
482 {
483 logger.trace("There is no current event Group. Current responses are: \n" + MapUtils.toString(responseMessages, true));
484 }
485 return null;
486 }
487 else
488 {
489 this.removeEventGroup(group);
490
491 MuleMessage msg = callback.aggregateEvents(group);
492 return msg;
493 }
494 }
495 }
496
497 if (result == null)
498 {
499
500 throw new IllegalStateException("Response Message is null");
501 }
502
503 if (logger.isDebugEnabled())
504 {
505 logger.debug("remaining locks : " + locks.keySet());
506 logger.debug("remaining results: " + responseMessages.keySet());
507 }
508
509 return result;
510 }
511
512
513 public boolean isFailOnTimeout()
514 {
515 return failOnTimeout;
516 }
517
518 public void setFailOnTimeout(boolean failOnTimeout)
519 {
520 this.failOnTimeout = failOnTimeout;
521 }
522
523 public int getTimeout()
524 {
525 return timeout;
526 }
527
528 public void setTimeout(int timeout)
529 {
530 this.timeout = timeout;
531 }
532 }