001/*
002 * Copyright 2002-2015 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.io.IOException;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.concurrent.atomic.AtomicBoolean;
023import javax.servlet.AsyncContext;
024import javax.servlet.AsyncEvent;
025import javax.servlet.AsyncListener;
026import javax.servlet.http.HttpServletRequest;
027import javax.servlet.http.HttpServletResponse;
028
029import org.springframework.util.Assert;
030import org.springframework.web.context.request.ServletWebRequest;
031
032/**
033 * A Servlet 3.0 implementation of {@link AsyncWebRequest}.
034 *
035 * <p>The servlet and all filters involved in an async request must have async
036 * support enabled using the Servlet API or by adding an
037 * {@code <async-supported>true</async-supported>} element to servlet and filter
038 * declarations in {@code web.xml}.
039 *
040 * @author Rossen Stoyanchev
041 * @since 3.2
042 */
043public class StandardServletAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest, AsyncListener {
044
045        private Long timeout;
046
047        private AsyncContext asyncContext;
048
049        private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
050
051        private final List<Runnable> timeoutHandlers = new ArrayList<Runnable>();
052
053        private ErrorHandler errorHandler;
054
055        private final List<Runnable> completionHandlers = new ArrayList<Runnable>();
056
057
058        /**
059         * Create a new instance for the given request/response pair.
060         * @param request current HTTP request
061         * @param response current HTTP response
062         */
063        public StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
064                super(request, response);
065        }
066
067
068        /**
069         * In Servlet 3 async processing, the timeout period begins after the
070         * container processing thread has exited.
071         */
072        @Override
073        public void setTimeout(Long timeout) {
074                Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress");
075                this.timeout = timeout;
076        }
077
078        @Override
079        public void addTimeoutHandler(Runnable timeoutHandler) {
080                this.timeoutHandlers.add(timeoutHandler);
081        }
082
083        void setErrorHandler(ErrorHandler errorHandler) {
084                this.errorHandler = errorHandler;
085        }
086
087        @Override
088        public void addCompletionHandler(Runnable runnable) {
089                this.completionHandlers.add(runnable);
090        }
091
092        @Override
093        public boolean isAsyncStarted() {
094                return (this.asyncContext != null && getRequest().isAsyncStarted());
095        }
096
097        /**
098         * Whether async request processing has completed.
099         * <p>It is important to avoid use of request and response objects after async
100         * processing has completed. Servlet containers often re-use them.
101         */
102        @Override
103        public boolean isAsyncComplete() {
104                return this.asyncCompleted.get();
105        }
106
107        @Override
108        public void startAsync() {
109                Assert.state(getRequest().isAsyncSupported(),
110                                "Async support must be enabled on a servlet and for all filters involved " +
111                                "in async request processing. This is done in Java code using the Servlet API " +
112                                "or by adding \"<async-supported>true</async-supported>\" to servlet and " +
113                                "filter declarations in web.xml.");
114                Assert.state(!isAsyncComplete(), "Async processing has already completed");
115
116                if (isAsyncStarted()) {
117                        return;
118                }
119                this.asyncContext = getRequest().startAsync(getRequest(), getResponse());
120                this.asyncContext.addListener(this);
121                if (this.timeout != null) {
122                        this.asyncContext.setTimeout(this.timeout);
123                }
124        }
125
126        @Override
127        public void dispatch() {
128                Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext");
129                this.asyncContext.dispatch();
130        }
131
132
133        // ---------------------------------------------------------------------
134        // Implementation of AsyncListener methods
135        // ---------------------------------------------------------------------
136
137        @Override
138        public void onStartAsync(AsyncEvent event) throws IOException {
139        }
140
141        @Override
142        public void onError(AsyncEvent event) throws IOException {
143                if (this.errorHandler != null) {
144                        this.errorHandler.handle(event.getThrowable());
145                }
146        }
147
148        @Override
149        public void onTimeout(AsyncEvent event) throws IOException {
150                for (Runnable handler : this.timeoutHandlers) {
151                        handler.run();
152                }
153        }
154
155        @Override
156        public void onComplete(AsyncEvent event) throws IOException {
157                for (Runnable handler : this.completionHandlers) {
158                        handler.run();
159                }
160                this.asyncContext = null;
161                this.asyncCompleted.set(true);
162        }
163
164
165        interface ErrorHandler {
166
167                void handle(Throwable ex);
168
169        }
170
171}