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    }