1
2
3
4
5
6
7
8
9
10
11 package org.mule.routing.requestreply;
12
13 import org.mule.DefaultMuleEvent;
14 import org.mule.OptimizedRequestContext;
15 import org.mule.RequestContext;
16 import org.mule.api.MessagingException;
17 import org.mule.api.MuleEvent;
18 import org.mule.api.MuleException;
19 import org.mule.api.MuleMessageCollection;
20 import org.mule.api.config.MuleProperties;
21 import org.mule.api.construct.FlowConstruct;
22 import org.mule.api.construct.FlowConstructAware;
23 import org.mule.api.lifecycle.Disposable;
24 import org.mule.api.lifecycle.Initialisable;
25 import org.mule.api.lifecycle.InitialisationException;
26 import org.mule.api.lifecycle.Startable;
27 import org.mule.api.lifecycle.Stoppable;
28 import org.mule.api.processor.MessageProcessor;
29 import org.mule.api.processor.RequestReplyRequesterMessageProcessor;
30 import org.mule.api.routing.ResponseTimeoutException;
31 import org.mule.api.source.MessageSource;
32 import org.mule.api.store.ListableObjectStore;
33 import org.mule.api.store.ObjectStore;
34 import org.mule.api.store.ObjectStoreException;
35 import org.mule.api.store.ObjectStoreManager;
36 import org.mule.config.i18n.CoreMessages;
37 import org.mule.context.notification.RoutingNotification;
38 import org.mule.processor.AbstractInterceptingMessageProcessor;
39 import org.mule.processor.AbstractInterceptingMessageProcessorBase;
40 import org.mule.routing.EventProcessingThread;
41 import org.mule.util.ObjectUtils;
42 import org.mule.util.concurrent.Latch;
43
44 import java.io.Serializable;
45 import java.util.Date;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.concurrent.ConcurrentHashMap;
49 import java.util.concurrent.ConcurrentMap;
50 import java.util.concurrent.TimeUnit;
51
52 import org.apache.commons.collections.buffer.BoundedFifoBuffer;
53 import org.mule.util.concurrent.ThreadNameHelper;
54
55 public abstract class AbstractAsyncRequestReplyRequester extends AbstractInterceptingMessageProcessorBase
56 implements RequestReplyRequesterMessageProcessor, FlowConstructAware, Initialisable, Startable, Stoppable, Disposable
57 {
58 public static final int MAX_PROCESSED_GROUPS = 50000;
59 public static final int UNCLAIMED_TIME_TO_LIVE = 60000;
60 public static int UNCLAIMED_INTERVAL = 60000;
61
62
63 public static final String NAME_TEMPLATE = "%s.%s.%s.asyncReplies";
64 protected String name;
65
66 protected volatile long timeout = -1;
67 protected volatile boolean failOnTimeout = true;
68 protected MessageSource replyMessageSource;
69 protected FlowConstruct flowConstruct;
70 private final MessageProcessor internalAsyncReplyMessageProcessor = new InternalAsyncReplyMessageProcessor();
71 private AsyncReplyMonitoringThread replyThread;
72 protected final Map<String, Latch> locks = new ConcurrentHashMap<String, Latch>();
73 private String storePrefix = "";
74
75 protected final ConcurrentMap<String, MuleEvent> responseEvents = new ConcurrentHashMap<String, MuleEvent>();
76 protected final Object processedLock = new Object();
77
78 protected final BoundedFifoBuffer processed = new BoundedFifoBuffer(MAX_PROCESSED_GROUPS);
79
80 protected ListableObjectStore store;
81
82 @Override
83 public MuleEvent process(MuleEvent event) throws MuleException
84 {
85 if (replyMessageSource == null)
86 {
87 return processNext(event);
88 }
89 else
90 {
91 locks.put(getAsyncReplyCorrelationId(event), new Latch());
92
93 sendAsyncRequest(event);
94
95 MuleEvent resultEvent = receiveAsyncReply(event);
96
97 if (resultEvent != null)
98 {
99 resultEvent = org.mule.RequestContext.setEvent(new DefaultMuleEvent(resultEvent.getMessage(), event));
100 }
101 return resultEvent;
102 }
103 }
104
105 public void setTimeout(long timeout)
106 {
107 this.timeout = timeout;
108 }
109
110 public void setFailOnTimeout(boolean failOnTimeout)
111 {
112 this.failOnTimeout = failOnTimeout;
113 }
114
115 @Override
116 public void setReplySource(MessageSource messageSource)
117 {
118 verifyReplyMessageSource(messageSource);
119 replyMessageSource = messageSource;
120 messageSource.setListener(internalAsyncReplyMessageProcessor);
121 }
122
123 @Override
124 public void initialise() throws InitialisationException
125 {
126 name = String.format(NAME_TEMPLATE, storePrefix, ThreadNameHelper.getPrefix(muleContext),
127 flowConstruct == null ? "" : flowConstruct.getName());
128 store = ((ObjectStoreManager) muleContext.getRegistry().
129 get(MuleProperties.OBJECT_STORE_MANAGER)).
130 getObjectStore(name, false, MAX_PROCESSED_GROUPS, UNCLAIMED_TIME_TO_LIVE, UNCLAIMED_INTERVAL);
131 }
132
133 @Override
134 public void start() throws MuleException
135 {
136 replyThread = new AsyncReplyMonitoringThread(name);
137 replyThread.start();
138 }
139
140 @Override
141 public void stop() throws MuleException
142 {
143 if (replyThread != null)
144 {
145 replyThread.stopProcessing();
146 }
147 }
148
149 @Override
150 public void dispose()
151 {
152 if (store != null)
153 {
154 try
155 {
156 ((ObjectStoreManager) muleContext.getRegistry().
157 get(MuleProperties.OBJECT_STORE_MANAGER)).disposeStore(store);
158 }
159 catch (ObjectStoreException e)
160 {
161 logger.debug("Exception disposingg of store", e);
162 }
163 }
164 }
165
166 public void setStorePrefix(String storePrefix)
167 {
168 this.storePrefix = storePrefix;
169 }
170
171 protected void verifyReplyMessageSource(MessageSource messageSource)
172 {
173
174 }
175
176 protected String getAsyncReplyCorrelationId(MuleEvent event)
177 {
178 if (event.getMessage() instanceof MuleMessageCollection)
179 {
180 return event.getMessage().getCorrelationId();
181 }
182 else
183 {
184 return event.getFlowConstruct().getMessageInfoMapping().getCorrelationId(event.getMessage());
185 }
186 }
187
188 protected void sendAsyncRequest(MuleEvent event) throws MuleException
189 {
190 processNext(event);
191 }
192
193 protected MuleEvent receiveAsyncReply(MuleEvent event) throws MessagingException
194 {
195 String asyncReplyCorrelationId = getAsyncReplyCorrelationId(event);
196 Latch asyncReplyLatch = locks.get(asyncReplyCorrelationId);
197
198
199 boolean interruptedWhileWaiting = false;
200 boolean resultAvailable = false;
201 MuleEvent result = null;
202
203 try
204 {
205 if (logger.isDebugEnabled())
206 {
207 logger.debug("Waiting for async reply message with id: " + asyncReplyCorrelationId);
208 }
209
210 if (timeout <= 0)
211 {
212 asyncReplyLatch.await();
213 resultAvailable = true;
214 }
215 else
216 {
217 resultAvailable = asyncReplyLatch.await(timeout, TimeUnit.MILLISECONDS);
218 }
219 if (!resultAvailable)
220 {
221 postLatchAwait(asyncReplyCorrelationId);
222 asyncReplyLatch.await(1000, TimeUnit.MILLISECONDS);
223 resultAvailable = asyncReplyLatch.getCount() == 0;
224 }
225 }
226 catch (InterruptedException e)
227 {
228 interruptedWhileWaiting = true;
229 }
230 finally
231 {
232 locks.remove(asyncReplyCorrelationId);
233 result = responseEvents.remove(asyncReplyCorrelationId);
234 if (interruptedWhileWaiting)
235 {
236 Thread.currentThread().interrupt();
237 }
238 }
239
240 if (interruptedWhileWaiting)
241 {
242 Thread.currentThread().interrupt();
243 }
244
245 if (resultAvailable)
246 {
247 if (result == null)
248 {
249
250 throw new IllegalStateException("Response MuleEvent is null");
251 }
252
253
254
255
256 return OptimizedRequestContext.criticalSetEvent(result);
257 }
258 else
259 {
260 addProcessed(asyncReplyCorrelationId);
261
262 if (failOnTimeout)
263 {
264 event.getMuleContext()
265 .fireNotification(
266 new RoutingNotification(event.getMessage(), null,
267 RoutingNotification.ASYNC_REPLY_TIMEOUT));
268
269 throw new ResponseTimeoutException(CoreMessages.responseTimedOutWaitingForId((int) timeout,
270 asyncReplyCorrelationId), event, null);
271 }
272 else
273 {
274 return null;
275 }
276 }
277 }
278
279 protected void postLatchAwait(String asyncReplyCorrelationId) throws MessagingException
280 {
281
282 }
283
284 protected void addProcessed(Object id)
285 {
286 synchronized (processedLock)
287 {
288 if (processed.isFull())
289 {
290 processed.remove();
291 }
292 processed.add(id);
293 }
294 }
295
296 protected boolean isAlreadyProcessed(Object id)
297 {
298 synchronized (processedLock)
299 {
300 return processed.contains(id);
301 }
302 }
303
304 class InternalAsyncReplyMessageProcessor implements MessageProcessor
305 {
306 @Override
307 public MuleEvent process(MuleEvent event) throws MuleException
308 {
309 String messageId = getAsyncReplyCorrelationId(event);
310 store.store(messageId, event);
311 replyThread.processNow();
312 return null;
313 }
314 }
315
316 @Override
317 public String toString()
318 {
319 return ObjectUtils.toString(this);
320 }
321
322 @Override
323 public void setFlowConstruct(FlowConstruct flowConstruct)
324 {
325 this.flowConstruct = flowConstruct;
326 }
327
328 private class AsyncReplyMonitoringThread extends EventProcessingThread
329 {
330 AsyncReplyMonitoringThread(String name)
331 {
332 super(name, 100);
333 }
334
335 @Override
336 protected void doRun()
337 {
338 try
339 {
340 List<Serializable> ids = store.allKeys();
341 logger.debug("Found " + ids.size() + " objects in store");
342 for (Serializable id : ids)
343 {
344 try
345 {
346 boolean deleteEvent = false;
347 String correlationId = (String) id;
348
349 if (isAlreadyProcessed(correlationId))
350 {
351 deleteEvent = true;
352 MuleEvent event = (MuleEvent) store.retrieve(correlationId);
353 if (logger.isDebugEnabled())
354 {
355 logger.debug("An event was received for an event group that has already been processed, "
356 + "this is probably because the async-reply timed out. Correlation Id is: "
357 + correlationId + ". Dropping event");
358 }
359
360 event.getMuleContext().fireNotification(
361 new RoutingNotification(event.getMessage(), event.getMessageSourceURI().toString(),
362 RoutingNotification.MISSED_ASYNC_REPLY));
363 }
364 else
365 {
366 Latch l = locks.get(correlationId);
367 if (l != null)
368 {
369 MuleEvent event = (MuleEvent) store.retrieve(correlationId);
370 MuleEvent previousResult = responseEvents.putIfAbsent(correlationId, event);
371 if (previousResult != null)
372 {
373
374
375
376 throw new IllegalStateException("Detected duplicate result message with id: " + correlationId);
377 }
378 addProcessed(correlationId);
379 deleteEvent = true;
380 l.countDown();
381 }
382 }
383 if (deleteEvent)
384 {
385 store.remove(correlationId);
386 }
387 }
388 catch (Exception ex)
389 {
390 logger.debug("Error processing async replies", ex);
391 }
392 }
393 }
394 catch (Exception ex)
395 {
396 logger.debug("Error processing async replies", ex);
397 }
398 }
399 }
400 }