001/*
002 * Copyright 2002-2018 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.concurrent.Callable;
020
021import org.springframework.beans.factory.BeanFactory;
022import org.springframework.beans.factory.BeanFactoryAware;
023import org.springframework.core.task.AsyncTaskExecutor;
024import org.springframework.lang.Nullable;
025import org.springframework.util.Assert;
026import org.springframework.web.context.request.NativeWebRequest;
027
028/**
029 * Holder for a {@link Callable}, a timeout value, and a task executor.
030 *
031 * @author Rossen Stoyanchev
032 * @author Juergen Hoeller
033 * @since 3.2
034 * @param <V> the value type
035 */
036public class WebAsyncTask<V> implements BeanFactoryAware {
037
038        private final Callable<V> callable;
039
040        private Long timeout;
041
042        private AsyncTaskExecutor executor;
043
044        private String executorName;
045
046        private BeanFactory beanFactory;
047
048        private Callable<V> timeoutCallback;
049
050        private Callable<V> errorCallback;
051
052        private Runnable completionCallback;
053
054
055        /**
056         * Create a {@code WebAsyncTask} wrapping the given {@link Callable}.
057         * @param callable the callable for concurrent handling
058         */
059        public WebAsyncTask(Callable<V> callable) {
060                Assert.notNull(callable, "Callable must not be null");
061                this.callable = callable;
062        }
063
064        /**
065         * Create a {@code WebAsyncTask} with a timeout value and a {@link Callable}.
066         * @param timeout a timeout value in milliseconds
067         * @param callable the callable for concurrent handling
068         */
069        public WebAsyncTask(long timeout, Callable<V> callable) {
070                this(callable);
071                this.timeout = timeout;
072        }
073
074        /**
075         * Create a {@code WebAsyncTask} with a timeout value, an executor name, and a {@link Callable}.
076         * @param timeout the timeout value in milliseconds; ignored if {@code null}
077         * @param executorName the name of an executor bean to use
078         * @param callable the callable for concurrent handling
079         */
080        public WebAsyncTask(@Nullable Long timeout, String executorName, Callable<V> callable) {
081                this(callable);
082                Assert.notNull(executorName, "Executor name must not be null");
083                this.executorName = executorName;
084                this.timeout = timeout;
085        }
086
087        /**
088         * Create a {@code WebAsyncTask} with a timeout value, an executor instance, and a Callable.
089         * @param timeout the timeout value in milliseconds; ignored if {@code null}
090         * @param executor the executor to use
091         * @param callable the callable for concurrent handling
092         */
093        public WebAsyncTask(@Nullable Long timeout, AsyncTaskExecutor executor, Callable<V> callable) {
094                this(callable);
095                Assert.notNull(executor, "Executor must not be null");
096                this.executor = executor;
097                this.timeout = timeout;
098        }
099
100
101        /**
102         * Return the {@link Callable} to use for concurrent handling (never {@code null}).
103         */
104        public Callable<?> getCallable() {
105                return this.callable;
106        }
107
108        /**
109         * Return the timeout value in milliseconds, or {@code null} if no timeout is set.
110         */
111        @Nullable
112        public Long getTimeout() {
113                return this.timeout;
114        }
115
116        /**
117         * A {@link BeanFactory} to use for resolving an executor name.
118         * <p>This factory reference will automatically be set when
119         * {@code WebAsyncTask} is used within a Spring MVC controller.
120         */
121        @Override
122        public void setBeanFactory(BeanFactory beanFactory) {
123                this.beanFactory = beanFactory;
124        }
125
126        /**
127         * Return the AsyncTaskExecutor to use for concurrent handling,
128         * or {@code null} if none specified.
129         */
130        @Nullable
131        public AsyncTaskExecutor getExecutor() {
132                if (this.executor != null) {
133                        return this.executor;
134                }
135                else if (this.executorName != null) {
136                        Assert.state(this.beanFactory != null, "BeanFactory is required to look up an executor bean by name");
137                        return this.beanFactory.getBean(this.executorName, AsyncTaskExecutor.class);
138                }
139                else {
140                        return null;
141                }
142        }
143
144
145        /**
146         * Register code to invoke when the async request times out.
147         * <p>This method is called from a container thread when an async request times
148         * out before the {@code Callable} has completed. The callback is executed in
149         * the same thread and therefore should return without blocking. It may return
150         * an alternative value to use, including an {@link Exception} or return
151         * {@link CallableProcessingInterceptor#RESULT_NONE RESULT_NONE}.
152         */
153        public void onTimeout(Callable<V> callback) {
154                this.timeoutCallback = callback;
155        }
156
157        /**
158         * Register code to invoke for an error during async request processing.
159         * <p>This method is called from a container thread when an error occurred
160         * while processing an async request before the {@code Callable} has
161         * completed. The callback is executed in the same thread and therefore
162         * should return without blocking. It may return an alternative value to
163         * use, including an {@link Exception} or return
164         * {@link CallableProcessingInterceptor#RESULT_NONE RESULT_NONE}.
165         * @since 5.0
166         */
167        public void onError(Callable<V> callback) {
168                this.errorCallback = callback;
169        }
170
171        /**
172         * Register code to invoke when the async request completes.
173         * <p>This method is called from a container thread when an async request
174         * completed for any reason, including timeout and network error.
175         */
176        public void onCompletion(Runnable callback) {
177                this.completionCallback = callback;
178        }
179
180        CallableProcessingInterceptor getInterceptor() {
181                return new CallableProcessingInterceptor() {
182                        @Override
183                        public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
184                                return (timeoutCallback != null ? timeoutCallback.call() : CallableProcessingInterceptor.RESULT_NONE);
185                        }
186                        @Override
187                        public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {
188                                return (errorCallback != null ? errorCallback.call() : CallableProcessingInterceptor.RESULT_NONE);
189                        }
190                        @Override
191                        public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
192                                if (completionCallback != null) {
193                                        completionCallback.run();
194                                }
195                        }
196                };
197        }
198
199}