1
2
3
4
5
6
7
8
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
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
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
116
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
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
167 throw new IllegalStateException("Response MuleEvent is null");
168 }
169
170
171
172
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
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
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
248
249
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 }