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;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.util.Assert;
026import org.springframework.web.context.request.NativeWebRequest;
027
028/**
029 * {@code DeferredResult} provides an alternative to using a {@link Callable} for
030 * asynchronous request processing. While a {@code Callable} is executed concurrently
031 * on behalf of the application, with a {@code DeferredResult} the application can
032 * produce the result from a thread of its choice.
033 *
034 * <p>Subclasses can extend this class to easily associate additional data or behavior
035 * with the {@link DeferredResult}. For example, one might want to associate the user
036 * used to create the {@link DeferredResult} by extending the class and adding an
037 * additional property for the user. In this way, the user could easily be accessed
038 * later without the need to use a data structure to do the mapping.
039 *
040 * <p>An example of associating additional behavior to this class might be realized
041 * by extending the class to implement an additional interface. For example, one
042 * might want to implement {@link Comparable} so that when the {@link DeferredResult}
043 * is added to a {@link PriorityQueue} it is handled in the correct order.
044 *
045 * @author Rossen Stoyanchev
046 * @author Juergen Hoeller
047 * @author Rob Winch
048 * @since 3.2
049 */
050public class DeferredResult<T> {
051
052        private static final Object RESULT_NONE = new Object();
053
054        private static final Log logger = LogFactory.getLog(DeferredResult.class);
055
056
057        private final Long timeoutValue;
058
059        private final Object timeoutResult;
060
061        private Runnable timeoutCallback;
062
063        private Runnable completionCallback;
064
065        private DeferredResultHandler resultHandler;
066
067        private volatile Object result = RESULT_NONE;
068
069        private volatile boolean expired = false;
070
071
072        /**
073         * Create a DeferredResult.
074         */
075        public DeferredResult() {
076                this(null, RESULT_NONE);
077        }
078
079        /**
080         * Create a DeferredResult with a custom timeout value.
081         * <p>By default not set in which case the default configured in the MVC
082         * Java Config or the MVC namespace is used, or if that's not set, then the
083         * timeout depends on the default of the underlying server.
084         * @param timeoutValue timeout value in milliseconds
085         */
086        public DeferredResult(Long timeoutValue) {
087                this(timeoutValue, RESULT_NONE);
088        }
089
090        /**
091         * Create a DeferredResult with a timeout value and a default result to use
092         * in case of timeout.
093         * @param timeoutValue timeout value in milliseconds (ignored if {@code null})
094         * @param timeoutResult the result to use
095         */
096        public DeferredResult(Long timeoutValue, Object timeoutResult) {
097                this.timeoutValue = timeoutValue;
098                this.timeoutResult = timeoutResult;
099        }
100
101
102        /**
103         * Return {@code true} if this DeferredResult is no longer usable either
104         * because it was previously set or because the underlying request expired.
105         * <p>The result may have been set with a call to {@link #setResult(Object)},
106         * or {@link #setErrorResult(Object)}, or as a result of a timeout, if a
107         * timeout result was provided to the constructor. The request may also
108         * expire due to a timeout or network error.
109         */
110        public final boolean isSetOrExpired() {
111                return (this.result != RESULT_NONE || this.expired);
112        }
113
114        /**
115         * Return {@code true} if the DeferredResult has been set.
116         * @since 4.0
117         */
118        public boolean hasResult() {
119                return (this.result != RESULT_NONE);
120        }
121
122        /**
123         * Return the result, or {@code null} if the result wasn't set. Since the result
124         * can also be {@code null}, it is recommended to use {@link #hasResult()} first
125         * to check if there is a result prior to calling this method.
126         * @since 4.0
127         */
128        public Object getResult() {
129                Object resultToCheck = this.result;
130                return (resultToCheck != RESULT_NONE ? resultToCheck : null);
131        }
132
133        /**
134         * Return the configured timeout value in milliseconds.
135         */
136        final Long getTimeoutValue() {
137                return this.timeoutValue;
138        }
139
140        /**
141         * Register code to invoke when the async request times out.
142         * <p>This method is called from a container thread when an async request
143         * times out before the {@code DeferredResult} has been populated.
144         * It may invoke {@link DeferredResult#setResult setResult} or
145         * {@link DeferredResult#setErrorResult setErrorResult} to resume processing.
146         */
147        public void onTimeout(Runnable callback) {
148                this.timeoutCallback = callback;
149        }
150
151        /**
152         * Register code to invoke when the async request completes.
153         * <p>This method is called from a container thread when an async request
154         * completed for any reason including timeout and network error. This is useful
155         * for detecting that a {@code DeferredResult} instance is no longer usable.
156         */
157        public void onCompletion(Runnable callback) {
158                this.completionCallback = callback;
159        }
160
161        /**
162         * Provide a handler to use to handle the result value.
163         * @param resultHandler the handler
164         * @see DeferredResultProcessingInterceptor
165         */
166        public final void setResultHandler(DeferredResultHandler resultHandler) {
167                Assert.notNull(resultHandler, "DeferredResultHandler is required");
168                // Immediate expiration check outside of the result lock
169                if (this.expired) {
170                        return;
171                }
172                Object resultToHandle;
173                synchronized (this) {
174                        // Got the lock in the meantime: double-check expiration status
175                        if (this.expired) {
176                                return;
177                        }
178                        resultToHandle = this.result;
179                        if (resultToHandle == RESULT_NONE) {
180                                // No result yet: store handler for processing once it comes in
181                                this.resultHandler = resultHandler;
182                                return;
183                        }
184                }
185                // If we get here, we need to process an existing result object immediately.
186                // The decision is made within the result lock; just the handle call outside
187                // of it, avoiding any deadlock potential with Servlet container locks.
188                try {
189                        resultHandler.handleResult(resultToHandle);
190                }
191                catch (Throwable ex) {
192                        logger.debug("Failed to handle existing result", ex);
193                }
194        }
195
196        /**
197         * Set the value for the DeferredResult and handle it.
198         * @param result the value to set
199         * @return {@code true} if the result was set and passed on for handling;
200         * {@code false} if the result was already set or the async request expired
201         * @see #isSetOrExpired()
202         */
203        public boolean setResult(T result) {
204                return setResultInternal(result);
205        }
206
207        private boolean setResultInternal(Object result) {
208                // Immediate expiration check outside of the result lock
209                if (isSetOrExpired()) {
210                        return false;
211                }
212                DeferredResultHandler resultHandlerToUse;
213                synchronized (this) {
214                        // Got the lock in the meantime: double-check expiration status
215                        if (isSetOrExpired()) {
216                                return false;
217                        }
218                        // At this point, we got a new result to process
219                        this.result = result;
220                        resultHandlerToUse = this.resultHandler;
221                        if (resultHandlerToUse == null) {
222                                // No result handler set yet -> let the setResultHandler implementation
223                                // pick up the result object and invoke the result handler for it.
224                                return true;
225                        }
226                        // Result handler available -> let's clear the stored reference since
227                        // we don't need it anymore.
228                        this.resultHandler = null;
229                }
230                // If we get here, we need to process an existing result object immediately.
231                // The decision is made within the result lock; just the handle call outside
232                // of it, avoiding any deadlock potential with Servlet container locks.
233                resultHandlerToUse.handleResult(result);
234                return true;
235        }
236
237        /**
238         * Set an error value for the {@link DeferredResult} and handle it.
239         * The value may be an {@link Exception} or {@link Throwable} in which case
240         * it will be processed as if a handler raised the exception.
241         * @param result the error result value
242         * @return {@code true} if the result was set to the error value and passed on
243         * for handling; {@code false} if the result was already set or the async
244         * request expired
245         * @see #isSetOrExpired()
246         */
247        public boolean setErrorResult(Object result) {
248                return setResultInternal(result);
249        }
250
251
252        final DeferredResultProcessingInterceptor getInterceptor() {
253                return new DeferredResultProcessingInterceptorAdapter() {
254                        @Override
255                        public <S> boolean handleTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) {
256                                boolean continueProcessing = true;
257                                try {
258                                        if (timeoutCallback != null) {
259                                                timeoutCallback.run();
260                                        }
261                                }
262                                finally {
263                                        if (timeoutResult != RESULT_NONE) {
264                                                continueProcessing = false;
265                                                try {
266                                                        setResultInternal(timeoutResult);
267                                                }
268                                                catch (Throwable ex) {
269                                                        logger.debug("Failed to handle timeout result", ex);
270                                                }
271                                        }
272                                }
273                                return continueProcessing;
274                        }
275                        @Override
276                        public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) {
277                                expired = true;
278                                if (completionCallback != null) {
279                                        completionCallback.run();
280                                }
281                        }
282                };
283        }
284
285
286        /**
287         * Handles a DeferredResult value when set.
288         */
289        public interface DeferredResultHandler {
290
291                void handleResult(Object result);
292        }
293
294}