View Javadoc

1   /*
2    * $Id: AbstractAsyncRequestReplyRequester.java 20637 2010-12-11 02:32:12Z dfeist $
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.OptimizedRequestContext;
14  import org.mule.api.MuleEvent;
15  import org.mule.api.MuleException;
16  import org.mule.api.MuleMessageCollection;
17  import org.mule.api.construct.FlowConstruct;
18  import org.mule.api.construct.FlowConstructAware;
19  import org.mule.api.processor.MessageProcessor;
20  import org.mule.api.processor.RequestReplyRequesterMessageProcessor;
21  import org.mule.api.routing.ResponseTimeoutException;
22  import org.mule.api.source.MessageSource;
23  import org.mule.config.i18n.CoreMessages;
24  import org.mule.context.notification.RoutingNotification;
25  import org.mule.processor.AbstractInterceptingMessageProcessor;
26  import org.mule.util.ObjectUtils;
27  import org.mule.util.concurrent.Latch;
28  
29  import java.util.Map;
30  
31  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
32  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentMap;
33  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
34  
35  import org.apache.commons.collections.buffer.BoundedFifoBuffer;
36  
37  public abstract class AbstractAsyncRequestReplyRequester extends AbstractInterceptingMessageProcessor
38      implements RequestReplyRequesterMessageProcessor, FlowConstructAware
39  {
40      public static final int MAX_PROCESSED_GROUPS = 50000;
41  
42      protected volatile long timeout = -1;
43      protected volatile boolean failOnTimeout = true;
44      protected MessageSource replyMessageSource;
45      protected FlowConstruct flowConstruct;
46      private final MessageProcessor internalAsyncReplyMessageProcessor = new InternalAsyncReplyMessageProcessor();
47  
48      @SuppressWarnings("unchecked")
49      protected final Map<String, Latch> locks = new ConcurrentHashMap();
50      
51      protected final ConcurrentMap responseEvents = new ConcurrentHashMap();
52      protected final Object processedLock = new Object();
53      // @GuardedBy processedLock
54      protected final BoundedFifoBuffer processed = new BoundedFifoBuffer(MAX_PROCESSED_GROUPS);
55  
56      public MuleEvent process(MuleEvent event) throws MuleException
57      {
58          if (replyMessageSource == null)
59          {
60              return processNext(event);
61          }
62          else
63          {
64              locks.put(getAsyncReplyCorrelationId(event), new Latch());
65  
66              sendAsyncRequest(event);
67  
68              return receiveAsyncReply(event);
69          }
70      }
71  
72      public void setTimeout(long timeout)
73      {
74          this.timeout = timeout;
75      }
76  
77      public void setFailOnTimeout(boolean failOnTimeout)
78      {
79          this.failOnTimeout = failOnTimeout;
80      }
81  
82      public void setReplySource(MessageSource messageSource)
83      {
84          verifyReplyMessageSource(messageSource);
85          replyMessageSource = messageSource;
86          messageSource.setListener(internalAsyncReplyMessageProcessor);
87      }
88  
89      protected void verifyReplyMessageSource(MessageSource messageSource)
90      {
91          // template method
92      }
93  
94      protected String getAsyncReplyCorrelationId(MuleEvent event)
95      {
96          if (event.getMessage() instanceof MuleMessageCollection)
97          {
98              return event.getMessage().getCorrelationId();
99          }
100         else
101         {
102             return event.getFlowConstruct().getMessageInfoMapping().getCorrelationId(event.getMessage());
103         }
104     }
105 
106     protected void sendAsyncRequest(MuleEvent event) throws MuleException
107     {
108         processNext(event);
109     }
110     
111     protected MuleEvent receiveAsyncReply(MuleEvent event) throws ResponseTimeoutException
112     {
113         String asyncReplyCorrelationId = getAsyncReplyCorrelationId(event);
114         Latch asyncReplyLatch = locks.get(asyncReplyCorrelationId);
115         // flag for catching the interrupted status of the Thread waiting for a
116         // result
117         boolean interruptedWhileWaiting = false;
118         boolean resultAvailable = false;
119         MuleEvent result = null;
120 
121         try
122         {
123             if (logger.isDebugEnabled())
124             {
125                 logger.debug("Waiting for async reply message with id: " + asyncReplyCorrelationId);
126             }
127             // how long should we wait for the lock?
128             if (timeout <= 0)
129             {
130                 asyncReplyLatch.await();
131                 resultAvailable = true;
132             }
133             else
134             {
135                 resultAvailable = asyncReplyLatch.await(timeout, TimeUnit.MILLISECONDS);
136             }
137             if (!resultAvailable)
138             {
139                 postLatchAwait(asyncReplyCorrelationId);
140                 resultAvailable = asyncReplyLatch.getCount() == 0;
141             }
142         }
143         catch (InterruptedException e)
144         {
145             interruptedWhileWaiting = true;
146         }
147         finally
148         {
149             locks.remove(asyncReplyCorrelationId);
150             result = (MuleEvent) responseEvents.remove(asyncReplyCorrelationId);
151             if (interruptedWhileWaiting)
152             {
153                 Thread.currentThread().interrupt();
154             }
155         }
156 
157         if (interruptedWhileWaiting)
158         {
159             Thread.currentThread().interrupt();
160         }
161 
162         if (resultAvailable)
163         {
164             if (result == null)
165             {
166                 // this should never happen, just using it as a safe guard for now
167                 throw new IllegalStateException("Response MuleEvent is null");
168             }
169             // Copy event because the async-reply message was received by a different
170             // receiver thread (or the senders dispatcher thread in case of vm
171             // with queueEvents="false") and the current thread may need to mutate
172             // the even. See MULE-4370
173             return OptimizedRequestContext.criticalSetEvent(result);
174         }
175         else
176         {
177             addProcessed(asyncReplyCorrelationId);
178 
179             if (failOnTimeout)
180             {
181                 event.getMuleContext()
182                     .fireNotification(
183                         new RoutingNotification(event.getMessage(), null,
184                             RoutingNotification.ASYNC_REPLY_TIMEOUT));
185 
186                 throw new ResponseTimeoutException(CoreMessages.responseTimedOutWaitingForId((int) timeout,
187                     asyncReplyCorrelationId), event, null);
188             }
189             else
190             {
191                 return null;
192             }
193         }
194     }
195 
196     protected void postLatchAwait(String asyncReplyCorrelationId)
197     {
198         // Template method
199     }
200 
201     protected void addProcessed(Object id)
202     {
203         synchronized (processedLock)
204         {
205             if (processed.isFull())
206             {
207                 processed.remove();
208             }
209             processed.add(id);
210         }
211     }
212 
213     protected boolean isAlreadyProcessed(Object id)
214     {
215         synchronized (processedLock)
216         {
217             return processed.contains(id);
218         }
219     }
220 
221     class InternalAsyncReplyMessageProcessor implements MessageProcessor
222     {
223         public MuleEvent process(MuleEvent event) throws MuleException
224         {
225             String messageId = getAsyncReplyCorrelationId(event);
226 
227             if (isAlreadyProcessed(messageId))
228             {
229                 if (logger.isDebugEnabled())
230                 {
231                     logger.debug("An event was received for an event group that has already been processed, "
232                                  + "this is probably because the async-reply timed out. Correlation Id is: "
233                                  + messageId + ". Dropping event");
234                 }
235                 // Fire a notification to say we received this message
236                 event.getMuleContext().fireNotification(
237                     new RoutingNotification(event.getMessage(), event.getEndpoint()
238                         .getEndpointURI()
239                         .toString(), RoutingNotification.MISSED_ASYNC_REPLY));
240                 return null;
241             }
242 
243             addProcessed(messageId);
244             MuleEvent previousResult = (MuleEvent) responseEvents.putIfAbsent(messageId, event);
245             if (previousResult != null)
246             {
247                 // this would indicate that we need a better way to prevent
248                 // continued aggregation for a group that is currently being
249                 // processed. Can this actually happen?
250                 throw new IllegalStateException("Detected duplicate result message with id: " + messageId);
251             }
252             Latch l = locks.get(messageId);
253             if (l != null)
254             {
255                 l.countDown();
256             }
257             else
258             {
259                 logger.warn("Unexpected  message with id " + messageId
260                             + " received.   This message will be discarded.");
261             }
262             return null;
263         }
264     }
265 
266     @Override
267     public String toString()
268     {
269         return ObjectUtils.toString(this);
270     }
271     
272     public void setFlowConstruct(FlowConstruct flowConstruct)
273     {
274         this.flowConstruct = flowConstruct;
275     }
276 }