/
DeferredResult.java
232 lines (200 loc) · 7.04 KB
/
DeferredResult.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/*
* Copyright 2002-2013 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.
*
* <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 Log logger = LogFactory.getLog(DeferredResult.class);
private static final Object RESULT_NONE = new Object();
private final Long timeout;
private final Object timeoutResult;
private Runnable timeoutCallback;
private Runnable completionCallback;
private DeferredResultHandler resultHandler;
private Object result = RESULT_NONE;
private boolean expired;
/**
* Create a DeferredResult.
*/
public DeferredResult() {
this(null, RESULT_NONE);
}
/**
* Create a DeferredResult with a timeout value.
* @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 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 set. It may invoke
* {@link DeferredResult#setResult(Object) setResult} or
* {@link DeferredResult#setErrorResult(Object) 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 method 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 t) {
logger.trace("DeferredResult not handled", t);
}
}
}
}
/**
* 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);
}
}