/*******************************************************************************
* Copyright (c) 2008, 2012 Wind River Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.debug.test.util;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.tcf.protocol.Protocol;
/**
* @since 2.2
*/
public abstract class Transaction<V> implements Future<V>, Runnable {
/**
* The exception we throw when the client transaction logic asks us to
* validate a cache object that is stale (or has never obtained a value from
* the source)
*/
public static final InvalidCacheException INVALID_CACHE_EXCEPTION = new InvalidCacheException();
/** The request object we've been given to set the transaction results in */
private DataCallback<V> fRm;
private Query<V> fQuery;
public static class InvalidCacheException extends Exception {
private static final long serialVersionUID = 1L;
}
/**
* Kicks off the transaction. We'll either complete the request monitor
* immediately if all the data points the transaction needs are cached and
* valid, or we'll end up asynchronously completing the monitor if and when
* either (a) all the data points are available and up-to-date, or (b)
* obtaining them from the source encountered an error. Note that there is
* potential in (b) for us to never complete the monitor. If one or more
* data points are perpetually becoming stale, then we'll indefinitely wait
* for them to stabilize. The caller should cancel its request monitor in
* order to get us to stop waiting.
*
* @param rm Request completion monitor.
*/
public void request(DataCallback<V> rm) {
if (fRm != null) {
assert fRm.isCanceled();
fRm.done();
}
fRm = rm;
assert fRm != null;
run();
}
protected void preProcess() {}
protected void postProcess(boolean done, V data, Throwable error) {}
protected boolean processUnchecked() {
try {
// Execute the transaction logic
V data = process();
// No exception means all cache objects used by the transaction
// were valid and up to date. Complete the request
setData(data);
return true;
}
catch (InvalidCacheException e) {
// At least one of the cache objects was stale/unset. Keep the
// request monitor in the incomplete state, thus leaving our client
// "waiting" (asynchronously). We'll get called again once the cache
// objects are updated, thus re-starting the whole transaction
// attempt.
return false;
}
catch (Throwable e) {
// At least one of the cache objects encountered a failure obtaining
// the data from the source. Complete the request.
setError(e);
return true;
}
}
/**
* The transaction logic--code that tries to synchronously make use of,
* usually, multiple data points that are normally obtained asynchronously.
* Each data point is represented by a cache object. The transaction logic
* must check the validity of each cache object just prior to using it
* (calling its getData()). It should do that check by calling one of our
* validate() methods. Those methods will throw InvalidCacheException if the
* cached data is invalid (stale, e.g.,) or CoreException if an error was
* encountered the last time it got data form the source. The exception will
* abort the transaction, but in the case of InvalidCacheException, we
* schedule an asynchronous call that will re-invoke the transaction
* logic once the cache object has been updated from the source.
*
* @return the cached data if it's valid, otherwise an exception is thrown
* @throws Transaction.InvalidCacheException Exception indicating that a
* cache is not valid and transaction will need to be rescheduled.
* @throws CoreException Exception indicating that one of the caches is
* in error state and transaction cannot be processed.
*/
protected V process() throws InvalidCacheException, ExecutionException {
return null;
}
/**
* Can be called only while in process().
* @param data
*/
protected void setData(V data) {
assert Protocol.isDispatchThread();
fRm.setData(data);
}
/**
* Can be called only while in process().
* @param data
*/
protected void setError(Throwable error) {
assert Protocol.isDispatchThread();
fRm.setError(error);
}
/**
* Method which invokes the transaction logic and handles any exception that
* may result. If that logic encounters a stale/unset cache object, then we
* simply do nothing. This method can be invoked by transaction logic when
* caches have become valid, thus unblocking transaction processing.
*/
public void run() {
// If execute is called after transaction completes (as a result of a
// cancelled request completing for example), ignore it.
if (fRm == null) {
return;
}
if (fRm.isCanceled()) {
fRm.done();
fRm = null;
return;
}
preProcess();
if (processUnchecked()) {
postProcess(true, fRm.getData(), fRm.getError());
fRm.done();
fRm = null;
} else {
postProcess(false, null, null);
}
}
/**
* Clients must call one of the validate methods prior to using (calling
* getData()) on data cache object.
*
* @param cache
* the object being validated
* @throws InvalidCacheException
* if the data is stale/unset
* @throws ExecutionException
* if an error was encountered getting the data from the source
*/
public <T> T validate(ICache<T> cache) throws InvalidCacheException, ExecutionException {
if (cache.isValid()) {
if (cache.getError() != null) {
throw new ExecutionException(cache.getError());
}
return cache.getData();
} else {
// Throw the invalid cache exception, but first ask the cache to
// update itself from its source, and schedule a re-attempt of the
// transaction logic to occur when the stale/unset cache has been
// updated
cache.wait(new Callback(fRm) {
@Override
protected void handleCompleted() {
run();
}
});
throw INVALID_CACHE_EXCEPTION;
}
}
/**
* See {@link #validate(ICache)}. This variant simply validates
* multiple cache objects.
*/
public void validate(ICache<?> ... caches) throws InvalidCacheException, ExecutionException {
validate(Arrays.asList(caches));
}
/**
* See {@link #validate(ICache)}. This variant validates
* multiple cache objects.
*/
public void validate(@SuppressWarnings("rawtypes") Iterable caches) throws InvalidCacheException, ExecutionException {
// Check if any of the caches have errors:
boolean allValid = true;
for (Object cacheObj : caches) {
ICache<?> cache = (ICache<?>)cacheObj;
if (cache.isValid()) {
if (cache.getError() != null) {
throw new ExecutionException(cache.getError());
}
} else {
allValid = false;
}
}
if (!allValid) {
// Throw the invalid cache exception, but first schedule a
// re-attempt of the transaction logic, to occur when the
// stale/unset cache objects have been updated
AggregateCallback countringRm = new AggregateCallback(fRm) {
@Override
protected void handleCompleted() {
run();
}
};
int count = 0;
for (Object cacheObj : caches) {
ICache<?> cache = (ICache<?>)cacheObj;
if (!cache.isValid()) {
cache.wait(countringRm);
count++;
}
}
countringRm.setDoneCount(count);
throw INVALID_CACHE_EXCEPTION;
}
}
/**
* See {@link #validate(ICache)}. This variant does not throw exceptions,
* instead it returns <code>false</code> if the cache is not valid. If the
* given cache is valid, and this method returns <code>true</code>, clients
* must still check if the cache contains an error before retrieving its
* data through {@link ICache#getData()}.
*
* @param cache the object being validated
* @return returns <code>false</code> if the cache is not yet valid and
* transaction processing should be interrupted.
*/
public boolean validateUnchecked(ICache<?> cache) {
if (cache.isValid()) {
return true;
} else {
// Just sk the cache to update itself from its source, and schedule a
// re-attempt of the transaction logic to occur when the stale/unset
// cache has been updated
cache.wait(new Callback(fRm) {
@Override
protected void handleCompleted() {
run();
}
});
return false;
}
}
/**
* See {@link #validate(ICache)}. This variant validates
* multiple cache objects.
*/
public boolean validateUnchecked(ICache<?> ... caches) {
return validateUnchecked(Arrays.asList(caches));
}
/**
* See {@link #validate(ICache)}. This variant validates
* multiple cache objects.
*/
public boolean validateUnchecked(@SuppressWarnings("rawtypes") Iterable caches) {
// Check if all caches are valid
boolean allValid = true;
for (Object cacheObj : caches) {
ICache<?> cache = (ICache<?>)cacheObj;
if (!cache.isValid()) {
allValid = false;
}
}
if (allValid) {
return true;
}
// Just schedule a re-attempt of the transaction logic, to occur
// when the stale/unset cache objects have been updated
AggregateCallback countringRm = new AggregateCallback(fRm) {
@Override
protected void handleCompleted() {
run();
}
};
int count = 0;
for (Object cacheObj : caches) {
ICache<?> cache = (ICache<?>)cacheObj;
if (!cache.isValid()) {
cache.wait(countringRm);
count++;
}
}
countringRm.setDoneCount(count);
return false;
}
private synchronized Query<V> getQuery(boolean create) {
if (fQuery == null && create) {
fQuery = new Query<V>() {
@Override
protected void execute(DataCallback<V> callback) {
request(callback);
}
};
fQuery.invoke();
}
return fQuery;
}
public boolean cancel(boolean mayInterruptIfRunning) {
Query<V> query = getQuery(false);
if (query != null) {
return query.cancel(mayInterruptIfRunning);
}
return false;
}
public boolean isCancelled() {
Query<V> query = getQuery(false);
if (query != null) {
return query.isCancelled();
}
return false;
}
public boolean isDone() {
Query<V> query = getQuery(false);
if (query != null) {
return query.isDone();
}
return false;
}
public V get() throws InterruptedException, ExecutionException {
return getQuery(true).get();
}
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return getQuery(true).get(timeout, unit);
}
}