package org.frameworkset.web.request.async; import java.util.PriorityQueue; import java.util.concurrent.Callable; import org.apache.log4j.Logger; import org.frameworkset.util.Assert; import org.frameworkset.web.servlet.mvc.NativeWebRequest; /** * {@code DeferredResult} provides an alternative to using a {@link Callable} for * asynchronous request processing. While a {@code Callable} is executed concurrently * on behalf of the application, with a {@code DeferredResult} the application can * produce the result from a thread of its choice. * * <p>Subclasses can extend this class to easily associate additional data or behavior * with the {@link DeferredResult}. For example, one might want to associate the user * used to create the {@link DeferredResult} by extending the class and adding an * additional property for the user. In this way, the user could easily be accessed * later without the need to use a data structure to do the mapping. * * <p>An example of associating additional behavior to this class might be realized * by extending the class to implement an additional interface. For example, one * might want to implement {@link Comparable} so that when the {@link DeferredResult} * is added to a {@link PriorityQueue} it is handled in the correct order. * * @author Rossen Stoyanchev * @author Rob Winch * @since 3.2 */ public class DeferredResult<T> { private static final Object RESULT_NONE = new Object(); protected static final Logger logger = Logger.getLogger(DeferredResult.class); private final Long timeout; private final Object timeoutResult; private Runnable timeoutCallback; private Runnable completionCallback; private DeferredResultHandler resultHandler; private volatile Object result = RESULT_NONE; private volatile boolean expired; /** * Create a DeferredResult. */ public DeferredResult() { this(null, RESULT_NONE); } /** * Create a DeferredResult with a timeout value. * <p>By default not set in which case the default configured in the MVC * Java Config or the MVC namespace is used, or if that's not set, then the * timeout depends on the default of the underlying server. * @param timeout timeout value in milliseconds */ public DeferredResult(Long timeout) { this(timeout, RESULT_NONE); } /** * Create a DeferredResult with a timeout value and a default result to use * in case of timeout. * @param timeout timeout value in milliseconds (ignored if {@code null}) * @param timeoutResult the result to use */ public DeferredResult(Long timeout, Object timeoutResult) { this.timeoutResult = timeoutResult; this.timeout = timeout; } /** * Return {@code true} if this DeferredResult is no longer usable either * because it was previously set or because the underlying request expired. * <p>The result may have been set with a call to {@link #setResult(Object)}, * or {@link #setErrorResult(Object)}, or as a result of a timeout, if a * timeout result was provided to the constructor. The request may also * expire due to a timeout or network error. */ public final boolean isSetOrExpired() { return (this.result != RESULT_NONE || this.expired); } /** * Return {@code true} if the DeferredResult has been set. * @since 4.0 */ public boolean hasResult() { return (this.result != RESULT_NONE); } /** * Return the result, or {@code null} if the result wasn't set. Since the result * can also be {@code null}, it is recommended to use {@link #hasResult()} first * to check if there is a result prior to calling this method. * @since 4.0 */ public Object getResult() { Object resultToCheck = this.result; return (resultToCheck != RESULT_NONE ? resultToCheck : null); } /** * Return the configured timeout value in milliseconds. */ final Long getTimeoutValue() { return this.timeout; } /** * Register code to invoke when the async request times out. * <p>This method is called from a container thread when an async request * times out before the {@code DeferredResult} has been populated. * It may invoke {@link DeferredResult#setResult setResult} or * {@link DeferredResult#setErrorResult setErrorResult} to resume processing. */ public void onTimeout(Runnable callback) { this.timeoutCallback = callback; } /** * Register code to invoke when the async request completes. * <p>This method is called from a container thread when an async request * completed for any reason including timeout and network error. This is useful * for detecting that a {@code DeferredResult} instance is no longer usable. */ public void onCompletion(Runnable callback) { this.completionCallback = callback; } /** * Provide a handler to use to handle the result value. * @param resultHandler the handler * @see DeferredResultProcessingInterceptor */ public final void setResultHandler(DeferredResultHandler resultHandler) { Assert.notNull(resultHandler, "DeferredResultHandler is required"); synchronized (this) { this.resultHandler = resultHandler; if (this.result != RESULT_NONE && !this.expired) { try { this.resultHandler.handleResult(this.result); } catch (Throwable ex) { logger.trace("DeferredResult not handled", ex); } } } } /** * Set the value for the DeferredResult and handle it. * @param result the value to set * @return "true" if the result was set and passed on for handling; * "false" if the result was already set or the async request expired * @see #isSetOrExpired() */ public boolean setResult(T result) { return setResultInternal(result); } private boolean setResultInternal(Object result) { synchronized (this) { if (isSetOrExpired()) { return false; } this.result = result; } if (this.resultHandler != null) { this.resultHandler.handleResult(this.result); } return true; } /** * Set an error value for the {@link DeferredResult} and handle it. * The value may be an {@link Exception} or {@link Throwable} in which case * it will be processed as if a handler raised the exception. * @param result the error result value * @return "true" if the result was set to the error value and passed on for * handling; "false" if the result was already set or the async request expired * @see #isSetOrExpired() */ public boolean setErrorResult(Object result) { return setResultInternal(result); } final DeferredResultProcessingInterceptor getInterceptor() { return new DeferredResultProcessingInterceptorAdapter() { @Override public <S> boolean handleTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) { if (timeoutCallback != null) { timeoutCallback.run(); } if (DeferredResult.this.timeoutResult != RESULT_NONE) { setResultInternal(timeoutResult); } return true; } @Override public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) { synchronized (DeferredResult.this) { expired = true; } if (completionCallback != null) { completionCallback.run(); } } }; } /** * Handles a DeferredResult value when set. */ public interface DeferredResultHandler { void handleResult(Object result); } }