001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.processor;
018
019 import java.io.Serializable;
020 import java.util.Random;
021
022 import org.apache.camel.Exchange;
023 import org.apache.camel.LoggingLevel;
024 import org.apache.camel.Predicate;
025 import org.apache.camel.util.ObjectHelper;
026 import org.apache.commons.logging.Log;
027 import org.apache.commons.logging.LogFactory;
028
029 // Code taken from the ActiveMQ codebase
030
031 /**
032 * The policy used to decide how many times to redeliver and the time between
033 * the redeliveries before being sent to a <a
034 * href="http://camel.apache.org/dead-letter-channel.html">Dead Letter
035 * Channel</a>
036 * <p>
037 * The default values are:
038 * <ul>
039 * <li>maximumRedeliveries = 0</li>
040 * <li>redeliverDelay = 1000L (the initial delay)</li>
041 * <li>maximumRedeliveryDelay = 60 * 1000L</li>
042 * <li>backOffMultiplier = 2</li>
043 * <li>useExponentialBackOff = false</li>
044 * <li>collisionAvoidanceFactor = 0.15d</li>
045 * <li>useCollisionAvoidance = false</li>
046 * <li>retriesExhaustedLogLevel = LoggingLevel.ERROR</li>
047 * <li>retryAttemptedLogLevel = LoggingLevel.DEBUG</li>
048 * <li>logRetryStackTrace = false</li>
049 * <li>logStackTrace = true</li>
050 * </ul>
051 * <p/>
052 * Setting the maximumRedeliveries to a negative value such as -1 will then always redeliver (unlimited).
053 * Setting the maximumRedeliveries to 0 will disable redelivery.
054 * <p/>
055 * This policy can be configured either by one of the following two settings:
056 * <ul>
057 * <li>using conventional options, using all the options defined above</li>
058 * <li>using delay pattern to declare intervals for delays</li>
059 * </ul>
060 * <p/>
061 * <b>Note:</b> If using delay patterns then the following options is not used (delay, backOffMultiplier, useExponentialBackOff, useCollisionAvoidance)
062 * <p/>
063 * <b>Using delay pattern</b>:
064 * <br/>The delay pattern syntax is: <tt>limit:delay;limit 2:delay 2;limit 3:delay 3;...;limit N:delay N</tt>.
065 * <p/>
066 * How it works is best illustrate with an example with this pattern: <tt>delayPattern=5:1000;10:5000:20:20000</tt>
067 * <br/>The delays will be for attempt in range 0..4 = 0 millis, 5..9 = 1000 millis, 10..19 = 5000 millis, >= 20 = 20000 millis.
068 * <p/>
069 * If you want to set a starting delay, then use 0 as the first limit, eg: <tt>0:1000;5:5000</tt> will use 1 sec delay
070 * until attempt number 5 where it will use 5 seconds going forward.
071 *
072 * @version $Revision: 905291 $
073 */
074 public class RedeliveryPolicy implements Cloneable, Serializable {
075 protected static transient Random randomNumberGenerator;
076 private static final long serialVersionUID = -338222777701473252L;
077 private static final transient Log LOG = LogFactory.getLog(RedeliveryPolicy.class);
078
079 protected long redeliverDelay = 1000L;
080 protected int maximumRedeliveries;
081 protected long maximumRedeliveryDelay = 60 * 1000L;
082 protected double backOffMultiplier = 2;
083 protected boolean useExponentialBackOff;
084 // +/-15% for a 30% spread -cgs
085 protected double collisionAvoidanceFactor = 0.15d;
086 protected boolean useCollisionAvoidance;
087 protected LoggingLevel retriesExhaustedLogLevel = LoggingLevel.ERROR;
088 protected LoggingLevel retryAttemptedLogLevel = LoggingLevel.DEBUG;
089 protected boolean logStackTrace = true;
090 protected boolean logRetryStackTrace;
091 protected String delayPattern;
092
093 public RedeliveryPolicy() {
094 }
095
096 @Override
097 public String toString() {
098 return "RedeliveryPolicy[maximumRedeliveries=" + maximumRedeliveries
099 + ", redeliverDelay=" + redeliverDelay
100 + ", maximumRedeliveryDelay=" + maximumRedeliveryDelay
101 + ", retriesExhaustedLogLevel=" + retriesExhaustedLogLevel
102 + ", retryAttemptedLogLevel=" + retryAttemptedLogLevel
103 + ", logStackTrace=" + logStackTrace
104 + ", logRetryStackTrace=" + logRetryStackTrace
105 + ", useExponentialBackOff=" + useExponentialBackOff
106 + ", backOffMultiplier=" + backOffMultiplier
107 + ", useCollisionAvoidance=" + useCollisionAvoidance
108 + ", collisionAvoidanceFactor=" + collisionAvoidanceFactor
109 + ", delayPattern=" + delayPattern + "]";
110 }
111
112 public RedeliveryPolicy copy() {
113 try {
114 return (RedeliveryPolicy)clone();
115 } catch (CloneNotSupportedException e) {
116 throw new RuntimeException("Could not clone: " + e, e);
117 }
118 }
119
120 /**
121 * Returns true if the policy decides that the message exchange should be
122 * redelivered.
123 *
124 * @param exchange the current exchange
125 * @param redeliveryCounter the current retry counter
126 * @param retryUntil an optional predicate to determine if we should redeliver or not
127 * @return true to redeliver, false to stop
128 */
129 public boolean shouldRedeliver(Exchange exchange, int redeliveryCounter, Predicate retryUntil) {
130 // predicate is always used if provided
131 if (retryUntil != null) {
132 return retryUntil.matches(exchange);
133 }
134
135 if (getMaximumRedeliveries() < 0) {
136 // retry forever if negative value
137 return true;
138 }
139 // redeliver until we hit the max
140 return redeliveryCounter <= getMaximumRedeliveries();
141 }
142
143
144 /**
145 * Calculates the new redelivery delay based on the last one then sleeps for the necessary amount of time
146 *
147 * @param redeliveryDelay previous redelivery delay
148 * @param redeliveryCounter number of previous redelivery attempts
149 * @return the calculate delay
150 * @throws InterruptedException is thrown if the sleep is interrupted likely because of shutdown
151 */
152 public long sleep(long redeliveryDelay, int redeliveryCounter) throws InterruptedException {
153 redeliveryDelay = calculateRedeliveryDelay(redeliveryDelay, redeliveryCounter);
154
155 if (redeliveryDelay > 0) {
156 if (LOG.isDebugEnabled()) {
157 LOG.debug("Sleeping for: " + redeliveryDelay + " millis until attempting redelivery");
158 }
159 Thread.sleep(redeliveryDelay);
160 }
161 return redeliveryDelay;
162 }
163
164 protected long calculateRedeliveryDelay(long previousDelay, int redeliveryCounter) {
165 if (ObjectHelper.isNotEmpty(delayPattern)) {
166 // calculate delay using the pattern
167 return calculateRedeliverDelayUsingPattern(delayPattern, redeliveryCounter);
168 }
169
170 // calculate the delay using the conventional parameters
171 long redeliveryDelay;
172 if (previousDelay == 0) {
173 redeliveryDelay = redeliverDelay;
174 } else if (useExponentialBackOff && backOffMultiplier > 1) {
175 redeliveryDelay = Math.round(backOffMultiplier * previousDelay);
176 } else {
177 redeliveryDelay = previousDelay;
178 }
179
180 if (useCollisionAvoidance) {
181
182 /*
183 * First random determines +/-, second random determines how far to
184 * go in that direction. -cgs
185 */
186 Random random = getRandomNumberGenerator();
187 double variance = (random.nextBoolean() ? collisionAvoidanceFactor : -collisionAvoidanceFactor)
188 * random.nextDouble();
189 redeliveryDelay += redeliveryDelay * variance;
190 }
191
192 if (maximumRedeliveryDelay > 0 && redeliveryDelay > maximumRedeliveryDelay) {
193 redeliveryDelay = maximumRedeliveryDelay;
194 }
195
196 return redeliveryDelay;
197 }
198
199 /**
200 * Calculates the delay using the delay pattern
201 */
202 protected static long calculateRedeliverDelayUsingPattern(String delayPattern, int redeliveryCounter) {
203 String[] groups = delayPattern.split(";");
204 // find the group where the redelivery counter matches
205 long answer = 0;
206 for (String group : groups) {
207 long delay = Long.valueOf(ObjectHelper.after(group, ":"));
208 int count = Integer.valueOf(ObjectHelper.before(group, ":"));
209 if (count > redeliveryCounter) {
210 break;
211 } else {
212 answer = delay;
213 }
214 }
215
216 return answer;
217 }
218
219
220 // Builder methods
221 // -------------------------------------------------------------------------
222
223 /**
224 * Sets the delay in milliseconds
225 */
226 public RedeliveryPolicy redeliverDelay(long delay) {
227 setRedeliverDelay(delay);
228 return this;
229 }
230
231 /**
232 * Sets the maximum number of times a message exchange will be redelivered
233 */
234 public RedeliveryPolicy maximumRedeliveries(int maximumRedeliveries) {
235 setMaximumRedeliveries(maximumRedeliveries);
236 return this;
237 }
238
239 /**
240 * Enables collision avoidance which adds some randomization to the backoff
241 * timings to reduce contention probability
242 */
243 public RedeliveryPolicy useCollisionAvoidance() {
244 setUseCollisionAvoidance(true);
245 return this;
246 }
247
248 /**
249 * Enables exponential backoff using the {@link #getBackOffMultiplier()} to
250 * increase the time between retries
251 */
252 public RedeliveryPolicy useExponentialBackOff() {
253 setUseExponentialBackOff(true);
254 return this;
255 }
256
257 /**
258 * Enables exponential backoff and sets the multiplier used to increase the
259 * delay between redeliveries
260 */
261 public RedeliveryPolicy backOffMultiplier(double multiplier) {
262 useExponentialBackOff();
263 setBackOffMultiplier(multiplier);
264 return this;
265 }
266
267 /**
268 * Enables collision avoidance and sets the percentage used
269 */
270 public RedeliveryPolicy collisionAvoidancePercent(double collisionAvoidancePercent) {
271 useCollisionAvoidance();
272 setCollisionAvoidancePercent(collisionAvoidancePercent);
273 return this;
274 }
275
276 /**
277 * Sets the maximum redelivery delay if using exponential back off.
278 * Use -1 if you wish to have no maximum
279 */
280 public RedeliveryPolicy maximumRedeliveryDelay(long maximumRedeliveryDelay) {
281 setMaximumRedeliveryDelay(maximumRedeliveryDelay);
282 return this;
283 }
284
285 /**
286 * Sets the logging level to use for log messages when retries have been exhausted.
287 */
288 public RedeliveryPolicy retriesExhaustedLogLevel(LoggingLevel retriesExhaustedLogLevel) {
289 setRetriesExhaustedLogLevel(retriesExhaustedLogLevel);
290 return this;
291 }
292
293 /**
294 * Sets the logging level to use for log messages when retries are attempted.
295 */
296 public RedeliveryPolicy retryAttemptedLogLevel(LoggingLevel retryAttemptedLogLevel) {
297 setRetryAttemptedLogLevel(retryAttemptedLogLevel);
298 return this;
299 }
300
301 /**
302 * Sets whether to log stacktraces for failed messages.
303 */
304 public RedeliveryPolicy logStackTrace(boolean logStackTrace) {
305 setLogStackTrace(logStackTrace);
306 return this;
307 }
308
309 /**
310 * Sets whether to log stacktrace for failed redelivery attempts
311 */
312 public RedeliveryPolicy logRetryStackTrace(boolean logRetryStackTrace) {
313 setLogRetryStackTrace(logRetryStackTrace);
314 return this;
315 }
316
317 /**
318 * Sets the delay pattern with delay intervals.
319 */
320 public RedeliveryPolicy delayPattern(String delayPattern) {
321 setDelayPattern(delayPattern);
322 return this;
323 }
324
325 /**
326 * Disables redelivery by setting maximum redeliveries to 0.
327 */
328 public RedeliveryPolicy disableRedelivery() {
329 setMaximumRedeliveries(0);
330 return this;
331 }
332
333 // Properties
334 // -------------------------------------------------------------------------
335
336 public long getRedeliverDelay() {
337 return redeliverDelay;
338 }
339
340 /**
341 * Sets the delay in milliseconds
342 */
343 public void setRedeliverDelay(long redeliverDelay) {
344 this.redeliverDelay = redeliverDelay;
345 // if max enabled then also set max to this value in case max was too low
346 if (maximumRedeliveryDelay > 0 && redeliverDelay > maximumRedeliveryDelay) {
347 this.maximumRedeliveryDelay = redeliverDelay;
348 }
349 }
350
351 public double getBackOffMultiplier() {
352 return backOffMultiplier;
353 }
354
355 /**
356 * Sets the multiplier used to increase the delay between redeliveries if
357 * {@link #setUseExponentialBackOff(boolean)} is enabled
358 */
359 public void setBackOffMultiplier(double backOffMultiplier) {
360 this.backOffMultiplier = backOffMultiplier;
361 }
362
363 public long getCollisionAvoidancePercent() {
364 return Math.round(collisionAvoidanceFactor * 100);
365 }
366
367 /**
368 * Sets the percentage used for collision avoidance if enabled via
369 * {@link #setUseCollisionAvoidance(boolean)}
370 */
371 public void setCollisionAvoidancePercent(double collisionAvoidancePercent) {
372 this.collisionAvoidanceFactor = collisionAvoidancePercent * 0.01d;
373 }
374
375 public double getCollisionAvoidanceFactor() {
376 return collisionAvoidanceFactor;
377 }
378
379 /**
380 * Sets the factor used for collision avoidance if enabled via
381 * {@link #setUseCollisionAvoidance(boolean)}
382 */
383 public void setCollisionAvoidanceFactor(double collisionAvoidanceFactor) {
384 this.collisionAvoidanceFactor = collisionAvoidanceFactor;
385 }
386
387 public int getMaximumRedeliveries() {
388 return maximumRedeliveries;
389 }
390
391 /**
392 * Sets the maximum number of times a message exchange will be redelivered.
393 * Setting a negative value will retry forever.
394 */
395 public void setMaximumRedeliveries(int maximumRedeliveries) {
396 this.maximumRedeliveries = maximumRedeliveries;
397 }
398
399 public long getMaximumRedeliveryDelay() {
400 return maximumRedeliveryDelay;
401 }
402
403 /**
404 * Sets the maximum redelivery delay.
405 * Use -1 if you wish to have no maximum
406 */
407 public void setMaximumRedeliveryDelay(long maximumRedeliveryDelay) {
408 this.maximumRedeliveryDelay = maximumRedeliveryDelay;
409 }
410
411 public boolean isUseCollisionAvoidance() {
412 return useCollisionAvoidance;
413 }
414
415 /**
416 * Enables/disables collision avoidance which adds some randomization to the
417 * backoff timings to reduce contention probability
418 */
419 public void setUseCollisionAvoidance(boolean useCollisionAvoidance) {
420 this.useCollisionAvoidance = useCollisionAvoidance;
421 }
422
423 public boolean isUseExponentialBackOff() {
424 return useExponentialBackOff;
425 }
426
427 /**
428 * Enables/disables exponential backoff using the
429 * {@link #getBackOffMultiplier()} to increase the time between retries
430 */
431 public void setUseExponentialBackOff(boolean useExponentialBackOff) {
432 this.useExponentialBackOff = useExponentialBackOff;
433 }
434
435 protected static synchronized Random getRandomNumberGenerator() {
436 if (randomNumberGenerator == null) {
437 randomNumberGenerator = new Random();
438 }
439 return randomNumberGenerator;
440 }
441
442 /**
443 * Sets the logging level to use for log messages when retries have been exhausted.
444 */
445 public void setRetriesExhaustedLogLevel(LoggingLevel retriesExhaustedLogLevel) {
446 this.retriesExhaustedLogLevel = retriesExhaustedLogLevel;
447 }
448
449 public LoggingLevel getRetriesExhaustedLogLevel() {
450 return retriesExhaustedLogLevel;
451 }
452
453 /**
454 * Sets the logging level to use for log messages when retries are attempted.
455 */
456 public void setRetryAttemptedLogLevel(LoggingLevel retryAttemptedLogLevel) {
457 this.retryAttemptedLogLevel = retryAttemptedLogLevel;
458 }
459
460 public LoggingLevel getRetryAttemptedLogLevel() {
461 return retryAttemptedLogLevel;
462 }
463
464 public String getDelayPattern() {
465 return delayPattern;
466 }
467
468 /**
469 * Sets an optional delay pattern to use instead of fixed delay.
470 */
471 public void setDelayPattern(String delayPattern) {
472 this.delayPattern = delayPattern;
473 }
474
475 public boolean isLogStackTrace() {
476 return logStackTrace;
477 }
478
479 /**
480 * Sets whether stack traces should be logged or not
481 */
482 public void setLogStackTrace(boolean logStackTrace) {
483 this.logStackTrace = logStackTrace;
484 }
485
486 public boolean isLogRetryStackTrace() {
487 return logRetryStackTrace;
488 }
489
490 /**
491 * Sets whether stack traces should be logged or not
492 */
493 public void setLogRetryStackTrace(boolean logRetryStackTrace) {
494 this.logRetryStackTrace = logRetryStackTrace;
495 }
496
497 }