/*
* Copyright 2006-2010 Amazon Technologies, Inc. or its affiliates.
* Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
* of Amazon Technologies, Inc. or its affiliates. All rights reserved.
*
* 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 com.amazon.carbonado;
import java.util.Random;
/**
* General checked exception thrown when accessing a {@link Repository}.
*
*
Some repository exceptions are the result of an optimistic lock failure
* or deadlock. One resolution strategy is to exit all transactions and try the
* operation again, after waiting some bounded random amount of time. As a
* convenience, this class provides a mechanism to support such a backoff
* strategy. For example:
*
*
* // Retry at most three more times
* for (int retryCount = 3;;) {
* try {
* ...
* myObject.load();
* ...
* myObject.update();
* break;
* } catch (OptimisticLockException e) {
* // Wait up to one second before retrying
* retryCount = e.backoff(e, retryCount, 1000);
* }
* }
*
*
* If the retry count is zero (or less) when backoff is called, then the
* original exception is rethrown, indicating retry failure.
*
* @author Brian S O'Neill
*/
public class RepositoryException extends Exception {
private static final long serialVersionUID = 7261406895435249366L;
/**
* One strategy for resolving an optimistic lock failure is to try the
* operation again, after waiting some bounded random amount of time. This
* method is provided as a convenience, to support such a random wait.
*
* A retry count is required as well, which is decremented and returned by
* this method. If the retry count is zero (or less) when this method is
* called, then this exception is thrown again, indicating retry failure.
*
* @param retryCount current retry count, if zero, throw this exception again
* @param milliseconds upper bound on the random amount of time to wait
* @return retryCount minus one
* @throws E if retry count is zero
*/
public static int backoff(E e, int retryCount, int milliseconds)
throws E
{
if (retryCount <= 0) {
// Workaround apparent compiler bug.
org.cojen.util.ThrowUnchecked.fire(e);
}
if (milliseconds > 0) {
Random rnd = cRandom;
if (rnd == null) {
cRandom = rnd = new Random();
}
if ((milliseconds = rnd.nextInt(milliseconds)) > 0) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e2) {
}
return retryCount - 1;
}
}
Thread.yield();
return retryCount - 1;
}
private static Random cRandom;
public RepositoryException() {
super();
}
public RepositoryException(String message) {
super(message);
}
public RepositoryException(String message, Throwable cause) {
super(message, cause);
}
public RepositoryException(Throwable cause) {
super(cause);
}
/**
* Recursively calls getCause, until the root cause is found. Returns this
* if no root cause.
*/
public Throwable getRootCause() {
Throwable cause = this;
while (cause.getCause() != null) {
cause = cause.getCause();
}
return cause;
}
/**
* Converts RepositoryException into an appropriate PersistException.
*/
public final PersistException toPersistException() {
return toPersistException(null);
}
/**
* Converts RepositoryException into an appropriate PersistException, prepending
* the specified message. If message is null, original exception message is
* preserved.
*
* @param message message to prepend, which may be null
*/
public final PersistException toPersistException(final String message) {
Throwable cause;
if (this instanceof PersistException) {
cause = this;
} else {
cause = getCause();
}
if (cause == null) {
cause = this;
} else if (cause instanceof PersistException && message == null) {
return (PersistException) cause;
}
String causeMessage = cause.getMessage();
if (causeMessage == null) {
causeMessage = message;
} else if (message != null) {
causeMessage = message + " : " + causeMessage;
}
return makePersistException(causeMessage, cause);
}
/**
* Converts RepositoryException into an appropriate FetchException.
*/
public final FetchException toFetchException() {
return toFetchException(null);
}
/**
* Converts RepositoryException into an appropriate FetchException, prepending
* the specified message. If message is null, original exception message is
* preserved.
*
* @param message message to prepend, which may be null
*/
public final FetchException toFetchException(final String message) {
Throwable cause;
if (this instanceof FetchException) {
cause = this;
} else {
cause = getCause();
}
if (cause == null) {
cause = this;
} else if (cause instanceof FetchException && message == null) {
return (FetchException) cause;
}
String causeMessage = cause.getMessage();
if (causeMessage == null) {
causeMessage = message;
} else if (message != null) {
causeMessage = message + " : " + causeMessage;
}
return makeFetchException(causeMessage, cause);
}
/**
* Subclasses can override this to provide a more specialized exception.
*
* @param message exception message, which may be null
* @param cause non-null cause
*/
protected PersistException makePersistException(String message, Throwable cause) {
return new PersistException(message, cause);
}
/**
* Subclasses can override this to provide a more specialized exception.
*
* @param message exception message, which may be null
* @param cause non-null cause
*/
protected FetchException makeFetchException(String message, Throwable cause) {
return new FetchException(message, cause);
}
}