|
| 1 | +/* |
| 2 | + * Copyright 2002-2012 the original author or authors. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package org.springframework.web.context.request.async; |
| 18 | + |
| 19 | +import java.util.ArrayList; |
| 20 | +import java.util.List; |
| 21 | +import java.util.concurrent.Callable; |
| 22 | + |
| 23 | +import javax.servlet.ServletRequest; |
| 24 | + |
| 25 | +import org.springframework.core.task.AsyncTaskExecutor; |
| 26 | +import org.springframework.core.task.SimpleAsyncTaskExecutor; |
| 27 | +import org.springframework.util.Assert; |
| 28 | +import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler; |
| 29 | + |
| 30 | +/** |
| 31 | + * The central class for managing async request processing, mainly intended as |
| 32 | + * an SPI and typically not by non-framework classes. |
| 33 | + * |
| 34 | + * <p>An async execution chain consists of a sequence of Callable instances and |
| 35 | + * represents the work required to complete request processing in a separate |
| 36 | + * thread. To construct the chain, each layer in the call stack of a normal |
| 37 | + * request (e.g. filter, servlet) may contribute an |
| 38 | + * {@link AbstractDelegatingCallable} when a request is being processed. |
| 39 | + * For example the DispatcherServlet might contribute a Callable that |
| 40 | + * performs view resolution while a HandlerAdapter might contribute a Callable |
| 41 | + * that returns the ModelAndView, etc. The last Callable is the one that |
| 42 | + * actually produces an application-specific value, for example the Callable |
| 43 | + * returned by an {@code @RequestMapping} method. |
| 44 | + * |
| 45 | + * @author Rossen Stoyanchev |
| 46 | + * @since 3.2 |
| 47 | + */ |
| 48 | +public final class AsyncExecutionChain { |
| 49 | + |
| 50 | + public static final String CALLABLE_CHAIN_ATTRIBUTE = AsyncExecutionChain.class.getName() + ".CALLABLE_CHAIN"; |
| 51 | + |
| 52 | + private final List<AbstractDelegatingCallable> delegatingCallables = new ArrayList<AbstractDelegatingCallable>(); |
| 53 | + |
| 54 | + private Callable<Object> callable; |
| 55 | + |
| 56 | + private AsyncWebRequest asyncWebRequest; |
| 57 | + |
| 58 | + private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("AsyncExecutionChain"); |
| 59 | + |
| 60 | + /** |
| 61 | + * Private constructor |
| 62 | + * @see #getForCurrentRequest() |
| 63 | + */ |
| 64 | + private AsyncExecutionChain() { |
| 65 | + } |
| 66 | + |
| 67 | + /** |
| 68 | + * Obtain the AsyncExecutionChain for the current request. |
| 69 | + * Or if not found, create an instance and associate it with the request. |
| 70 | + */ |
| 71 | + public static AsyncExecutionChain getForCurrentRequest(ServletRequest request) { |
| 72 | + AsyncExecutionChain chain = (AsyncExecutionChain) request.getAttribute(CALLABLE_CHAIN_ATTRIBUTE); |
| 73 | + if (chain == null) { |
| 74 | + chain = new AsyncExecutionChain(); |
| 75 | + request.setAttribute(CALLABLE_CHAIN_ATTRIBUTE, chain); |
| 76 | + } |
| 77 | + return chain; |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * Provide an instance of an AsyncWebRequest. |
| 82 | + * This property must be set before async request processing can begin. |
| 83 | + */ |
| 84 | + public void setAsyncWebRequest(AsyncWebRequest asyncRequest) { |
| 85 | + this.asyncWebRequest = asyncRequest; |
| 86 | + } |
| 87 | + |
| 88 | + /** |
| 89 | + * Provide an AsyncTaskExecutor to use when |
| 90 | + * {@link #startCallableChainProcessing()} is invoked, for example when a |
| 91 | + * controller method returns a Callable. |
| 92 | + * <p>By default a {@link SimpleAsyncTaskExecutor} instance is used. |
| 93 | + */ |
| 94 | + public void setTaskExecutor(AsyncTaskExecutor taskExecutor) { |
| 95 | + this.taskExecutor = taskExecutor; |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * Whether async request processing has started through one of: |
| 100 | + * <ul> |
| 101 | + * <li>{@link #startCallableChainProcessing()} |
| 102 | + * <li>{@link #startDeferredResultProcessing(DeferredResult)} |
| 103 | + * </ul> |
| 104 | + */ |
| 105 | + public boolean isAsyncStarted() { |
| 106 | + return ((this.asyncWebRequest != null) && this.asyncWebRequest.isAsyncStarted()); |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * Add a Callable with logic required to complete request processing in a |
| 111 | + * separate thread. See {@link AbstractDelegatingCallable} for details. |
| 112 | + */ |
| 113 | + public void addDelegatingCallable(AbstractDelegatingCallable callable) { |
| 114 | + Assert.notNull(callable, "Callable required"); |
| 115 | + this.delegatingCallables.add(callable); |
| 116 | + } |
| 117 | + |
| 118 | + /** |
| 119 | + * Add the last Callable, for example the one returned by the controller. |
| 120 | + * This property must be set prior to invoking |
| 121 | + * {@link #startCallableChainProcessing()}. |
| 122 | + */ |
| 123 | + public AsyncExecutionChain setCallable(Callable<Object> callable) { |
| 124 | + Assert.notNull(callable, "Callable required"); |
| 125 | + this.callable = callable; |
| 126 | + return this; |
| 127 | + } |
| 128 | + |
| 129 | + /** |
| 130 | + * Start the async execution chain by submitting an |
| 131 | + * {@link AsyncExecutionChainRunnable} instance to the TaskExecutor provided via |
| 132 | + * {@link #setTaskExecutor(AsyncTaskExecutor)} and returning immediately. |
| 133 | + * @see AsyncExecutionChainRunnable |
| 134 | + */ |
| 135 | + public void startCallableChainProcessing() { |
| 136 | + startAsync(); |
| 137 | + this.taskExecutor.execute(new AsyncExecutionChainRunnable(this.asyncWebRequest, buildChain())); |
| 138 | + } |
| 139 | + |
| 140 | + private void startAsync() { |
| 141 | + Assert.state(this.asyncWebRequest != null, "An AsyncWebRequest is required to start async processing"); |
| 142 | + this.asyncWebRequest.startAsync(); |
| 143 | + } |
| 144 | + |
| 145 | + private Callable<Object> buildChain() { |
| 146 | + Assert.state(this.callable != null, "The callable field is required to complete the chain"); |
| 147 | + this.delegatingCallables.add(new StaleAsyncRequestCheckingCallable(asyncWebRequest)); |
| 148 | + Callable<Object> result = this.callable; |
| 149 | + for (int i = this.delegatingCallables.size() - 1; i >= 0; i--) { |
| 150 | + AbstractDelegatingCallable callable = this.delegatingCallables.get(i); |
| 151 | + callable.setNextCallable(result); |
| 152 | + result = callable; |
| 153 | + } |
| 154 | + return result; |
| 155 | + } |
| 156 | + |
| 157 | + /** |
| 158 | + * Mark the start of async request processing accepting the provided |
| 159 | + * DeferredResult and initializing it such that if |
| 160 | + * {@link DeferredResult#set(Object)} is called (from another thread), |
| 161 | + * the set Object value will be processed with the execution chain by |
| 162 | + * invoking {@link AsyncExecutionChainRunnable}. |
| 163 | + * <p>The resulting processing from this method is identical to |
| 164 | + * {@link #startCallableChainProcessing()}. The main difference is in |
| 165 | + * the threading model, i.e. whether a TaskExecutor is used. |
| 166 | + * @see DeferredResult |
| 167 | + */ |
| 168 | + public void startDeferredResultProcessing(DeferredResult deferredResult) { |
| 169 | + Assert.notNull(deferredResult, "A DeferredResult is required"); |
| 170 | + startAsync(); |
| 171 | + deferredResult.setValueProcessor(new DeferredResultHandler() { |
| 172 | + public void handle(Object value) { |
| 173 | + if (asyncWebRequest.isAsyncCompleted()) { |
| 174 | + throw new StaleAsyncWebRequestException("Async request processing already completed"); |
| 175 | + } |
| 176 | + setCallable(getSimpleCallable(value)); |
| 177 | + new AsyncExecutionChainRunnable(asyncWebRequest, buildChain()).run(); |
| 178 | + } |
| 179 | + }); |
| 180 | + } |
| 181 | + |
| 182 | + private Callable<Object> getSimpleCallable(final Object value) { |
| 183 | + return new Callable<Object>() { |
| 184 | + public Object call() throws Exception { |
| 185 | + return value; |
| 186 | + } |
| 187 | + }; |
| 188 | + } |
| 189 | +} |
0 commit comments