001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.context.request.async;
018
019import java.util.PriorityQueue;
020import java.util.concurrent.Callable;
021import java.util.function.Consumer;
022import java.util.function.Supplier;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.lang.Nullable;
028import org.springframework.util.Assert;
029import org.springframework.web.context.request.NativeWebRequest;
030
031/**
032 * {@code DeferredResult} provides an alternative to using a {@link Callable} for
033 * asynchronous request processing. While a {@code Callable} is executed concurrently
034 * on behalf of the application, with a {@code DeferredResult} the application can
035 * produce the result from a thread of its choice.
036 *
037 * <p>Subclasses can extend this class to easily associate additional data or behavior
038 * with the {@link DeferredResult}. For example, one might want to associate the user
039 * used to create the {@link DeferredResult} by extending the class and adding an
040 * additional property for the user. In this way, the user could easily be accessed
041 * later without the need to use a data structure to do the mapping.
042 *
043 * <p>An example of associating additional behavior to this class might be realized
044 * by extending the class to implement an additional interface. For example, one
045 * might want to implement {@link Comparable} so that when the {@link DeferredResult}
046 * is added to a {@link PriorityQueue} it is handled in the correct order.
047 *
048 * @author Rossen Stoyanchev
049 * @author Juergen Hoeller
050 * @author Rob Winch
051 * @since 3.2
052 * @param <T> the result type
053 */
054public class DeferredResult<T> {
055
056        private static final Object RESULT_NONE = new Object();
057
058        private static final Log logger = LogFactory.getLog(DeferredResult.class);
059
060
061        @Nullable
062        private final Long timeoutValue;
063
064        private final Supplier<?> timeoutResult;
065
066        private Runnable timeoutCallback;
067
068        private Consumer<Throwable> errorCallback;
069
070        private Runnable completionCallback;
071
072        private DeferredResultHandler resultHandler;
073
074        private volatile Object result = RESULT_NONE;
075
076        private volatile boolean expired = false;
077
078
079        /**
080         * Create a DeferredResult.
081         */
082        public DeferredResult() {
083                this(null, () -> RESULT_NONE);
084        }
085
086        /**
087         * Create a DeferredResult with a custom timeout value.
088         * <p>By default not set in which case the default configured in the MVC
089         * Java Config or the MVC namespace is used, or if that's not set, then the
090         * timeout depends on the default of the underlying server.
091         * @param timeoutValue timeout value in milliseconds
092         */
093        public DeferredResult(Long timeoutValue) {
094                this(timeoutValue, () -> RESULT_NONE);
095        }
096
097        /**
098         * Create a DeferredResult with a timeout value and a default result to use
099         * in case of timeout.
100         * @param timeoutValue timeout value in milliseconds (ignored if {@code null})
101         * @param timeoutResult the result to use
102         */
103        public DeferredResult(@Nullable Long timeoutValue, Object timeoutResult) {
104                this.timeoutValue = timeoutValue;
105                this.timeoutResult = () -> timeoutResult;
106        }
107
108        /**
109         * Variant of {@link #DeferredResult(Long, Object)} that accepts a dynamic
110         * fallback value based on a {@link Supplier}.
111         * @param timeoutValue timeout value in milliseconds (ignored if {@code null})
112         * @param timeoutResult the result supplier to use
113         * @since 5.1.1
114         */
115        public DeferredResult(@Nullable Long timeoutValue, Supplier<?> timeoutResult) {
116                this.timeoutValue = timeoutValue;
117                this.timeoutResult = timeoutResult;
118        }
119
120
121        /**
122         * Return {@code true} if this DeferredResult is no longer usable either
123         * because it was previously set or because the underlying request expired.
124         * <p>The result may have been set with a call to {@link #setResult(Object)},
125         * or {@link #setErrorResult(Object)}, or as a result of a timeout, if a
126         * timeout result was provided to the constructor. The request may also
127         * expire due to a timeout or network error.
128         */
129        public final boolean isSetOrExpired() {
130                return (this.result != RESULT_NONE || this.expired);
131        }
132
133        /**
134         * Return {@code true} if the DeferredResult has been set.
135         * @since 4.0
136         */
137        public boolean hasResult() {
138                return (this.result != RESULT_NONE);
139        }
140
141        /**
142         * Return the result, or {@code null} if the result wasn't set. Since the result
143         * can also be {@code null}, it is recommended to use {@link #hasResult()} first
144         * to check if there is a result prior to calling this method.
145         * @since 4.0
146         */
147        @Nullable
148        public Object getResult() {
149                Object resultToCheck = this.result;
150                return (resultToCheck != RESULT_NONE ? resultToCheck : null);
151        }
152
153        /**
154         * Return the configured timeout value in milliseconds.
155         */
156        @Nullable
157        final Long getTimeoutValue() {
158                return this.timeoutValue;
159        }
160
161        /**
162         * Register code to invoke when the async request times out.
163         * <p>This method is called from a container thread when an async request
164         * times out before the {@code DeferredResult} has been populated.
165         * It may invoke {@link DeferredResult#setResult setResult} or
166         * {@link DeferredResult#setErrorResult setErrorResult} to resume processing.
167         */
168        public void onTimeout(Runnable callback) {
169                this.timeoutCallback = callback;
170        }
171
172        /**
173         * Register code to invoke when an error occurred during the async request.
174         * <p>This method is called from a container thread when an error occurs
175         * while processing an async request before the {@code DeferredResult} has
176         * been populated. It may invoke {@link DeferredResult#setResult setResult}
177         * or {@link DeferredResult#setErrorResult setErrorResult} to resume
178         * processing.
179         * @since 5.0
180         */
181        public void onError(Consumer<Throwable> callback) {
182                this.errorCallback = callback;
183        }
184
185        /**
186         * Register code to invoke when the async request completes.
187         * <p>This method is called from a container thread when an async request
188         * completed for any reason including timeout and network error. This is useful
189         * for detecting that a {@code DeferredResult} instance is no longer usable.
190         */
191        public void onCompletion(Runnable callback) {
192                this.completionCallback = callback;
193        }
194
195        /**
196         * Provide a handler to use to handle the result value.
197         * @param resultHandler the handler
198         * @see DeferredResultProcessingInterceptor
199         */
200        public final void setResultHandler(DeferredResultHandler resultHandler) {
201                Assert.notNull(resultHandler, "DeferredResultHandler is required");
202                // Immediate expiration check outside of the result lock
203                if (this.expired) {
204                        return;
205                }
206                Object resultToHandle;
207                synchronized (this) {
208                        // Got the lock in the meantime: double-check expiration status
209                        if (this.expired) {
210                                return;
211                        }
212                        resultToHandle = this.result;
213                        if (resultToHandle == RESULT_NONE) {
214                                // No result yet: store handler for processing once it comes in
215                                this.resultHandler = resultHandler;
216                                return;
217                        }
218                }
219                // If we get here, we need to process an existing result object immediately.
220                // The decision is made within the result lock; just the handle call outside
221                // of it, avoiding any deadlock potential with Servlet container locks.
222                try {
223                        resultHandler.handleResult(resultToHandle);
224                }
225                catch (Throwable ex) {
226                        logger.debug("Failed to process async result", ex);
227                }
228        }
229
230        /**
231         * Set the value for the DeferredResult and handle it.
232         * @param result the value to set
233         * @return {@code true} if the result was set and passed on for handling;
234         * {@code false} if the result was already set or the async request expired
235         * @see #isSetOrExpired()
236         */
237        public boolean setResult(T result) {
238                return setResultInternal(result);
239        }
240
241        private boolean setResultInternal(Object result) {
242                // Immediate expiration check outside of the result lock
243                if (isSetOrExpired()) {
244                        return false;
245                }
246                DeferredResultHandler resultHandlerToUse;
247                synchronized (this) {
248                        // Got the lock in the meantime: double-check expiration status
249                        if (isSetOrExpired()) {
250                                return false;
251                        }
252                        // At this point, we got a new result to process
253                        this.result = result;
254                        resultHandlerToUse = this.resultHandler;
255                        if (resultHandlerToUse == null) {
256                                // No result handler set yet -> let the setResultHandler implementation
257                                // pick up the result object and invoke the result handler for it.
258                                return true;
259                        }
260                        // Result handler available -> let's clear the stored reference since
261                        // we don't need it anymore.
262                        this.resultHandler = null;
263                }
264                // If we get here, we need to process an existing result object immediately.
265                // The decision is made within the result lock; just the handle call outside
266                // of it, avoiding any deadlock potential with Servlet container locks.
267                resultHandlerToUse.handleResult(result);
268                return true;
269        }
270
271        /**
272         * Set an error value for the {@link DeferredResult} and handle it.
273         * The value may be an {@link Exception} or {@link Throwable} in which case
274         * it will be processed as if a handler raised the exception.
275         * @param result the error result value
276         * @return {@code true} if the result was set to the error value and passed on
277         * for handling; {@code false} if the result was already set or the async
278         * request expired
279         * @see #isSetOrExpired()
280         */
281        public boolean setErrorResult(Object result) {
282                return setResultInternal(result);
283        }
284
285
286        final DeferredResultProcessingInterceptor getInterceptor() {
287                return new DeferredResultProcessingInterceptor() {
288                        @Override
289                        public <S> boolean handleTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) {
290                                boolean continueProcessing = true;
291                                try {
292                                        if (timeoutCallback != null) {
293                                                timeoutCallback.run();
294                                        }
295                                }
296                                finally {
297                                        Object value = timeoutResult.get();
298                                        if (value != RESULT_NONE) {
299                                                continueProcessing = false;
300                                                try {
301                                                        setResultInternal(value);
302                                                }
303                                                catch (Throwable ex) {
304                                                        logger.debug("Failed to handle timeout result", ex);
305                                                }
306                                        }
307                                }
308                                return continueProcessing;
309                        }
310                        @Override
311                        public <S> boolean handleError(NativeWebRequest request, DeferredResult<S> deferredResult, Throwable t) {
312                                try {
313                                        if (errorCallback != null) {
314                                                errorCallback.accept(t);
315                                        }
316                                }
317                                finally {
318                                        try {
319                                                setResultInternal(t);
320                                        }
321                                        catch (Throwable ex) {
322                                                logger.debug("Failed to handle error result", ex);
323                                        }
324                                }
325                                return false;
326                        }
327                        @Override
328                        public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) {
329                                expired = true;
330                                if (completionCallback != null) {
331                                        completionCallback.run();
332                                }
333                        }
334                };
335        }
336
337
338        /**
339         * Handles a DeferredResult value when set.
340         */
341        @FunctionalInterface
342        public interface DeferredResultHandler {
343
344                void handleResult(Object result);
345        }
346
347}