/****************************************************************************** * Copyright (c) 2006, 2010 VMware Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0 * is available at http://www.opensource.org/licenses/apache2.0.php. * You may elect to redistribute this code under either of these licenses. * * Contributors: * VMware Inc. *****************************************************************************/ package org.eclipse.gemini.blueprint.service.importer.support.internal.support; import org.springframework.util.Assert; /** * Wrapper retry template. This class does specialized retries using a given * callback and lock. * * @author Costin Leau */ public class RetryTemplate { private static final int hashCode = RetryTemplate.class.hashCode() * 13; public static final long DEFAULT_WAIT_TIME = 1000; private final Object monitor = new Object(); private final Object notificationLock; private long waitTime = DEFAULT_WAIT_TIME; // wait threshold (in millis) private static final long WAIT_THRESHOLD = 3; public RetryTemplate(long waitTime, Object notificationLock) { Assert.isTrue(waitTime >= 0, "waitTime must be positive"); Assert.notNull(notificationLock, "notificationLock must be non null"); synchronized (monitor) { this.waitTime = waitTime; this.notificationLock = notificationLock; } } public RetryTemplate(Object notificationLock) { this(DEFAULT_WAIT_TIME, notificationLock); } /** * Main retry method. Executes the callback until it gets completed. The * callback will get executed the number of {@link #retryNumbers} while * waiting in-between for the {@link #DEFAULT_WAIT_TIME} amount. * * Before bailing out, the callback will be called one more time. Thus, in * case of being unsuccessful, the default value of the callback is * returned. * * @param callback * @return */ public <T> T execute(RetryCallback<T> callback) { long waitTime; synchronized (monitor) { waitTime = this.waitTime; } boolean retry = false; long initialStart = 0, start = 0, stop = 0; long waitLeft = waitTime; boolean startWaiting = false; do { T result = callback.doWithRetry(); if (callback.isComplete(result)) { if (startWaiting) { callbackSucceeded(stop); } return result; } if (!startWaiting) { startWaiting = true; onMissingTarget(); // initial wait initialStart = System.currentTimeMillis(); } if (waitLeft > 0) { try { start = System.currentTimeMillis(); synchronized (notificationLock) { // Do NOT use Thread.sleep() here - it does not release // locks. notificationLock.wait(waitTime); } // local wait timer stop = System.currentTimeMillis(); waitLeft -= (stop - start); // total wait timer stop -= initialStart; } catch (InterruptedException ex) { stop = System.currentTimeMillis() - initialStart; callbackFailed(stop); throw new RuntimeException("Retry failed; interrupted while waiting", ex); } } retry = false; // handle reset cases synchronized (monitor) { // has there been a reset in place ? if (waitTime != this.waitTime) { // start counting again retry = true; waitTime = this.waitTime; waitLeft = waitTime; } } } while (retry || waitLeft > WAIT_THRESHOLD); T result = callback.doWithRetry(); stop = System.currentTimeMillis() - initialStart; if (callback.isComplete(result)) { callbackSucceeded(stop); return result; } else { callbackFailed(stop); return null; } } /** * Template method invoked if the backing service is missing. */ protected void onMissingTarget() { } /** * Template method invoked when the retry succeeded. * * @param stop the time it took to execute the call (including waiting for * the service) */ protected void callbackSucceeded(long stop) { } /** * Template method invoked when the retry has failed. * * @param stop the time it took to execute the call (including waiting for * the service) */ protected void callbackFailed(long stop) { } /** * Reset the retry template, by applying the new values. Any in-flight * waiting is interrupted and restarted using the new values. * * @param retriesNumber * @param waitTime */ public void reset(long waitTime) { synchronized (monitor) { this.waitTime = waitTime; } synchronized (notificationLock) { notificationLock.notifyAll(); } } public long getWaitTime() { synchronized (monitor) { return waitTime; } } public boolean equals(Object other) { if (this == other) return true; if (other instanceof RetryTemplate) { RetryTemplate oth = (RetryTemplate) other; return (getWaitTime() == oth.getWaitTime()); } return false; } public int hashCode() { return hashCode; } }