View Javadoc

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