View Javadoc

1   /*
2    * $Id: IdempotentRedeliveryPolicy.java 22887 2011-09-07 23:38:34Z mike.schilling $
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  package org.mule.processor;
11  
12  import org.mule.api.MuleEvent;
13  import org.mule.api.MuleException;
14  import org.mule.api.lifecycle.Disposable;
15  import org.mule.api.lifecycle.InitialisationException;
16  import org.mule.api.lifecycle.Startable;
17  import org.mule.api.store.ObjectAlreadyExistsException;
18  import org.mule.api.store.ObjectStoreException;
19  import org.mule.config.i18n.CoreMessages;
20  import org.mule.transformer.simple.ByteArrayToHexString;
21  import org.mule.transformer.simple.ObjectToByteArray;
22  import org.mule.transformer.simple.SerializableToByteArray;
23  import org.mule.util.store.AbstractMonitoredObjectStore;
24  import org.mule.util.store.InMemoryObjectStore;
25  
26  
27  import java.io.InputStream;
28  import java.security.MessageDigest;
29  import java.security.NoSuchAlgorithmException;
30  import java.util.concurrent.atomic.AtomicInteger;
31  
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  /**
36   * Implement a retry policy for Mule.  This is similar to JMS retry policies that will redeliver a message a maximum
37   * number of times.  If this maximum is exceeded, the message is sent to a dead letter queue,  Here, if the processing of the messages
38   * fails too often, the message is sent to the failedMessageProcessor MP, whence success is force to be returned, to allow
39   * the message to be considered "consumed".
40   */
41  public class IdempotentRedeliveryPolicy extends AbstractRedeliveryPolicy
42  {
43      private final ObjectToByteArray objectToByteArray = new ObjectToByteArray();
44      private final ByteArrayToHexString byteArrayToHexString = new ByteArrayToHexString();
45  
46      protected Logger logger = LoggerFactory.getLogger(this.getClass());
47  
48      private boolean useSecureHash;
49      private String messageDigestAlgorithm;
50      private String idExpression;
51      private AbstractMonitoredObjectStore<AtomicInteger> store;
52  
53      @Override
54      public void initialise() throws InitialisationException
55      {
56          super.initialise();
57          if (useSecureHash && idExpression != null)
58          {
59              throw new InitialisationException(
60                  CoreMessages.initialisationFailure(String.format(
61                      "The Id expression'%s' was specified when a secure hash will be used",
62                      idExpression)), this);
63          }
64          if (!useSecureHash && messageDigestAlgorithm != null)
65          {
66              throw new InitialisationException(
67                  CoreMessages.initialisationFailure(String.format(
68                      "The message digest algorithm '%s' was specified when a secure hash will not be used",
69                      messageDigestAlgorithm)), this);
70          }
71          if (!useSecureHash && idExpression == null)
72          {
73              throw new InitialisationException(
74                  CoreMessages.initialisationFailure(
75                      "No method for identifying messages was specified"), this);
76          }
77          if (useSecureHash)
78          {
79              if (messageDigestAlgorithm == null)
80              {
81                  messageDigestAlgorithm = "SHA-256";
82              }
83              try
84              {
85                  MessageDigest md = MessageDigest.getInstance(messageDigestAlgorithm);
86              }
87              catch (NoSuchAlgorithmException e)
88              {
89                  throw new InitialisationException(
90                      CoreMessages.initialisationFailure(
91                          String.format("Exception '%s' initializing message digest algorithm %s", e.getMessage(), messageDigestAlgorithm)), this);
92  
93              }
94          }
95  
96          store = createStore();
97      }
98  
99      private AbstractMonitoredObjectStore<AtomicInteger> createStore() throws InitialisationException
100     {
101         AbstractMonitoredObjectStore s = new InMemoryObjectStore<AtomicInteger>();
102         s.setName(flowConstruct.getName() + "." + getClass().getName());
103         s.setMaxEntries(-1);
104         s.setEntryTTL(60 * 5 * 1000);
105         s.setExpirationInterval(6000);
106         s.initialise();
107         return s;
108     }
109 
110 
111     @Override
112     public void dispose()
113     {
114         super.dispose();
115 
116         if (store != null)
117         {
118             store.dispose();
119             store = null;
120         }
121 
122         if (deadLetterQueue instanceof Disposable)
123         {
124             ((Disposable) deadLetterQueue).dispose();
125         }
126     }
127 
128     @Override
129     public void start() throws MuleException
130     {
131         if (deadLetterQueue instanceof Startable)
132         {
133             ((Startable) deadLetterQueue).start();
134         }
135     }
136 
137 
138     @Override
139     public MuleEvent process(MuleEvent event) throws MuleException
140     {
141         boolean exceptionSeen = false;
142         boolean tooMany = false;
143         AtomicInteger counter = null;
144 
145         String messageId = null;
146         try
147         {
148             messageId = getIdForEvent(event);
149         }
150         catch (Exception ex)
151         {
152             exceptionSeen = true;
153         }
154 
155         if (!exceptionSeen)
156         {
157             counter = getCounter(messageId, null, false);
158             tooMany = counter != null && counter.get() > maxRedeliveryCount;
159         }
160 
161         if (tooMany || exceptionSeen)
162         {
163             try
164             {
165                 return deadLetterQueue.process(event);
166             }
167             catch (Exception ex)
168             {
169                 logger.info("Exception thrown from failed message processing for message " + messageId, ex);
170             }
171             return null;
172         }
173 
174         try
175         {
176             MuleEvent returnEvent = processNext(event);
177             counter = getCounter(messageId, counter, false);
178             if (counter != null)
179             {
180                 counter.set(0);
181             }
182             return returnEvent;
183         }
184         catch (MuleException ex)
185         {
186             incrementCounter(messageId, counter);
187             throw ex;
188         }
189         catch (RuntimeException ex)
190         {
191             incrementCounter(messageId, counter);
192             throw ex;
193         }
194     }
195 
196 
197     private AtomicInteger incrementCounter(String messageId, AtomicInteger counter) throws ObjectStoreException
198     {
199         counter = getCounter(messageId,  counter, true);
200         counter.incrementAndGet();
201         return counter;
202     }
203 
204     private AtomicInteger getCounter(String messageId, AtomicInteger counter, boolean create) throws ObjectStoreException
205     {
206         if (counter != null)
207         {
208             return counter;
209         }
210         boolean counterExists = store.contains(messageId);
211         if (counterExists)
212         {
213             return store.retrieve(messageId);
214         }
215         if (create)
216         {
217             try
218             {
219                 counter = new AtomicInteger();
220                 store.store(messageId, counter);
221             }
222             catch (ObjectAlreadyExistsException e)
223             {
224                 counter = store.retrieve(messageId);
225             }
226         }
227         return counter;
228     }
229 
230 
231     private String getIdForEvent(MuleEvent event) throws Exception
232     {
233         if (useSecureHash)
234         {
235             Object payload = event.getMessage().getPayload();
236             byte[] bytes = (byte[]) objectToByteArray.transform(payload);
237             if (payload instanceof InputStream)
238             {
239                 // We've consumed the stream.
240                 event.getMessage().setPayload(bytes);
241             }
242             MessageDigest md = MessageDigest.getInstance(messageDigestAlgorithm);
243             byte[] digestedBytes = md.digest(bytes);
244             return (String)byteArrayToHexString.transform(digestedBytes);
245         }
246         else
247         {
248              return event.getMuleContext().getExpressionManager().parse(idExpression, event.getMessage(), true);
249         }
250     }
251 
252     public boolean isUseSecureHash()
253     {
254         return useSecureHash;
255     }
256 
257     public void setUseSecureHash(boolean useSecureHash)
258     {
259         this.useSecureHash = useSecureHash;
260     }
261 
262     public String getMessageDigestAlgorithm()
263     {
264         return messageDigestAlgorithm;
265     }
266 
267     public void setMessageDigestAlgorithm(String messageDigestAlgorithm)
268     {
269         this.messageDigestAlgorithm = messageDigestAlgorithm;
270     }
271 
272     public String getIdExpression()
273     {
274         return idExpression;
275     }
276 
277     public void setIdExpression(String idExpression)
278     {
279         this.idExpression = idExpression;
280     }
281 }