/* Copyright (c) 2010, skobbler GmbH * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.openstreetmap.josm.plugins.mapdust.util.retry; import org.openstreetmap.josm.Main; /** * A <code>RetryAgent</code> attempts several times to execute a given * operation. It can run in several modes (see {@link RetryMode} for details), * but no matter what mode it is in, it will run at least once and it will stop * trying after the first success. This class has two abstract methods which * have to be implemented specifically by any instance: the target method and * the cleanup method. * * @author rrainn * @param <T> the type of the object returned by the target method */ public abstract class RetryAgent<T> { /** The <code>RetrySetup</code> object */ private RetrySetup setup; /** * Builds a new <code>RetryAgent</code> that will run as specified by a * default setup. */ public RetryAgent() { this(RetrySetup.DEFAULT); } /** * Builds a new <code>RetryAgent</code> that will run as specified by the * given <code>RetrySetup</code>. * * @param setup the metadata specifying the running parameters for this * <code>RetryAgent</code> */ public RetryAgent(RetrySetup setup) { this.setup = setup; } /** * The target operation of the <code>RetryAgent</code>. This method will be * called several times (until it returns successfully, or until the number * of attempts has been exhausted). * * @return what this method returns must be defined by the implementer * @throws Exception the cases in which this method returns this mode of * exception must be defined by the implementer */ protected abstract T target() throws Exception; /** * The cleanup operation of the <code>RetryAgent</code>. This method will be * called after each call of the target method. * * @throws Exception the cases in which this method returns this mode of * exception must be defined by the implementer */ protected abstract void cleanup() throws Exception; /** * Launches the <code>RetryAgent</code>'s execution. This involves at least * one run of the target and cleanup methods. * * @return whatever the target method is returning * @throws Exception in case the target method failed on every attempt and * the running conditions have been exhausted */ public T run() throws Exception { T result; if (setup.getMode() == RetryMode.COUNTED) { result = runCounted(); } else if (setup.getMode() == RetryMode.TIMED) { result = runTimed(); } else { throw new RuntimeException("Unsupported retry mode: '" + setup.getMode() + "'"); } return result; } /** * Launches the <code>RetryAgent</code>'s counted execution. * * @return whatever the target method is returning * @throws Exception in case the target method failed on every attempt and * the running conditions have been exhausted */ private T runCounted() throws Exception { T result = null; boolean success = false; int attempts = setup.getStopCondition(); int delay = setup.getBaseDelay(); do { attempts--; try { result = target(); success = true; } catch (Exception e) { if (attempts <= 0) { throw e; } try { Thread.sleep(delay); } catch (InterruptedException e1) { Main.error(e1); // throw e; } delay = delay * 3 / 2; } finally { try { cleanup(); } catch (Exception e) { /* if it can't be cleaned up, there's nothing to do */ Main.error("Could not clean up", e); } } } while (!success && attempts > 0); return result; } /** * Launches the <code>RetryAgent</code>'s timed execution. * * @return whatever the target method is returning * @throws Exception in case the target method failed on every attempt and * the running conditions have been exhausted */ private T runTimed() throws Exception { T result = null; boolean success = false; int delay = setup.getBaseDelay(); int maxTime = setup.getStopCondition(); do { long time = System.currentTimeMillis(); try { result = target(); success = true; } catch (Exception e) { // LOG.debug("Attempt failed after " // + (setup.getStopCondition() - maxTime) + " ms", e); if (maxTime - delay <= 0) { throw e; } try { Thread.sleep(delay); } catch (InterruptedException e1) { Main.error(e1); throw e; } delay = delay * 3 / 2; } finally { try { cleanup(); } catch (Exception e) { /* if it can't be cleaned up, there's nothing to do */ Main.error("Could not clean up", e); } } time = System.currentTimeMillis() - time; maxTime -= time; } while (!success && maxTime > 0); return result; } }