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