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}