View Javadoc

1   /*
2    * $Id: AbstractAsyncRequestReplyRequester.java 23371 2011-11-17 10:43:31Z pablo.lagreca $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.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.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      // @GuardedBy processedLock
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         // template method
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         // flag for catching the interrupted status of the Thread waiting for a
198         // result
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             // how long should we wait for the lock?
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                 // this should never happen, just using it as a safe guard for now
250                 throw new IllegalStateException("Response MuleEvent is null");
251             }
252             // Copy event because the async-reply message was received by a different
253             // receiver thread (or the senders dispatcher thread in case of vm
254             // with queueEvents="false") and the current thread may need to mutate
255             // the even. See MULE-4370
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         // Template method
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                             // Fire a notification to say we received this message
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                                     // this would indicate that we need a better way to prevent
374                                     // continued aggregation for a group that is currently being
375                                     // processed. Can this actually happen?
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 }