1
2
3
4
5
6
7
8
9
10
11 package org.mule.routing.correlation;
12
13 import org.mule.api.MessagingException;
14 import org.mule.api.MuleContext;
15 import org.mule.api.MuleEvent;
16 import org.mule.api.MuleException;
17 import org.mule.api.MuleMessageCollection;
18 import org.mule.api.config.MuleProperties;
19 import org.mule.api.construct.FlowConstruct;
20 import org.mule.api.lifecycle.Startable;
21 import org.mule.api.lifecycle.Stoppable;
22 import org.mule.api.processor.MessageProcessor;
23 import org.mule.api.routing.MessageInfoMapping;
24 import org.mule.api.routing.RoutingException;
25 import org.mule.api.service.Service;
26 import org.mule.api.store.ListableObjectStore;
27 import org.mule.api.store.ObjectAlreadyExistsException;
28 import org.mule.api.store.ObjectDoesNotExistException;
29 import org.mule.api.store.ObjectStore;
30 import org.mule.api.store.ObjectStoreException;
31 import org.mule.api.store.ObjectStoreManager;
32 import org.mule.config.i18n.CoreMessages;
33 import org.mule.context.notification.RoutingNotification;
34 import org.mule.routing.EventGroup;
35 import org.mule.routing.EventProcessingThread;
36 import org.mule.util.StringMessageUtils;
37 import org.mule.util.concurrent.ThreadNameHelper;
38 import org.mule.util.monitor.Expirable;
39 import org.mule.util.monitor.ExpiryMonitor;
40
41 import java.io.Serializable;
42 import java.text.MessageFormat;
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.concurrent.TimeUnit;
46
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
49
50
51
52 public class EventCorrelator implements Startable, Stoppable
53 {
54
55
56
57 protected transient final Log logger = LogFactory.getLog(EventCorrelator.class);
58
59 public static final String NO_CORRELATION_ID = "no-id";
60
61 public static final int MAX_PROCESSED_GROUPS = 50000;
62
63 protected static final long MILLI_TO_NANO_MULTIPLIER = 1000000L;
64
65 private static final long ONE_DAY_IN_MILLI = 1000 * 60 * 60 * 24;
66
67 protected long groupTimeToLive = ONE_DAY_IN_MILLI;
68
69
70
71
72
73
74 protected ListableObjectStore<EventGroup> eventGroups;
75
76 protected final Object groupsLock = new Object();
77
78
79 protected ObjectStore<Long> processedGroups = null;
80
81 private long timeout = -1;
82
83 private boolean failOnTimeout = true;
84
85 private MessageInfoMapping messageInfoMapping;
86
87 private MuleContext muleContext;
88
89 private EventCorrelatorCallback callback;
90
91 private MessageProcessor timeoutMessageProcessor;
92
93
94
95
96 private ListableObjectStore<Long> expiredAndDispatchedGroups = null;
97
98 private EventCorrelator.ExpiringGroupMonitoringThread expiringGroupMonitoringThread;
99 private final String name;
100
101 private final boolean persistentStores;
102 private final String storePrefix;
103
104 public EventCorrelator(EventCorrelatorCallback callback,
105 MessageProcessor timeoutMessageProcessor,
106 MessageInfoMapping messageInfoMapping,
107 MuleContext muleContext,
108 String flowConstructName,
109 boolean persistentStores,
110 String storePrefix)
111 {
112 if (callback == null)
113 {
114 throw new IllegalArgumentException(CoreMessages.objectIsNull("EventCorrelatorCallback")
115 .getMessage());
116 }
117 if (messageInfoMapping == null)
118 {
119 throw new IllegalArgumentException(CoreMessages.objectIsNull("MessageInfoMapping").getMessage());
120 }
121 if (muleContext == null)
122 {
123 throw new IllegalArgumentException(CoreMessages.objectIsNull("MuleContext").getMessage());
124 }
125 this.callback = callback;
126 this.messageInfoMapping = messageInfoMapping;
127 this.muleContext = muleContext;
128 this.timeoutMessageProcessor = timeoutMessageProcessor;
129 this.persistentStores = persistentStores;
130 this.storePrefix = storePrefix;
131 name = String.format("%s%s.event.correlator", ThreadNameHelper.getPrefix(muleContext),
132 flowConstructName);
133 ObjectStoreManager objectStoreManager = muleContext.getRegistry().get(
134 MuleProperties.OBJECT_STORE_MANAGER);
135 expiredAndDispatchedGroups = (ListableObjectStore<Long>) objectStoreManager.getObjectStore(
136 storePrefix + ".expiredAndDispatchedGroups", persistentStores);
137 processedGroups = (ListableObjectStore<Long>) objectStoreManager.getObjectStore(storePrefix
138 + ".processedGroups",
139 persistentStores, MAX_PROCESSED_GROUPS, -1, 1000);
140 eventGroups = (ListableObjectStore<EventGroup>) objectStoreManager.getObjectStore(storePrefix
141 + ".eventGroups",
142 persistentStores);
143 }
144
145 public void forceGroupExpiry(String groupId) throws MessagingException
146 {
147 try
148 {
149 if (eventGroups.retrieve(groupId) != null)
150 {
151 handleGroupExpiry((EventGroup) eventGroups.retrieve(groupId));
152 }
153 else
154 {
155 addProcessedGroup(groupId);
156 }
157 }
158 catch (ObjectStoreException e)
159 {
160
161 throw new MessagingException(null, e);
162 }
163 }
164
165 public MuleEvent process(MuleEvent event) throws RoutingException
166 {
167
168 final String groupId = messageInfoMapping.getCorrelationId(event.getMessage());
169
170 if (logger.isTraceEnabled())
171 {
172 try
173 {
174 logger.trace(String.format("Received async reply message for correlationID: %s%n%s%n%s",
175 groupId, StringMessageUtils.truncate(
176 StringMessageUtils.toString(event.getMessage().getPayload()), 200, false),
177 StringMessageUtils.headersToString(event.getMessage())));
178 }
179 catch (Exception e)
180 {
181
182 }
183 }
184 if (groupId == null || groupId.equals("-1"))
185 {
186 throw new RoutingException(CoreMessages.noCorrelationId(), event, timeoutMessageProcessor);
187 }
188
189
190 boolean lookupMiss = false;
191
192
193 while (true)
194 {
195 if (lookupMiss)
196 {
197 try
198 {
199
200 Thread.sleep(1);
201 }
202 catch (InterruptedException interrupted)
203 {
204 Thread.currentThread().interrupt();
205 }
206 }
207
208 try
209 {
210 if (isGroupAlreadyProcessed(groupId))
211 {
212 if (logger.isDebugEnabled())
213 {
214 logger.debug("An event was received for an event group that has already been processed, "
215 + "this is probably because the async-reply timed out. Correlation Id is: "
216 + groupId + ". Dropping event");
217 }
218
219 muleContext.fireNotification(new RoutingNotification(event.getMessage(),
220 event.getMessageSourceURI().toString(),
221 RoutingNotification.MISSED_AGGREGATION_GROUP_EVENT));
222 return null;
223 }
224 }
225 catch (ObjectStoreException e)
226 {
227 throw new RoutingException(event, timeoutMessageProcessor, e);
228 }
229
230
231 EventGroup group;
232 try
233 {
234 group = this.getEventGroup(groupId);
235 }
236 catch (ObjectStoreException e)
237 {
238 throw new RoutingException(event, timeoutMessageProcessor, e);
239 }
240
241
242 if (group == null)
243 {
244
245 try
246 {
247 group = this.addEventGroup(callback.createEventGroup(event, groupId));
248 }
249 catch (ObjectStoreException e)
250 {
251 throw new RoutingException(event, timeoutMessageProcessor, e);
252 }
253 }
254
255
256 synchronized (groupsLock)
257 {
258 if (logger.isDebugEnabled())
259 {
260 logger.debug("Adding event to aggregator group: " + groupId);
261 }
262
263
264 try
265 {
266 group.addEvent(event);
267 }
268 catch (ObjectStoreException e)
269 {
270 throw new RoutingException(event, timeoutMessageProcessor, e);
271 }
272
273
274 if (callback.shouldAggregateEvents(group))
275 {
276
277 MuleEvent returnEvent = callback.aggregateEvents(group);
278 returnEvent.getMessage().setCorrelationId(groupId);
279 String rootId = group.getCommonRootId();
280 if (rootId != null)
281 {
282 returnEvent.getMessage().setMessageRootId(rootId);
283 }
284
285
286
287 try
288 {
289 this.removeEventGroup(group);
290 group.clear();
291 }
292 catch (ObjectStoreException e)
293 {
294 throw new RoutingException(event, timeoutMessageProcessor, e);
295 }
296
297 return returnEvent;
298 }
299 else
300 {
301 return null;
302 }
303 }
304 }
305 }
306
307 protected EventGroup getEventGroup(String groupId) throws ObjectStoreException
308 {
309 try
310 {
311 return (EventGroup) eventGroups.retrieve(groupId);
312 }
313 catch (ObjectDoesNotExistException e)
314 {
315 return null;
316 }
317 }
318
319 protected EventGroup addEventGroup(EventGroup group) throws ObjectStoreException
320 {
321 try
322 {
323 eventGroups.store((Serializable) group.getGroupId(), group);
324 return group;
325 }
326 catch (ObjectAlreadyExistsException e)
327 {
328 return (EventGroup) eventGroups.retrieve((Serializable) group.getGroupId());
329 }
330 }
331
332 protected void removeEventGroup(EventGroup group) throws ObjectStoreException
333 {
334 final Object groupId = group.getGroupId();
335 eventGroups.remove((Serializable) groupId);
336 addProcessedGroup(groupId);
337 }
338
339 protected void addProcessedGroup(Object id) throws ObjectStoreException
340 {
341 synchronized (groupsLock)
342 {
343 processedGroups.store((Serializable) id, System.nanoTime());
344 }
345 }
346
347 protected boolean isGroupAlreadyProcessed(Object id) throws ObjectStoreException
348 {
349 synchronized (groupsLock)
350 {
351 return processedGroups.contains((Serializable) id);
352 }
353 }
354
355 public boolean isFailOnTimeout()
356 {
357 return failOnTimeout;
358 }
359
360 public void setFailOnTimeout(boolean failOnTimeout)
361 {
362 this.failOnTimeout = failOnTimeout;
363 }
364
365 public long getTimeout()
366 {
367 return timeout;
368 }
369
370 public void setTimeout(long timeout)
371 {
372 this.timeout = timeout;
373 }
374
375 protected void handleGroupExpiry(EventGroup group) throws MessagingException
376 {
377 try
378 {
379 removeEventGroup(group);
380 }
381 catch (ObjectStoreException e)
382 {
383 throw new MessagingException(group.getMessageCollectionEvent(), e);
384 }
385
386 if (isFailOnTimeout())
387 {
388 MuleMessageCollection messageCollection;
389 try
390 {
391 messageCollection = group.toMessageCollection();
392 }
393 catch (ObjectStoreException e)
394 {
395 throw new MessagingException(group.getMessageCollectionEvent(), e);
396 }
397 muleContext.fireNotification(new RoutingNotification(messageCollection, null,
398 RoutingNotification.CORRELATION_TIMEOUT));
399 MuleEvent groupCollectionEvent = group.getMessageCollectionEvent();
400 try
401 {
402 group.clear();
403 }
404 catch (ObjectStoreException e)
405 {
406 logger.warn("Failed to clear group with id " + group.getGroupId()
407 + " since underlying ObjectStore threw Exception:" + e.getMessage());
408 }
409 throw new CorrelationTimeoutException(CoreMessages.correlationTimedOut(group.getGroupId()),
410 groupCollectionEvent);
411 }
412 else
413 {
414 if (logger.isDebugEnabled())
415 {
416 logger.debug(MessageFormat.format(
417 "Aggregator expired, but ''failOnTimeOut'' is false. Forwarding {0} events out of {1} "
418 + "total for group ID: {2}", group.size(), group.expectedSize(),
419 group.getGroupId()));
420 }
421
422 try
423 {
424 if (!(group.getCreated() + groupTimeToLive < System.currentTimeMillis()))
425 {
426 MuleEvent newEvent = callback.aggregateEvents(group);
427 group.clear();
428 newEvent.getMessage().setCorrelationId(group.getGroupId().toString());
429
430 if (!expiredAndDispatchedGroups.contains((Serializable) group.getGroupId()))
431 {
432
433
434 if (timeoutMessageProcessor != null)
435 {
436 timeoutMessageProcessor.process(newEvent);
437 }
438 else
439 {
440 final FlowConstruct service = group.toArray()[0].getFlowConstruct();
441 if (!(service instanceof Service))
442 {
443 throw new UnsupportedOperationException(
444 "EventAggregator is only supported with Service");
445 }
446
447 ((Service) service).dispatchEvent(newEvent);
448 }
449 expiredAndDispatchedGroups.store((Serializable) group.getGroupId(),
450 group.getCreated());
451 }
452 else
453 {
454 logger.warn(MessageFormat.format("Discarding group {0}", group.getGroupId()));
455 }
456 }
457 }
458 catch (MessagingException me)
459 {
460 throw me;
461 }
462 catch (Exception e)
463 {
464 throw new MessagingException(group.getMessageCollectionEvent(), e);
465 }
466 }
467 }
468
469 public void start() throws MuleException
470 {
471 logger.info("Starting event correlator: " + name);
472 if (timeout != 0)
473 {
474 expiringGroupMonitoringThread = new ExpiringGroupMonitoringThread();
475 expiringGroupMonitoringThread.start();
476 }
477 }
478
479 public void stop() throws MuleException
480 {
481 logger.info("Stopping event correlator: " + name);
482 if (expiringGroupMonitoringThread != null)
483 {
484 expiringGroupMonitoringThread.stopProcessing();
485 }
486 }
487
488 private final class ExpiringGroupMonitoringThread extends EventProcessingThread implements Expirable
489 {
490 private ExpiryMonitor expiryMonitor;
491 public static final long DELAY_TIME = 10;
492
493 public ExpiringGroupMonitoringThread()
494 {
495 super(name, DELAY_TIME);
496 this.expiryMonitor = new ExpiryMonitor(name, 1000 * 60, muleContext, true);
497
498 this.expiryMonitor.addExpirable(1000 * 60 * 30, TimeUnit.MILLISECONDS, this);
499 }
500
501
502
503
504
505
506
507 public void expired()
508 {
509 try
510 {
511 for (Serializable o : expiredAndDispatchedGroups.allKeys())
512 {
513 Long time = (Long) expiredAndDispatchedGroups.retrieve(o);
514 if (time + groupTimeToLive < System.currentTimeMillis())
515 {
516 expiredAndDispatchedGroups.remove(o);
517 logger.warn(MessageFormat.format("Discarding group {0}", o));
518 }
519 }
520 }
521 catch (ObjectStoreException e)
522 {
523 logger.warn("Expiration of objects failed due to ObjectStoreException " + e + ".");
524 }
525 }
526
527 public void doRun()
528 {
529 List<EventGroup> expired = new ArrayList<EventGroup>(1);
530 try
531 {
532 for (Serializable o : eventGroups.allKeys())
533 {
534 EventGroup group = (EventGroup) eventGroups.retrieve(o);
535 if ((group.getCreated() + getTimeout() * MILLI_TO_NANO_MULTIPLIER) < System.nanoTime())
536 {
537 expired.add(group);
538 }
539 }
540 }
541 catch (ObjectStoreException e)
542 {
543 logger.warn("expiry failed dues to ObjectStoreException " + e);
544 }
545 if (expired.size() > 0)
546 {
547 for (Object anExpired : expired)
548 {
549 EventGroup group = (EventGroup) anExpired;
550 try
551 {
552 handleGroupExpiry(group);
553 }
554 catch (MessagingException e)
555 {
556 e.getEvent()
557 .getFlowConstruct()
558 .getExceptionListener()
559 .handleException(e, e.getEvent());
560 }
561 }
562 }
563 }
564 }
565 }