1
2
3
4
5
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
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
77
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
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
128
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
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
175 throw new IllegalStateException("Response MuleEvent is null");
176 }
177
178
179
180 if (result.getMessage().getInboundProperty(MuleProperties.MULE_SESSION_PROPERTY) != null)
181 {
182 event.getSession().merge(result.getSession());
183 }
184
185
186
187
188
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
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
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
268
269
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 }