1
2
3
4
5
6
7
8
9
10
11 package org.mule.routing;
12
13 import java.io.Serializable;
14 import java.util.concurrent.TimeUnit;
15
16 import org.mule.DefaultMuleEvent;
17 import org.mule.DefaultMuleMessage;
18 import org.mule.api.MessagingException;
19 import org.mule.api.MuleEvent;
20 import org.mule.api.MuleException;
21 import org.mule.api.MuleMessage;
22 import org.mule.api.MuleRuntimeException;
23 import org.mule.api.endpoint.EndpointBuilder;
24 import org.mule.api.endpoint.EndpointException;
25 import org.mule.api.lifecycle.InitialisationException;
26 import org.mule.api.processor.MessageProcessor;
27 import org.mule.api.retry.RetryCallback;
28 import org.mule.api.retry.RetryContext;
29 import org.mule.api.retry.RetryNotifier;
30 import org.mule.api.retry.RetryPolicyTemplate;
31 import org.mule.api.store.ListableObjectStore;
32 import org.mule.api.store.ObjectStoreException;
33 import org.mule.config.i18n.MessageFactory;
34 import org.mule.retry.async.AsynchronousRetryTemplate;
35 import org.mule.retry.policies.SimpleRetryPolicyTemplate;
36 import org.mule.routing.filters.ExpressionFilter;
37 import org.mule.routing.outbound.AbstractOutboundRouter;
38 import org.mule.util.SystemUtils;
39
40
41
42
43
44
45
46
47 public class UntilSuccessful extends AbstractOutboundRouter
48 {
49 public static class EventStoreKey implements Serializable
50 {
51 private static final long serialVersionUID = 1L;
52 private final String value;
53
54 private EventStoreKey(final String value)
55 {
56 this.value = value;
57 }
58
59 public static EventStoreKey buildFor(final MuleEvent muleEvent)
60 {
61
62
63 String key = muleEvent.getFlowConstruct() + "@"
64 + muleEvent.getMuleContext().getClusterId() + ":" + muleEvent.getId();
65 return new EventStoreKey(SystemUtils.legalizeFileName(key));
66 }
67
68 @Override
69 public String toString()
70 {
71 return value;
72 }
73
74 @Override
75 public int hashCode()
76 {
77 return value.hashCode();
78 }
79
80 @Override
81 public boolean equals(final Object obj)
82 {
83 if (!(obj instanceof EventStoreKey))
84 {
85 return false;
86 }
87
88 return value.equals(((EventStoreKey) obj).value);
89 }
90 }
91
92 public static final String PROCESS_ATTEMPT_COUNT_PROPERTY_NAME = "process.attempt.count";
93
94 private static final int DEFAULT_PROCESS_ATTEMPT_COUNT_PROPERTY_VALUE = 1;
95
96 private ListableObjectStore<MuleEvent> objectStore;
97 private int maxRetries = 5;
98 private long secondsBetweenRetries = 60L;
99 private String failureExpression;
100 private String ackExpression;
101 private ExpressionFilter failureExpressionFilter;
102 private String eventKeyPrefix;
103 private Object deadLetterQueue;
104 private MessageProcessor dlqMP;
105
106 @Override
107 public void initialise() throws InitialisationException
108 {
109 if (routes.isEmpty())
110 {
111 throw new InitialisationException(
112 MessageFactory.createStaticMessage("One message processor must be configured within UntilSuccessful."),
113 this);
114 }
115
116 if (routes.size() > 1)
117 {
118 throw new InitialisationException(
119 MessageFactory.createStaticMessage("Only one message processor is allowed within UntilSuccessful."
120 + " Use a Processor Chain to group several message processors into one."),
121 this);
122 }
123
124 if (objectStore == null)
125 {
126 throw new InitialisationException(
127 MessageFactory.createStaticMessage("A ListableObjectStore must be configured on UntilSuccessful."),
128 this);
129 }
130
131 super.initialise();
132
133 if (deadLetterQueue != null)
134 {
135 if (deadLetterQueue instanceof EndpointBuilder)
136 {
137 try
138 {
139
140 dlqMP = ((EndpointBuilder) deadLetterQueue).buildOutboundEndpoint();
141 }
142 catch (final EndpointException ee)
143 {
144 throw new InitialisationException(
145 MessageFactory.createStaticMessage("deadLetterQueue-ref is not a valid endpoint builder: "
146 + deadLetterQueue), ee, this);
147 }
148 }
149 else if (deadLetterQueue instanceof MessageProcessor)
150 {
151 dlqMP = (MessageProcessor) deadLetterQueue;
152 }
153 else
154 {
155 throw new InitialisationException(
156 MessageFactory.createStaticMessage("deadLetterQueue-ref is not a valid mesage processor: "
157 + deadLetterQueue), null, this);
158 }
159 }
160
161 if (failureExpression != null)
162 {
163 failureExpressionFilter = new ExpressionFilter(failureExpression);
164 }
165 else
166 {
167 failureExpressionFilter = new ExpressionFilter("exception-type:");
168 }
169 failureExpressionFilter.setMuleContext(muleContext);
170
171 if ((ackExpression != null) && (!muleContext.getExpressionManager().isExpression(ackExpression)))
172 {
173 throw new InitialisationException(MessageFactory.createStaticMessage("Invalid ackExpression: "
174 + ackExpression), this);
175 }
176
177 eventKeyPrefix = flowConstruct.getName() + "@" + muleContext.getClusterId() + ":";
178 }
179
180 @Override
181 public void start() throws MuleException
182 {
183 super.start();
184 scheduleAllPendingEventsForProcessing();
185 }
186
187 @Override
188 public boolean isMatch(final MuleMessage message) throws MuleException
189 {
190 return true;
191 }
192
193 @Override
194 protected MuleEvent route(final MuleEvent event) throws MessagingException
195 {
196 try
197 {
198 ensurePayloadSerializable(event);
199 }
200 catch (final Exception e)
201 {
202 throw new MessagingException(
203 MessageFactory.createStaticMessage("Failed to prepare message for processing"), event, e);
204 }
205
206 try
207 {
208 final EventStoreKey eventStoreKey = storeEvent(event);
209 scheduleForProcessing(eventStoreKey);
210
211 if (ackExpression == null)
212 {
213 return null;
214 }
215
216 final Object ackResponsePayload = muleContext.getExpressionManager().evaluate(ackExpression,
217 event.getMessage());
218
219 return new DefaultMuleEvent(new DefaultMuleMessage(ackResponsePayload, event.getMessage(),
220 muleContext), event);
221 }
222 catch (final Exception e)
223 {
224 throw new MessagingException(
225 MessageFactory.createStaticMessage("Failed to schedule the event for processing"), event, e);
226 }
227 }
228
229 private void scheduleAllPendingEventsForProcessing() throws ObjectStoreException
230 {
231 for (final Serializable eventStoreKey : objectStore.allKeys())
232 {
233 try
234 {
235 scheduleForProcessing((EventStoreKey) eventStoreKey);
236 }
237 catch (final Exception e)
238 {
239 logger.error(
240 MessageFactory.createStaticMessage("Failed to schedule for processing event stored with key: "
241 + eventStoreKey), e);
242 }
243 }
244 }
245
246 private void scheduleForProcessing(final EventStoreKey eventStoreKey) throws Exception
247 {
248 final RetryCallback callback = new RetryCallback()
249 {
250 @Override
251 public String getWorkDescription()
252 {
253 return "Until successful processing of event stored under key: " + eventStoreKey;
254 }
255
256 @Override
257 public void doWork(final RetryContext context) throws Exception
258 {
259 retrieveAndProcessEvent(eventStoreKey);
260 }
261 };
262
263 final SimpleRetryPolicyTemplate simpleRetryPolicyTemplate = new SimpleRetryPolicyTemplate(
264 TimeUnit.SECONDS.toMillis(secondsBetweenRetries), maxRetries);
265
266 final RetryPolicyTemplate retryPolicyTemplate = new AsynchronousRetryTemplate(
267 simpleRetryPolicyTemplate);
268 retryPolicyTemplate.setNotifier(new RetryNotifier()
269 {
270 @Override
271 public void onSuccess(final RetryContext context)
272 {
273 removeFromStore(eventStoreKey);
274 }
275
276 @Override
277 public void onFailure(final RetryContext context, final Throwable e)
278 {
279 incrementProcessAttemptCountOrRemoveFromStore(eventStoreKey);
280 }
281 });
282
283 retryPolicyTemplate.execute(callback, muleContext.getWorkManager());
284 }
285
286 private EventStoreKey storeEvent(final MuleEvent event) throws ObjectStoreException
287 {
288 final MuleMessage message = event.getMessage();
289 final Integer deliveryAttemptCount = message.getInvocationProperty(
290 PROCESS_ATTEMPT_COUNT_PROPERTY_NAME, DEFAULT_PROCESS_ATTEMPT_COUNT_PROPERTY_VALUE);
291 return storeEvent(event, deliveryAttemptCount);
292 }
293
294 private EventStoreKey storeEvent(final MuleEvent event, final int deliveryAttemptCount)
295 throws ObjectStoreException
296 {
297 final MuleMessage message = event.getMessage();
298 message.setInvocationProperty(PROCESS_ATTEMPT_COUNT_PROPERTY_NAME, deliveryAttemptCount);
299 final EventStoreKey eventStoreKey = EventStoreKey.buildFor(event);
300 objectStore.store(eventStoreKey, event);
301 return eventStoreKey;
302 }
303
304 private void incrementProcessAttemptCountOrRemoveFromStore(final EventStoreKey eventStoreKey)
305 {
306 try
307 {
308 final MuleEvent event = objectStore.remove(eventStoreKey);
309 final MuleEvent mutableEvent = threadSafeCopy(event);
310
311 final MuleMessage message = mutableEvent.getMessage();
312 final Integer deliveryAttemptCount = message.getInvocationProperty(
313 PROCESS_ATTEMPT_COUNT_PROPERTY_NAME, DEFAULT_PROCESS_ATTEMPT_COUNT_PROPERTY_VALUE);
314
315 if (deliveryAttemptCount <= getMaxRetries())
316 {
317
318 message.setInvocationProperty(PROCESS_ATTEMPT_COUNT_PROPERTY_NAME, deliveryAttemptCount + 1);
319 objectStore.store(eventStoreKey, mutableEvent);
320 }
321 else
322 {
323 abandonRetries(event, mutableEvent);
324 }
325 }
326 catch (final ObjectStoreException ose)
327 {
328 logger.error("Failed to increment failure count for event stored with key: " + eventStoreKey);
329 }
330 }
331
332 private void abandonRetries(final MuleEvent event, final MuleEvent mutableEvent)
333 {
334 if (dlqMP == null)
335 {
336 logger.info("Retry attempts exhausted and no DLQ defined, dropping message: " + event);
337 return;
338 }
339
340 try
341 {
342 logger.info("Retry attempts exhausted, routing message to DLQ: " + dlqMP);
343 dlqMP.process(mutableEvent);
344 }
345 catch (final MuleException me)
346 {
347 logger.error("Failed to route message to DLQ: " + dlqMP + ", dropping message: " + event, me);
348 }
349 }
350
351 private void removeFromStore(final EventStoreKey eventStoreKey)
352 {
353 try
354 {
355 objectStore.remove(eventStoreKey);
356 }
357 catch (final ObjectStoreException ose)
358 {
359 logger.warn("Failed to remove following event from store with key: " + eventStoreKey);
360 }
361 }
362
363 private void retrieveAndProcessEvent(final EventStoreKey eventStoreKey) throws ObjectStoreException
364 {
365 final MuleEvent persistedEvent = objectStore.retrieve(eventStoreKey);
366 final MuleEvent mutableEvent = threadSafeCopy(persistedEvent);
367 processEvent(mutableEvent);
368 }
369
370 private void processEvent(final MuleEvent event)
371 {
372 if (routes.isEmpty())
373 {
374 return;
375 }
376
377 MuleEvent returnEvent;
378 try
379 {
380 returnEvent = routes.get(0).process(event);
381 }
382 catch (final MuleException me)
383 {
384 throw new MuleRuntimeException(me);
385 }
386
387 if (returnEvent == null)
388 {
389 return;
390 }
391
392 final MuleMessage msg = returnEvent.getMessage();
393 if (msg == null)
394 {
395 throw new MuleRuntimeException(
396 MessageFactory.createStaticMessage("No message found in response to processing, which is therefore considered failed for event: "
397 + event));
398 }
399
400 final boolean errorDetected = failureExpressionFilter.accept(msg);
401 if (errorDetected)
402 {
403 throw new MuleRuntimeException(
404 MessageFactory.createStaticMessage("Failure expression positive when processing event: "
405 + event));
406 }
407 }
408
409 private DefaultMuleEvent threadSafeCopy(final MuleEvent event)
410 {
411 final DefaultMuleMessage message = new DefaultMuleMessage(event.getMessage().getPayload(),
412 event.getMessage(), muleContext);
413
414 return new DefaultMuleEvent(message, event);
415 }
416
417 private void ensurePayloadSerializable(final MuleEvent event) throws Exception
418 {
419 final MuleMessage message = event.getMessage();
420 if (message instanceof DefaultMuleMessage)
421 {
422 if (((DefaultMuleMessage) message).isConsumable())
423 {
424 message.getPayloadAsBytes();
425 }
426 }
427 else
428 {
429 message.getPayloadAsBytes();
430 }
431 }
432
433 public ListableObjectStore<MuleEvent> getObjectStore()
434 {
435 return objectStore;
436 }
437
438 public void setObjectStore(final ListableObjectStore<MuleEvent> objectStore)
439 {
440 this.objectStore = objectStore;
441 }
442
443 public int getMaxRetries()
444 {
445 return maxRetries;
446 }
447
448 public void setMaxRetries(final int maxRetries)
449 {
450 this.maxRetries = maxRetries;
451 }
452
453 public long getSecondsBetweenRetries()
454 {
455 return secondsBetweenRetries;
456 }
457
458 public void setSecondsBetweenRetries(final long secondsBetweenRetries)
459 {
460 this.secondsBetweenRetries = secondsBetweenRetries;
461 }
462
463 public String getFailureExpression()
464 {
465 return failureExpression;
466 }
467
468 public void setFailureExpression(final String failureExpression)
469 {
470 this.failureExpression = failureExpression;
471 }
472
473 public String getAckExpression()
474 {
475 return ackExpression;
476 }
477
478 public void setAckExpression(final String ackExpression)
479 {
480 this.ackExpression = ackExpression;
481 }
482
483 public void setDeadLetterQueue(final Object deadLetterQueue)
484 {
485 this.deadLetterQueue = deadLetterQueue;
486 }
487
488 public Object getDeadLetterQueue()
489 {
490 return deadLetterQueue;
491 }
492
493 public String getEventKeyPrefix()
494 {
495 return eventKeyPrefix;
496 }
497
498 public ExpressionFilter getFailureExpressionFilter()
499 {
500 return failureExpressionFilter;
501 }
502 }