diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java index 1af4dc4d2f9..127924c7bd4 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java @@ -34,7 +34,7 @@ import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.async.AsyncWebUtils; import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer; +import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -195,13 +195,13 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { participate = true; } else { - if (!isAsyncDispatch(request) || !asyncManager.applyAsyncThreadInitializer(key)) { + if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) { logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter"); Session session = getSession(sessionFactory); SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); - AsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder); + WebAsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder); asyncManager.registerAsyncThreadInitializer(key, initializer); } } @@ -240,10 +240,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { } } - private AsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory, + private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory, final SessionHolder sessionHolder) { - return new AsyncThreadInitializer() { + return new WebAsyncThreadInitializer() { public void initialize() { TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java index 5f7ca275490..22f49c2c0ab 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java @@ -28,7 +28,7 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.async.AsyncWebRequestInterceptor; import org.springframework.web.context.request.async.AsyncWebUtils; import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer; +import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer; /** * Spring web request interceptor that binds a Hibernate Session to the @@ -147,7 +147,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A String participateAttributeName = getParticipateAttributeName(); if (asyncManager.hasConcurrentResult()) { - if (asyncManager.applyAsyncThreadInitializer(participateAttributeName)) { + if (asyncManager.initializeAsyncThread(participateAttributeName)) { return; } } @@ -169,7 +169,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); - AsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder); + WebAsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder); asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer); } else { @@ -261,8 +261,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A return getSessionFactory().toString() + PARTICIPATE_SUFFIX; } - private AsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) { - return new AsyncThreadInitializer() { + private WebAsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) { + return new WebAsyncThreadInitializer() { public void initialize() { TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java index 329a4e4eca1..929af55bd42 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java @@ -34,7 +34,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.async.AsyncWebUtils; import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer; +import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -126,13 +126,13 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { participate = true; } else { - if (!isAsyncDispatch(request) || !asyncManager.applyAsyncThreadInitializer(key)) { + if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) { logger.debug("Opening Hibernate Session in OpenSessionInViewFilter"); Session session = openSession(sessionFactory); SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); - AsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder); + WebAsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder); asyncManager.registerAsyncThreadInitializer(key, initializer); } } @@ -153,10 +153,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { } } - private AsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory, + private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory, final SessionHolder sessionHolder) { - return new AsyncThreadInitializer() { + return new WebAsyncThreadInitializer() { public void initialize() { TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java index 6132430c38a..2a2678bd6fc 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java @@ -32,7 +32,7 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.async.AsyncWebRequestInterceptor; import org.springframework.web.context.request.async.AsyncWebUtils; import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer; +import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer; /** * Spring web request interceptor that binds a Hibernate Session to the @@ -109,7 +109,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor String participateAttributeName = getParticipateAttributeName(); if (asyncManager.hasConcurrentResult()) { - if (asyncManager.applyAsyncThreadInitializer(participateAttributeName)) { + if (asyncManager.initializeAsyncThread(participateAttributeName)) { return; } } @@ -126,7 +126,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); - AsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder); + WebAsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder); asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer); } } @@ -200,8 +200,8 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor return getSessionFactory().toString() + PARTICIPATE_SUFFIX; } - private AsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) { - return new AsyncThreadInitializer() { + private WebAsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) { + return new WebAsyncThreadInitializer() { public void initialize() { TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncTask.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncTask.java new file mode 100644 index 00000000000..453ac62b275 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncTask.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2012 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.concurrent.Callable; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.util.Assert; + +/** + * Holder for a {@link Callable}, a timeout value, and a task executor. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class AsyncTask { + + private final Callable callable; + + private final Long timeout; + + private final String executorName; + + private final AsyncTaskExecutor executor; + + private BeanFactory beanFactory; + + + /** + * Create an AsyncTask with a timeout value and a Callable. + * @param timeout timeout value in milliseconds + * @param callable the callable for concurrent handling + */ + public AsyncTask(long timeout, Callable callable) { + this(timeout, null, null, callable); + Assert.notNull(timeout, "Timeout must not be null"); + } + + /** + * Create an AsyncTask with a timeout value, an executor name, and a Callable. + * @param timeout timeout value in milliseconds; ignored if {@code null} + * @param callable the callable for concurrent handling + */ + public AsyncTask(Long timeout, String executorName, Callable callable) { + this(timeout, null, executorName, callable); + Assert.notNull(executor, "Executor name must not be null"); + } + + /** + * Create an AsyncTask with a timeout value, an executor instance, and a Callable. + * @param timeout timeout value in milliseconds; ignored if {@code null} + * @param callable the callable for concurrent handling + */ + public AsyncTask(Long timeout, AsyncTaskExecutor executor, Callable callable) { + this(timeout, executor, null, callable); + Assert.notNull(executor, "Executor must not be null"); + } + + private AsyncTask(Long timeout, AsyncTaskExecutor executor, String executorName, Callable callable) { + Assert.notNull(callable, "Callable must not be null"); + this.callable = callable; + this.timeout = timeout; + this.executor = executor; + this.executorName = executorName; + } + + + /** + * Return the {@link Callable} to use for concurrent handling, never {@code null}. + */ + public Callable getCallable() { + return this.callable; + } + + /** + * Return the timeout value in milliseconds or {@code null} if not value is set. + */ + public Long getTimeout() { + return this.timeout; + } + + /** + * Return the AsyncTaskExecutor to use for concurrent handling, or {@code null}. + */ + public AsyncTaskExecutor getExecutor() { + if (this.executor != null) { + return this.executor; + } + else if (this.executorName != null) { + Assert.state(this.beanFactory != null, "A BeanFactory is required to look up an task executor bean"); + return this.beanFactory.getBean(this.executorName, AsyncTaskExecutor.class); + } + else { + return null; + } + } + + /** + * A {@link BeanFactory} to use to resolve an executor name. Applications are + * not expected to have to set this property when AsyncTask is used in a + * Spring MVC controller. + */ + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequest.java index a4e6af0e4e4..bfadad785ae 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequest.java @@ -29,20 +29,18 @@ public interface AsyncWebRequest extends NativeWebRequest { /** * Set the time required for concurrent handling to complete. - * @param timeout amount of time in milliseconds + * This property should not be set when concurrent handling is in progress, + * i.e. when {@link #isAsyncStarted()} is {@code true}. + * @param timeout amount of time in milliseconds; {@code null} means no + * timeout, i.e. rely on the default timeout of the container. */ void setTimeout(Long timeout); /** - * Provide a Runnable to invoke on timeout. + * Set a handler to be invoked if concurrent processing times out. */ void setTimeoutHandler(Runnable runnable); - /** - * Provide a Runnable to invoke at the end of asynchronous request processing. - */ - void addCompletionHandler(Runnable runnable); - /** * Mark the start of asynchronous request processing so that when the main * processing thread exits, the response remains open for further processing @@ -70,6 +68,11 @@ public interface AsyncWebRequest extends NativeWebRequest { */ boolean isDispatched(); + /** + * Add a Runnable to be invoked when request processing completes. + */ + void addCompletionHandler(Runnable runnable); + /** * Whether asynchronous processing has completed. */ diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebUtils.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebUtils.java index 02f138c8ccb..be8e8ee4288 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebUtils.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebUtils.java @@ -15,8 +15,14 @@ */ package org.springframework.web.context.request.async; -import javax.servlet.ServletRequest; +import java.lang.reflect.Constructor; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.BeanUtils; +import org.springframework.util.ClassUtils; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.WebRequest; @@ -57,4 +63,30 @@ public abstract class AsyncWebUtils { return asyncManager; } + /** + * Create an AsyncWebRequest instance. + *

By default an instance of {@link StandardServletAsyncWebRequest} is created + * if running in Servlet 3.0 (or higher) environment or as a fallback option an + * instance of {@link NoSupportAsyncWebRequest} is returned. + * @param request the current request + * @param response the current response + * @return an AsyncWebRequest instance, never {@code null} + */ + public static AsyncWebRequest createAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) { + return ClassUtils.hasMethod(ServletRequest.class, "startAsync") ? + createStandardServletAsyncWebRequest(request, response) : new NoSupportAsyncWebRequest(request, response); + } + + private static AsyncWebRequest createStandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) { + try { + String className = "org.springframework.web.context.request.async.StandardServletAsyncWebRequest"; + Class clazz = ClassUtils.forName(className, AsyncWebUtils.class.getClassLoader()); + Constructor constructor = clazz.getConstructor(HttpServletRequest.class, HttpServletResponse.class); + return (AsyncWebRequest) BeanUtils.instantiateClass(constructor, request, response); + } + catch (Throwable t) { + throw new IllegalStateException("Failed to instantiate StandardServletAsyncWebRequest", t); + } + } + } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java index 6ceb3da02d2..b0675c8f491 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java @@ -24,10 +24,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** - * {@code DeferredResult} provides an alternative to returning a {@link Callable} - * for asynchronous request processing. While with a Callable, a thread is used - * to execute it on behalf of the application, with a DeferredResult the application - * sets the result whenever it needs to from a thread of its choice. + * {@code DeferredResult} provides an alternative to using a {@link Callable} + * for asynchronous request processing. While a Callable is executed concurrently + * on behalf of the application, with a DeferredResult the application can produce + * the result from a thread of its choice. * * @author Rossen Stoyanchev * @since 3.2 @@ -38,32 +38,53 @@ public final class DeferredResult { private static final Object RESULT_NONE = new Object(); - private Object result = RESULT_NONE; private final Object timeoutResult; - private final AtomicBoolean expired = new AtomicBoolean(false); + private final Long timeout; private DeferredResultHandler resultHandler; + private Object result = RESULT_NONE; + + private final AtomicBoolean expired = new AtomicBoolean(false); + private final Object lock = new Object(); private final CountDownLatch latch = new CountDownLatch(1); /** - * Create a DeferredResult instance. + * Create a DeferredResult. */ public DeferredResult() { - this(RESULT_NONE); + this(null, RESULT_NONE); } /** - * Create a DeferredResult with a default result to use in case of a timeout. - * @param timeoutResult the result to use + * Create a DeferredResult with a timeout. + * @param timeout timeout value in milliseconds */ - public DeferredResult(Object timeoutResult) { + public DeferredResult(long timeout) { + this(timeout, RESULT_NONE); + } + + /** + * Create a DeferredResult with a timeout and a default result to use on timeout. + * @param timeout timeout value in milliseconds; ignored if {@code null} + * @param timeoutResult the result to use, possibly {@code null} + */ + public DeferredResult(Long timeout, Object timeoutResult) { this.timeoutResult = timeoutResult; + this.timeout = timeout; + } + + + /** + * Return the configured timeout value in milliseconds. + */ + public Long getTimeoutMilliseconds() { + return this.timeout; } /** diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java index b8e18cf770a..593376ed7bc 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java @@ -70,6 +70,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements *

The timeout period begins when the main processing thread has exited. */ public void setTimeout(Long timeout) { + Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress"); this.timeout = timeout; } @@ -145,6 +146,9 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements } + /** + * Sends a SERVICE_UNAVAILABLE (503). + */ private class DefaultTimeoutHandler implements Runnable { public void run() { diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java index f440ca77668..08b3e5a0300 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java @@ -38,25 +38,16 @@ import org.springframework.web.util.UrlPathHelper; * as an SPI and not typically used directly by application classes. * *

An async scenario starts with request processing as usual in a thread (T1). - * When a handler decides to handle the request concurrently, it calls + * Concurrent request handling can be innitiated by calling * {@linkplain #startCallableProcessing(Callable, Object...) startCallableProcessing} or * {@linkplain #startDeferredResultProcessing(DeferredResult, Object...) startDeferredResultProcessing} - * both of which will process in a separate thread (T2). - * After the start of concurrent handling {@link #isConcurrentHandlingStarted()} - * returns "true" and this can be used by classes involved in processing on the - * main thread (T1) quickly and with very minimal processing. + * both of which produce a result in a separate thread (T2). The result is saved + * and the request dispatched to the container, to resume processing with the saved + * result in a third thread (T3). Within the dispatched thread (T3), the saved + * result can be accessed via {@link #getConcurrentResult()} or its presence + * detected via {@link #hasConcurrentResult()}. * - *

When the concurrent handling completes in a separate thread (T2), both - * {@code startCallableProcessing} and {@code startDeferredResultProcessing} - * save the results and dispatched to the container, essentially to the - * same request URI as the one that started concurrent handling. This allows for - * further processing of the concurrent results. Classes in the dispatched - * thread (T3), can access the results via {@link #getConcurrentResult()} or - * detect their presence via {@link #hasConcurrentResult()}. Also in the - * dispatched thread {@link #isConcurrentHandlingStarted()} will return "false" - * unless concurrent handling is started once again. - * - * TODO .. mention Servlet 3 configuration + *

TODO .. Servlet 3 config * * @author Rossen Stoyanchev * @since 3.2 @@ -73,42 +64,38 @@ public final class WebAsyncManager { private static final Log logger = LogFactory.getLog(WebAsyncManager.class); + private AsyncWebRequest asyncWebRequest; private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName()); - private final Map threadInitializers = new LinkedHashMap(); - private Object concurrentResult = RESULT_NONE; private Object[] concurrentResultContext; + private final Map threadInitializers = new LinkedHashMap(); + private static final UrlPathHelper urlPathHelper = new UrlPathHelper(); + /** - * Package private constructor - * @see AsyncWebUtils + * Package private constructor. + * @see AsyncWebUtils#getAsyncManager(javax.servlet.ServletRequest) + * @see AsyncWebUtils#getAsyncManager(org.springframework.web.context.request.WebRequest) */ WebAsyncManager() { } /** - * Configure an AsyncTaskExecutor for use with {@link #startCallableProcessing(Callable)}. - *

By default a {@link SimpleAsyncTaskExecutor} instance is used. Applications - * are advised to provide a TaskExecutor configured for production use. - * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setAsyncTaskExecutor - */ - public void setTaskExecutor(AsyncTaskExecutor taskExecutor) { - this.taskExecutor = taskExecutor; - } - - /** - * Provide an {@link AsyncWebRequest} to use to start and to dispatch request. - * This property must be set before the start of concurrent handling. - * @param asyncWebRequest the request to use + * Configure the {@link AsyncWebRequest} to use. This property may be + * set more than once during a single request to accurately reflect the + * current state of the request (e.g. following a forward, request/response + * wrapping, etc). However, it should not be set while concurrent handling is + * in progress, i.e. while {@link #isConcurrentHandlingStarted()} is {@code true}. + * @param asyncWebRequest the web request to use */ public void setAsyncWebRequest(final AsyncWebRequest asyncWebRequest) { - Assert.notNull(asyncWebRequest, "Expected AsyncWebRequest"); + Assert.notNull(asyncWebRequest, "AsyncWebRequest must not be null"); Assert.state(!isConcurrentHandlingStarted(), "Can't set AsyncWebRequest with concurrent handling in progress"); this.asyncWebRequest = asyncWebRequest; this.asyncWebRequest.addCompletionHandler(new Runnable() { @@ -119,18 +106,27 @@ public final class WebAsyncManager { } /** - * Whether the handler for the current request is executed concurrently. - * Once concurrent handling is done, the result is saved, and the request - * dispatched again to resume processing where the result of concurrent - * handling is available via {@link #getConcurrentResult()}. + * Configure an AsyncTaskExecutor for use with concurrent processing via + * {@link #startCallableProcessing(Callable, Object...)}. + *

By default a {@link SimpleAsyncTaskExecutor} instance is used. + */ + public void setTaskExecutor(AsyncTaskExecutor taskExecutor) { + this.taskExecutor = taskExecutor; + } + + /** + * Whether the target handler chose to handle the request asynchronously. + * A return value of "true" indicates concurrent handling is under way and the + * response will remain open. A return value of "false" will be returned again after concurrent + * handling produces a result and the request is dispatched to resume processing. */ public boolean isConcurrentHandlingStarted() { return ((this.asyncWebRequest != null) && (this.asyncWebRequest.isAsyncStarted())); } /** - * Whether the current thread was dispatched to continue processing the result - * of concurrent handler execution. + * Whether the request was dispatched to resume processing the result of + * concurrent handling. */ public boolean hasConcurrentResult() { @@ -142,84 +138,58 @@ public final class WebAsyncManager { } /** - * Return the result of concurrent handler execution. This may be an Object - * value on successful return or an {@code Exception} or {@code Throwable}. + * Provides access to the result from concurrent handling. + * @return an Object, possibly an {@code Exception} or {@code Throwable} if + * concurrent handling raised one. */ public Object getConcurrentResult() { return this.concurrentResult; } /** - * Return the processing context saved at the start of concurrent handling. + * Provides access to additional processing context saved at the start of + * concurrent handling. */ public Object[] getConcurrentResultContext() { return this.concurrentResultContext; } /** - * Reset the {@linkplain #getConcurrentResult() concurrentResult} and the + * Clear {@linkplain #getConcurrentResult() concurrentResult} and * {@linkplain #getConcurrentResultContext() concurrentResultContext}. */ - public void resetConcurrentResult() { + public void clearConcurrentResult() { this.concurrentResult = RESULT_NONE; this.concurrentResultContext = null; } /** - * Register an {@link AsyncThreadInitializer} with the WebAsyncManager instance - * for the current request. It may later be accessed and applied via - * {@link #applyAsyncThreadInitializer(String)} and will also be used to - * initialize and reset threads for concurrent handler execution. - * @param key a unique the key under which to keep the initializer - * @param initializer the initializer instance - */ - public void registerAsyncThreadInitializer(Object key, AsyncThreadInitializer initializer) { - Assert.notNull(initializer, "An AsyncThreadInitializer instance is required"); - this.threadInitializers.put(key, initializer); - } - - /** - * Invoke the {@linkplain AsyncThreadInitializer#initialize() initialize()} - * method of the named {@link AsyncThreadInitializer}. - * @param key the key under which the initializer was registered - * @return whether an initializer was found and applied - */ - public boolean applyAsyncThreadInitializer(Object key) { - AsyncThreadInitializer initializer = this.threadInitializers.get(key); - if (initializer != null) { - initializer.initialize(); - return true; - } - return false; - } - - /** - * Submit a request handling task for concurrent execution. Returns immediately - * and subsequent calls to {@link #isConcurrentHandlingStarted()} return "true". - *

When concurrent handling is done, the resulting value, which may be an - * Object or a raised {@code Exception} or {@code Throwable}, is saved and the - * request is dispatched for further processing of that result. In the dispatched - * thread, the result can be accessed via {@link #getConcurrentResult()} while - * {@link #hasConcurrentResult()} returns "true" and - * {@link #isConcurrentHandlingStarted()} is back to returning "false". + * Start concurrent request processing and execute the given task with an + * {@link #setTaskExecutor(AsyncTaskExecutor) AsyncTaskExecutor}. The result + * from the task execution is saved and the request dispatched in order to + * resume processing of that result. If the task raises an Exception then + * the saved result will be the raised Exception. * * @param callable a unit of work to be executed asynchronously - * @param processingContext additional context to save for later access via - * {@link #getConcurrentResultContext()} + * @param processingContext additional context to save that can be accessed + * via {@link #getConcurrentResultContext()} + * + * @see #getConcurrentResult() + * @see #getConcurrentResultContext() */ public void startCallableProcessing(final Callable callable, Object... processingContext) { - Assert.notNull(callable, "Callable is required"); + Assert.notNull(callable, "Callable must not be null"); startAsyncProcessing(processingContext); this.taskExecutor.submit(new Runnable() { public void run() { - List initializers = - new ArrayList(threadInitializers.values()); + List initializers = + new ArrayList(threadInitializers.values()); try { - for (AsyncThreadInitializer initializer : initializers) { + for (WebAsyncThreadInitializer initializer : initializers) { initializer.initialize(); } concurrentResult = callable.call(); @@ -229,7 +199,7 @@ public final class WebAsyncManager { } finally { Collections.reverse(initializers); - for (AsyncThreadInitializer initializer : initializers) { + for (WebAsyncThreadInitializer initializer : initializers) { initializer.reset(); } } @@ -250,24 +220,51 @@ public final class WebAsyncManager { } /** - * Initialize the given given {@link DeferredResult} so that whenever the - * DeferredResult is set, the resulting value, which may be an Object or a - * raised {@code Exception} or {@code Throwable}, is saved and the request - * is dispatched for further processing of the result. In the dispatch - * thread, the result value can be accessed via {@link #getConcurrentResult()}. - *

The method returns immediately and it's up to the caller to set the - * DeferredResult. Subsequent calls to {@link #isConcurrentHandlingStarted()} - * return "true" until after the dispatch when {@link #hasConcurrentResult()} - * returns "true" and {@link #isConcurrentHandlingStarted()} is back to "false". + * Use the given {@link AsyncTask} to configure the task executor as well as + * the timeout value of the {@code AsyncWebRequest} before delegating to + * {@link #startCallableProcessing(Callable, Object...)}. + * @param asyncTask an asyncTask containing the target {@code Callable} + * @param processingContext additional context to save that can be accessed + * via {@link #getConcurrentResultContext()} + */ + public void startCallableProcessing(AsyncTask asyncTask, Object... processingContext) { + Assert.notNull(asyncTask, "AsyncTask must not be null"); + + Long timeout = asyncTask.getTimeout(); + if (timeout != null) { + this.asyncWebRequest.setTimeout(timeout); + } + + AsyncTaskExecutor executor = asyncTask.getExecutor(); + if (executor != null) { + this.taskExecutor = executor; + } + + startCallableProcessing(asyncTask.getCallable(), processingContext); + } + + /** + * Start concurrent request processing and initialize the given {@link DeferredResult} + * with a {@link DeferredResultHandler} that saves the result and dispatches + * the request to resume processing of that result. + * The {@code AsyncWebRequest} is also updated with a completion handler that + * expires the {@code DeferredResult} and a timeout handler assuming the + * {@code DeferredResult} has a default timeout result. * * @param deferredResult the DeferredResult instance to initialize - * @param processingContext additional context to save for later access via - * {@link #getConcurrentResultContext()} + * @param processingContext additional context to save that can be accessed + * via {@link #getConcurrentResultContext()} + * + * @see #getConcurrentResult() + * @see #getConcurrentResultContext() */ public void startDeferredResultProcessing(final DeferredResult deferredResult, Object... processingContext) { - Assert.notNull(deferredResult, "DeferredResult is required"); + Assert.notNull(deferredResult, "DeferredResult must not be null"); - startAsyncProcessing(processingContext); + Long timeout = deferredResult.getTimeoutMilliseconds(); + if (timeout != null) { + this.asyncWebRequest.setTimeout(timeout); + } this.asyncWebRequest.addCompletionHandler(new Runnable() { public void run() { @@ -283,6 +280,8 @@ public final class WebAsyncManager { }); } + startAsyncProcessing(processingContext); + deferredResult.setResultHandler(new DeferredResultHandler() { public void handleResult(Object result) { @@ -300,13 +299,13 @@ public final class WebAsyncManager { }); } - private void startAsyncProcessing(Object... context) { + private void startAsyncProcessing(Object[] processingContext) { - Assert.state(this.asyncWebRequest != null, "AsyncWebRequest was not set"); + Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null"); this.asyncWebRequest.startAsync(); this.concurrentResult = null; - this.concurrentResultContext = context; + this.concurrentResultContext = processingContext; if (logger.isDebugEnabled()) { HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class); @@ -315,11 +314,38 @@ public final class WebAsyncManager { } } + /** + * Register an {@link WebAsyncThreadInitializer} for the current request. It may + * later be accessed and applied via {@link #initializeAsyncThread(String)} + * and will also be used to initialize and reset threads for concurrent handler execution. + * @param key a unique the key under which to keep the initializer + * @param initializer the initializer instance + */ + public void registerAsyncThreadInitializer(Object key, WebAsyncThreadInitializer initializer) { + Assert.notNull(initializer, "WebAsyncThreadInitializer must not be null"); + this.threadInitializers.put(key, initializer); + } /** - * A contract for initializing and resetting a thread. + * Invoke the {@linkplain WebAsyncThreadInitializer#initialize() initialize()} + * method of the named {@link WebAsyncThreadInitializer}. + * @param key the key under which the initializer was registered + * @return whether an initializer was found and applied */ - public interface AsyncThreadInitializer { + public boolean initializeAsyncThread(Object key) { + WebAsyncThreadInitializer initializer = this.threadInitializers.get(key); + if (initializer != null) { + initializer.initialize(); + return true; + } + return false; + } + + + /** + * Initialize and reset thread-bound variables. + */ + public interface WebAsyncThreadInitializer { void initialize(); diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/package-info.java b/spring-web/src/main/java/org/springframework/web/context/request/async/package-info.java new file mode 100644 index 00000000000..cc58da1e6ef --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/package-info.java @@ -0,0 +1,10 @@ + +/** + * + * Support for asynchronous request processing. + * + * @since 3.2 + * + */ +package org.springframework.web.context.request.async; + diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java index 3bbe8b1c251..a3df7a58f04 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java @@ -108,7 +108,7 @@ public class DeferredResultTests { @Test public void hasTimeout() { assertFalse(new DeferredResult().hasTimeoutResult()); - assertTrue(new DeferredResult("timed out").hasTimeoutResult()); + assertTrue(new DeferredResult(null, "timed out").hasTimeoutResult()); } @Test @@ -117,7 +117,7 @@ public class DeferredResultTests { handler.handleResult("timed out"); replay(handler); - DeferredResult result = new DeferredResult("timed out"); + DeferredResult result = new DeferredResult(null, "timed out"); result.setResultHandler(handler); assertTrue(result.applyTimeoutResult()); diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java index 2c80e6f8b73..e8ce85bae33 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java @@ -135,4 +135,10 @@ public class StandardServletAsyncWebRequestTests { verify(timeoutHandler); } + @Test(expected=IllegalStateException.class) + public void setTimeoutDuringConcurrentHandling() { + this.asyncRequest.startAsync(); + this.asyncRequest.setTimeout(25L); + } + } diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java index 266f8af37a4..019385fd876 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java @@ -16,26 +16,26 @@ package org.springframework.web.context.request.async; -import static org.hamcrest.Matchers.containsString; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.notNull; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.concurrent.Callable; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.junit.Before; import org.junit.Test; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer; /** @@ -47,168 +47,140 @@ public class WebAsyncManagerTests { private WebAsyncManager asyncManager; - private MockHttpServletRequest request; - - private StubAsyncWebRequest stubAsyncWebRequest; + private AsyncWebRequest asyncWebRequest; @Before public void setUp() { - this.request = new MockHttpServletRequest(); - this.stubAsyncWebRequest = new StubAsyncWebRequest(this.request, new MockHttpServletResponse()); - - this.asyncManager = AsyncWebUtils.getAsyncManager(this.request); + this.asyncManager = AsyncWebUtils.getAsyncManager(new MockHttpServletRequest()); this.asyncManager.setTaskExecutor(new SyncTaskExecutor()); - this.asyncManager.setAsyncWebRequest(this.stubAsyncWebRequest); - } - @Test - public void getForCurrentRequest() throws Exception { - assertNotNull(this.asyncManager); - assertSame(this.asyncManager, AsyncWebUtils.getAsyncManager(this.request)); - assertSame(this.asyncManager, this.request.getAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE)); + this.asyncWebRequest = createStrictMock(AsyncWebRequest.class); + this.asyncWebRequest.addCompletionHandler((Runnable) notNull()); + replay(this.asyncWebRequest); + + this.asyncManager.setAsyncWebRequest(this.asyncWebRequest); + + verify(this.asyncWebRequest); + reset(this.asyncWebRequest); } @Test public void isConcurrentHandlingStarted() { + + expect(this.asyncWebRequest.isAsyncStarted()).andReturn(false); + replay(this.asyncWebRequest); + assertFalse(this.asyncManager.isConcurrentHandlingStarted()); - this.stubAsyncWebRequest.startAsync(); + verify(this.asyncWebRequest); + reset(this.asyncWebRequest); + + expect(this.asyncWebRequest.isAsyncStarted()).andReturn(true); + replay(this.asyncWebRequest); + assertTrue(this.asyncManager.isConcurrentHandlingStarted()); + + verify(this.asyncWebRequest); } @Test(expected=IllegalArgumentException.class) public void setAsyncWebRequestAfterAsyncStarted() { - this.stubAsyncWebRequest.startAsync(); + this.asyncWebRequest.startAsync(); this.asyncManager.setAsyncWebRequest(null); } @Test - public void startCallableChainProcessing() throws Exception { + public void startCallableProcessing() throws Exception { + + WebAsyncThreadInitializer initializer = createStrictMock(WebAsyncThreadInitializer.class); + initializer.initialize(); + initializer.reset(); + replay(initializer); + + this.asyncWebRequest.startAsync(); + expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false); + this.asyncWebRequest.dispatch(); + replay(this.asyncWebRequest); + + this.asyncManager.registerAsyncThreadInitializer("testInitializer", initializer); this.asyncManager.startCallableProcessing(new Callable() { public Object call() throws Exception { return 1; } }); - assertTrue(this.asyncManager.isConcurrentHandlingStarted()); - assertTrue(this.stubAsyncWebRequest.isDispatched()); + verify(initializer, this.asyncWebRequest); } @Test - public void startCallableChainProcessingStaleRequest() { - this.stubAsyncWebRequest.setAsyncComplete(true); - this.asyncManager.startCallableProcessing(new Callable() { - public Object call() throws Exception { - return 1; - } - }); + public void startCallableProcessingAsyncTask() { - assertFalse(this.stubAsyncWebRequest.isDispatched()); + AsyncTaskExecutor executor = createMock(AsyncTaskExecutor.class); + expect(executor.submit((Runnable) notNull())).andReturn(null); + replay(executor); + + this.asyncWebRequest.setTimeout(1000L); + this.asyncWebRequest.startAsync(); + replay(this.asyncWebRequest); + + AsyncTask asyncTask = new AsyncTask(1000L, executor, createMock(Callable.class)); + this.asyncManager.startCallableProcessing(asyncTask); + + verify(executor, this.asyncWebRequest); } @Test - public void startCallableChainProcessingCallableRequired() { + public void startCallableProcessingNullCallable() { try { - this.asyncManager.startCallableProcessing(null); + this.asyncManager.startCallableProcessing((Callable) null); fail("Expected exception"); } catch (IllegalArgumentException ex) { - assertEquals(ex.getMessage(), "Callable is required"); + assertEquals(ex.getMessage(), "Callable must not be null"); } } @Test - public void startCallableChainProcessingAsyncWebRequestRequired() { - this.request.removeAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE); - this.asyncManager = AsyncWebUtils.getAsyncManager(this.request); + public void startCallableProcessingNullRequest() { + WebAsyncManager manager = AsyncWebUtils.getAsyncManager(new MockHttpServletRequest()); try { - this.asyncManager.startCallableProcessing(new Callable() { + manager.startCallableProcessing(new Callable() { public Object call() throws Exception { - return null; + return 1; } }); fail("Expected exception"); } catch (IllegalStateException ex) { - assertEquals(ex.getMessage(), "AsyncWebRequest was not set"); + assertEquals(ex.getMessage(), "AsyncWebRequest must not be null"); } } @Test public void startDeferredResultProcessing() throws Exception { - DeferredResult deferredResult = new DeferredResult(); + + this.asyncWebRequest.setTimeout(1000L); + this.asyncWebRequest.addCompletionHandler((Runnable) notNull()); + this.asyncWebRequest.setTimeoutHandler((Runnable) notNull()); + this.asyncWebRequest.startAsync(); + replay(this.asyncWebRequest); + + DeferredResult deferredResult = new DeferredResult(1000L, 10); this.asyncManager.startDeferredResultProcessing(deferredResult); - assertTrue(this.asyncManager.isConcurrentHandlingStarted()); + verify(this.asyncWebRequest); + reset(this.asyncWebRequest); + + expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false); + this.asyncWebRequest.dispatch(); + replay(this.asyncWebRequest); deferredResult.setResult(25); + assertEquals(25, this.asyncManager.getConcurrentResult()); + verify(this.asyncWebRequest); } - @Test - public void startDeferredResultProcessingStaleRequest() throws Exception { - DeferredResult deferredResult = new DeferredResult(); - this.asyncManager.startDeferredResultProcessing(deferredResult); - - this.stubAsyncWebRequest.setAsyncComplete(true); - assertFalse(deferredResult.setResult(1)); - } - - @Test - public void startDeferredResultProcessingDeferredResultRequired() { - try { - this.asyncManager.startDeferredResultProcessing(null); - fail("Expected exception"); - } - catch (IllegalArgumentException ex) { - assertThat(ex.getMessage(), containsString("DeferredResult is required")); - } - } - - - private static class StubAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest { - - private boolean asyncStarted; - - private boolean dispatched; - - private boolean asyncComplete; - - public StubAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) { - super(request, response); - } - - public void setTimeout(Long timeout) { } - - public void setTimeoutHandler(Runnable runnable) { } - - public void startAsync() { - this.asyncStarted = true; - } - - public boolean isAsyncStarted() { - return this.asyncStarted; - } - - public void dispatch() { - this.dispatched = true; - } - - public boolean isDispatched() { - return dispatched; - } - - public void setAsyncComplete(boolean asyncComplete) { - this.asyncComplete = asyncComplete; - } - - public boolean isAsyncComplete() { - return this.asyncComplete; - } - - public void addCompletionHandler(Runnable runnable) { - } - } @SuppressWarnings("serial") private static class SyncTaskExecutor extends SimpleAsyncTaskExecutor { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java index 0336487e46e..9995e3696eb 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java @@ -49,7 +49,7 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.async.AsyncWebUtils; import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer; +import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer; import org.springframework.web.context.support.ServletRequestHandledEvent; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.context.support.XmlWebApplicationContext; @@ -991,9 +991,9 @@ public abstract class FrameworkServlet extends HttpServletBean { } } - private AsyncThreadInitializer createAsyncThreadInitializer(final HttpServletRequest request) { + private WebAsyncThreadInitializer createAsyncThreadInitializer(final HttpServletRequest request) { - return new AsyncThreadInitializer() { + return new WebAsyncThreadInitializer() { public void initialize() { initContextHolders(request, buildLocaleContext(request), new ServletRequestAttributes(request)); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncMethodReturnValueHandler.java deleted file mode 100644 index 31d432af2c3..00000000000 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncMethodReturnValueHandler.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2002-2012 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.servlet.mvc.method.annotation; - -import java.lang.reflect.Method; -import java.util.concurrent.Callable; - -import javax.servlet.ServletRequest; - -import org.springframework.core.MethodParameter; -import org.springframework.util.Assert; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.async.DeferredResult; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.AsyncWebUtils; -import org.springframework.web.method.support.HandlerMethodReturnValueHandler; -import org.springframework.web.method.support.ModelAndViewContainer; - -/** - * Handles return values of type {@link Callable} and {@link DeferredResult}. - * - *

This handler does not have a defined behavior for {@code null} return - * values and will raise an {@link IllegalArgumentException}. - * - * @author Rossen Stoyanchev - * @since 3.2 - */ -public class AsyncMethodReturnValueHandler implements HandlerMethodReturnValueHandler { - - public boolean supportsReturnType(MethodParameter returnType) { - Class paramType = returnType.getParameterType(); - return Callable.class.isAssignableFrom(paramType) || DeferredResult.class.isAssignableFrom(paramType); - } - - @SuppressWarnings("unchecked") - public void handleReturnValue(Object returnValue, - MethodParameter returnType, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest) throws Exception { - - Assert.notNull(returnValue, "A Callable or a DeferredValue is required"); - - Class paramType = returnType.getParameterType(); - ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class); - WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(servletRequest); - - if (Callable.class.isAssignableFrom(paramType)) { - asyncManager.startCallableProcessing((Callable) returnValue, mavContainer); - } - else if (DeferredResult.class.isAssignableFrom(paramType)) { - asyncManager.startDeferredResultProcessing((DeferredResult) returnValue, mavContainer); - } - else { - // should never happen.. - Method method = returnType.getMethod(); - throw new UnsupportedOperationException("Unknown return value: " + paramType + " in method: " + method); - } - } - -} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncTaskMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncTaskMethodReturnValueHandler.java new file mode 100644 index 00000000000..3892fccd926 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncTaskMethodReturnValueHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2012 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.servlet.mvc.method.annotation; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.MethodParameter; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.AsyncTask; +import org.springframework.web.context.request.async.AsyncWebUtils; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * Handles return values of type {@link AsyncTask}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnValueHandler { + + private final BeanFactory beanFactory; + + + public AsyncTaskMethodReturnValueHandler(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + public boolean supportsReturnType(MethodParameter returnType) { + Class paramType = returnType.getParameterType(); + return AsyncTask.class.isAssignableFrom(paramType); + } + + public void handleReturnValue(Object returnValue, + MethodParameter returnType, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest) throws Exception { + + if (returnValue == null) { + return; + } + + AsyncTask asyncTask = (AsyncTask) returnValue; + asyncTask.setBeanFactory(this.beanFactory); + AsyncWebUtils.getAsyncManager(webRequest).startCallableProcessing(asyncTask.getCallable(), mavContainer); + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CallableMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CallableMethodReturnValueHandler.java new file mode 100644 index 00000000000..ab53315b64f --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CallableMethodReturnValueHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2012 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.servlet.mvc.method.annotation; + +import java.util.concurrent.Callable; + +import org.springframework.core.MethodParameter; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.AsyncWebUtils; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * Handles return values of type {@link Callable}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class CallableMethodReturnValueHandler implements HandlerMethodReturnValueHandler { + + public boolean supportsReturnType(MethodParameter returnType) { + Class paramType = returnType.getParameterType(); + return Callable.class.isAssignableFrom(paramType); + } + + public void handleReturnValue(Object returnValue, + MethodParameter returnType, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest) throws Exception { + + if (returnValue == null) { + return; + } + + Callable callable = (Callable) returnValue; + AsyncWebUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer); + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java new file mode 100644 index 00000000000..8e20f4de220 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2012 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.servlet.mvc.method.annotation; + +import org.springframework.core.MethodParameter; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.AsyncWebUtils; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * Handles return values of type {@link DeferredResult}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class DeferredResultMethodReturnValueHandler implements HandlerMethodReturnValueHandler { + + public boolean supportsReturnType(MethodParameter returnType) { + Class paramType = returnType.getParameterType(); + return DeferredResult.class.isAssignableFrom(paramType); + } + + public void handleReturnValue(Object returnValue, + MethodParameter returnType, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest) throws Exception { + + if (returnValue == null) { + return; + } + + DeferredResult deferredResult = (DeferredResult) returnValue; + AsyncWebUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer); + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index 1e0e2bafb86..ebfdd5b054a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -16,7 +16,6 @@ package org.springframework.web.servlet.mvc.method.annotation; -import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; @@ -27,13 +26,11 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.xml.transform.Source; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; @@ -50,7 +47,6 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; import org.springframework.ui.ModelMap; -import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils.MethodFilter; import org.springframework.web.accept.ContentNegotiationManager; @@ -68,7 +64,6 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.async.AsyncWebRequest; import org.springframework.web.context.request.async.AsyncWebUtils; -import org.springframework.web.context.request.async.NoSupportAsyncWebRequest; import org.springframework.web.context.request.async.WebAsyncManager; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethodSelector; @@ -552,7 +547,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager)); - handlers.add(new AsyncMethodReturnValueHandler()); + handlers.add(new CallableMethodReturnValueHandler()); + handlers.add(new DeferredResultMethodReturnValueHandler()); + handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); // Annotation-based return value types handlers.add(new ModelAttributeMethodProcessor(false)); @@ -690,7 +687,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); - AsyncWebRequest asyncWebRequest = createAsyncWebRequest(request, response); + AsyncWebRequest asyncWebRequest = AsyncWebUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); final WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request); @@ -700,7 +697,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; - asyncManager.resetConcurrentResult(); + asyncManager.clearConcurrentResult(); if (logger.isDebugEnabled()) { logger.debug("Found concurrent result value [" + result + "]"); @@ -802,23 +799,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer()); } - private AsyncWebRequest createAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) { - return ClassUtils.hasMethod(ServletRequest.class, "startAsync") ? - createStandardServletAsyncWebRequest(request, response) : new NoSupportAsyncWebRequest(request, response); - } - - private AsyncWebRequest createStandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) { - try { - String className = "org.springframework.web.context.request.async.StandardServletAsyncWebRequest"; - Class clazz = ClassUtils.forName(className, this.getClass().getClassLoader()); - Constructor constructor = clazz.getConstructor(HttpServletRequest.class, HttpServletResponse.class); - return (AsyncWebRequest) BeanUtils.instantiateClass(constructor, request, response); - } - catch (Throwable t) { - throw new IllegalStateException("Failed to instantiate StandardServletAsyncWebRequest", t); - } - } - private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {