/******************************************************************************
* 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;
}
}