/* ServiceRetrier.java * * Copyright 2009-2012 Comcast Interactive Media, LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.fishwife.jrugged; import java.util.concurrent.Callable; /** * Calls a service multiple times until the call succeeds or the maximum number of tries * is exceeded. A delay can be configured between calls and that delay can be constant or * configured to double between each call. */ public class ServiceRetrier implements ServiceWrapper { public static final int DEFAULT_MAX_TRIES = 10; public static final int DEFAULT_DELAY = 1000; private int _delay = DEFAULT_DELAY; private int _maxTries = DEFAULT_MAX_TRIES; private boolean _doubleDelay = false; private boolean _throwCauseException = false; private Class<? extends Throwable>[] _retryOn = null; public ServiceRetrier(final int delay, final int maxTries) { setDelay(delay); setMaxTries(maxTries); } public ServiceRetrier(final int delay, final int maxTries, final boolean doubleDelay, final boolean throwCauseException, final Class<? extends Throwable>[] retryOn) { setDelay(delay); setMaxTries(maxTries); setDoubleDelay(doubleDelay); setThrowCauseException(throwCauseException); setRetryOn(retryOn); } public ServiceRetrier() { } @Override public <V> V invoke(final Callable<V> c) throws Exception { int tries = 0; int delay = _delay; while (true) { try { return c.call(); } catch (final Exception cause) { // If this type of Exception should be retried... if (shouldRetry(cause)) { tries++; // Don't delay after max tries reached. if (tries < _maxTries) { if (delay > 0) { sleep(delay); } // Double the next delay if configured to do so. if (_doubleDelay) { delay = delay * 2; } // Try again. continue; } } if (_throwCauseException) { throw cause; } throw new Exception("Call failed " + tries + " times", cause); } } } private boolean shouldRetry(final Throwable cause) { if (_retryOn == null || _retryOn.length == 0) { return true; } for (final Class<? extends Throwable> clazz : _retryOn) { if (clazz.isInstance(cause)) { return true; } } return false; } @Override public void invoke(final Runnable r) throws Exception { final Callable<Void> adapter = new CallableAdapter<>(r); invoke(adapter); } @Override public <T> T invoke(final Runnable r, final T result) throws Exception { final Callable<T> adapter = new CallableAdapter<>(r, result); return invoke(adapter); } public int getDelay() { return _delay; } public void setDelay(final int delay) { if (delay < 0) { throw new IllegalArgumentException("Delay cannot be negative"); } _delay = delay; } public int getMaxTries() { return _maxTries; } public void setMaxTries(final int maxTries) { if (maxTries < 1) { throw new IllegalArgumentException( "Maximum number of tries must be greater than zero"); } _maxTries = maxTries; } public boolean isDoubleDelay() { return _doubleDelay; } public void setDoubleDelay(final boolean doubleDelay) { _doubleDelay = doubleDelay; } public boolean isThrowCauseException() { return _throwCauseException; } public void setThrowCauseException(final boolean throwCauseException) { _throwCauseException = throwCauseException; } public Class<? extends Throwable>[] getRetryOn() { return _retryOn; } public void setRetryOn(final Class<? extends Throwable>[] retryOn) { _retryOn = retryOn; } protected void sleep(final long millis) { try { Thread.sleep(millis); } catch (final InterruptedException e) { // Nothing much to do here. } } }