1
2
3
4
5
6
7
8
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
37
38
39
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
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 }