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