All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.web.context.request.async.DeferredResult Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2018 the original author or authors.
 *
 * 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 org.springframework.web.context.request.async;

import java.util.PriorityQueue;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Supplier;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.context.request.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.
 *
 * 

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 Juergen Hoeller * @author Rob Winch * @since 3.2 * @param the result type */ public class DeferredResult { private static final Object RESULT_NONE = new Object(); private static final Log logger = LogFactory.getLog(DeferredResult.class); @Nullable private final Long timeout; private final Supplier timeoutResult; private Runnable timeoutCallback; private Consumer errorCallback; private Runnable completionCallback; private DeferredResultHandler resultHandler; private volatile Object result = RESULT_NONE; private volatile boolean expired = false; /** * Create a DeferredResult. */ public DeferredResult() { this(null, () -> RESULT_NONE); } /** * Create a DeferredResult with a custom 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(@Nullable Long timeout, final Object timeoutResult) { this.timeoutResult = () -> timeoutResult; this.timeout = timeout; } /** * Variant of {@link #DeferredResult(Long, Object)} that accepts a dynamic * fallback value based on a {@link Supplier}. * @param timeout timeout value in milliseconds (ignored if {@code null}) * @param timeoutResult the result supplier to use * @since 5.1.1 */ public DeferredResult(@Nullable Long timeout, Supplier 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 */ @Nullable public Object getResult() { Object resultToCheck = this.result; return (resultToCheck != RESULT_NONE ? resultToCheck : null); } /** * Return the configured timeout value in milliseconds. */ @Nullable 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 an error occurred during the async request. *

This method is called from a container thread when an error occurs * while processing an async request before the {@code DeferredResult} has * been populated. It may invoke {@link DeferredResult#setResult setResult} * or {@link DeferredResult#setErrorResult setErrorResult} to resume * processing. * @since 5.0 */ public void onError(Consumer callback) { this.errorCallback = 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"); // Immediate expiration check outside of the result lock if (this.expired) { return; } Object resultToHandle; synchronized (this) { // Got the lock in the meantime: double-check expiration status if (this.expired) { return; } resultToHandle = this.result; if (resultToHandle == RESULT_NONE) { // No result yet: store handler for processing once it comes in this.resultHandler = resultHandler; return; } } // If we get here, we need to process an existing result object immediately. // The decision is made within the result lock; just the handle call outside // of it, avoiding any deadlock potential with Servlet container locks. try { resultHandler.handleResult(resultToHandle); } catch (Throwable ex) { logger.debug("Failed to process async result", ex); } } /** * Set the value for the DeferredResult and handle it. * @param result the value to set * @return {@code true} if the result was set and passed on for handling; * {@code 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) { // Immediate expiration check outside of the result lock if (isSetOrExpired()) { return false; } DeferredResultHandler resultHandlerToUse; synchronized (this) { // Got the lock in the meantime: double-check expiration status if (isSetOrExpired()) { return false; } // At this point, we got a new result to process this.result = result; resultHandlerToUse = this.resultHandler; if (resultHandlerToUse == null) { // No result handler set yet -> let the setResultHandler implementation // pick up the result object and invoke the result handler for it. return true; } // Result handler available -> let's clear the stored reference since // we don't need it anymore. this.resultHandler = null; } // If we get here, we need to process an existing result object immediately. // The decision is made within the result lock; just the handle call outside // of it, avoiding any deadlock potential with Servlet container locks. resultHandlerToUse.handleResult(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 {@code true} if the result was set to the error value and passed on * for handling; {@code 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 DeferredResultProcessingInterceptor() { @Override public boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) { boolean continueProcessing = true; try { if (timeoutCallback != null) { timeoutCallback.run(); } } finally { Object value = timeoutResult.get(); if (value != RESULT_NONE) { continueProcessing = false; try { setResultInternal(value); } catch (Throwable ex) { logger.debug("Failed to handle timeout result", ex); } } } return continueProcessing; } @Override public boolean handleError(NativeWebRequest request, DeferredResult deferredResult, Throwable t) { try { if (errorCallback != null) { errorCallback.accept(t); } } finally { try { setResultInternal(t); } catch (Throwable ex) { logger.debug("Failed to handle error result", ex); } } return false; } @Override public void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) { expired = true; if (completionCallback != null) { completionCallback.run(); } } }; } /** * Handles a DeferredResult value when set. */ @FunctionalInterface public interface DeferredResultHandler { void handleResult(Object result); } }