package com.googlecode.objectify.cache; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * <p> * A Future<?> wrapper that executes an abstract method with the result at some point after * the data becomes available. A "best effort" is made to ensure execution, but it may be * left untriggered until the end of a request. * </p> * * <p> * Notification will happen ONCE: * </p> * * <ul> * <li>After get() is called</li> * <li>When the future is done and isDone() is called</li> * <li>At the end of a request that has the AsyncCacheFilter enabled.</li> * </ul> * * <p>Use the AsyncCacheFilter for normal requests. For situations where a filter is not appropriate * (ie, the remote api) be sure to call PendingFutures.completeAllPendingFutures() manually.</p> * * <p>Note that if you are using this with Objectify, you probably want to use ObjectifyFilter.complete() * rather than PendingFutures or AsyncCacheFilter static methods.</p> * * @author Jeff Schnitzer <jeff@infohazard.org> */ abstract public class TriggerFuture<T> implements Future<T> { /** Wrap the raw Future<?> */ protected Future<T> raw; /** If we have run the trigger() method already */ boolean triggered = false; /** Wrap a normal Future<?> */ public TriggerFuture(Future<T> raw) { this.raw = raw; // We now need to register ourself so that we'll get checked at future API calls PendingFutures.addPending(this); } /** * This method will be called ONCE upon completion of the future, successful or not. * Beware that this.get() may throw an exception. */ abstract protected void trigger(); /* (non-Javadoc) * @see java.util.concurrent.Future#cancel(boolean) */ @Override public boolean cancel(boolean mayInterruptIfRunning) { throw new UnsupportedOperationException("This makes my head spin. Don't do it."); } /* (non-Javadoc) * @see java.util.concurrent.Future#isCancelled() */ @Override public boolean isCancelled() { return this.raw.isCancelled(); } /** * This version also checks to see if we are done and we still need to call the trigger. * If so, it calls it. * * @see java.util.concurrent.Future#isDone() */ @Override public boolean isDone() { boolean done = this.raw.isDone(); if (!triggered && done) { this.triggered = true; PendingFutures.removePending(this); this.trigger(); } return done; } /* (non-Javadoc) * @see java.util.concurrent.Future#get() */ @Override public T get() throws InterruptedException, ExecutionException { try { return this.raw.get(); } finally { this.isDone(); } } /* (non-Javadoc) * @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit) */ @Override public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { try { return this.raw.get(timeout, unit); } finally { this.isDone(); } } }