org.frameworkset.web.request.async.DeferredResult Maven / Gradle / Ivy
Show all versions of bboss-mvc Show documentation
package org.frameworkset.web.request.async;
import java.util.PriorityQueue;
import java.util.concurrent.Callable;
import org.frameworkset.util.Assert;
import org.frameworkset.web.servlet.mvc.NativeWebRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@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.
*
* 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.
*
*
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 {
private static final Object RESULT_NONE = new Object();
protected static final Logger logger = LoggerFactory.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.
* 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.
*
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.
*
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.
*
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 boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) {
if (timeoutCallback != null) {
timeoutCallback.run();
}
if (DeferredResult.this.timeoutResult != RESULT_NONE) {
setResultInternal(timeoutResult);
}
return true;
}
@Override
public void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) {
synchronized (DeferredResult.this) {
expired = true;
}
if (completionCallback != null) {
completionCallback.run();
}
}
};
}
/**
* Handles a DeferredResult value when set.
*/
public interface DeferredResultHandler {
void handleResult(Object result);
}
}