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.mock.web;
018
019import java.io.IOException;
020import java.util.Arrays;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.List;
024
025import javax.servlet.Filter;
026import javax.servlet.FilterChain;
027import javax.servlet.FilterConfig;
028import javax.servlet.Servlet;
029import javax.servlet.ServletException;
030import javax.servlet.ServletRequest;
031import javax.servlet.ServletResponse;
032
033import org.springframework.lang.Nullable;
034import org.springframework.util.Assert;
035import org.springframework.util.ObjectUtils;
036
037/**
038 * Mock implementation of the {@link javax.servlet.FilterChain} interface.
039 *
040 * <p>A {@link MockFilterChain} can be configured with one or more filters and a
041 * Servlet to invoke. The first time the chain is called, it invokes all filters
042 * and the Servlet, and saves the request and response. Subsequent invocations
043 * raise an {@link IllegalStateException} unless {@link #reset()} is called.
044 *
045 * @author Juergen Hoeller
046 * @author Rob Winch
047 * @author Rossen Stoyanchev
048 * @since 2.0.3
049 * @see MockFilterConfig
050 * @see PassThroughFilterChain
051 */
052public class MockFilterChain implements FilterChain {
053
054        @Nullable
055        private ServletRequest request;
056
057        @Nullable
058        private ServletResponse response;
059
060        private final List<Filter> filters;
061
062        @Nullable
063        private Iterator<Filter> iterator;
064
065
066        /**
067         * Register a single do-nothing {@link Filter} implementation. The first
068         * invocation saves the request and response. Subsequent invocations raise
069         * an {@link IllegalStateException} unless {@link #reset()} is called.
070         */
071        public MockFilterChain() {
072                this.filters = Collections.emptyList();
073        }
074
075        /**
076         * Create a FilterChain with a Servlet.
077         * @param servlet the Servlet to invoke
078         * @since 3.2
079         */
080        public MockFilterChain(Servlet servlet) {
081                this.filters = initFilterList(servlet);
082        }
083
084        /**
085         * Create a {@code FilterChain} with Filter's and a Servlet.
086         * @param servlet the {@link Servlet} to invoke in this {@link FilterChain}
087         * @param filters the {@link Filter}'s to invoke in this {@link FilterChain}
088         * @since 3.2
089         */
090        public MockFilterChain(Servlet servlet, Filter... filters) {
091                Assert.notNull(filters, "filters cannot be null");
092                Assert.noNullElements(filters, "filters cannot contain null values");
093                this.filters = initFilterList(servlet, filters);
094        }
095
096        private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
097                Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
098                return Arrays.asList(allFilters);
099        }
100
101
102        /**
103         * Return the request that {@link #doFilter} has been called with.
104         */
105        @Nullable
106        public ServletRequest getRequest() {
107                return this.request;
108        }
109
110        /**
111         * Return the response that {@link #doFilter} has been called with.
112         */
113        @Nullable
114        public ServletResponse getResponse() {
115                return this.response;
116        }
117
118        /**
119         * Invoke registered {@link Filter Filters} and/or {@link Servlet} also saving the
120         * request and response.
121         */
122        @Override
123        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
124                Assert.notNull(request, "Request must not be null");
125                Assert.notNull(response, "Response must not be null");
126                Assert.state(this.request == null, "This FilterChain has already been called!");
127
128                if (this.iterator == null) {
129                        this.iterator = this.filters.iterator();
130                }
131
132                if (this.iterator.hasNext()) {
133                        Filter nextFilter = this.iterator.next();
134                        nextFilter.doFilter(request, response, this);
135                }
136
137                this.request = request;
138                this.response = response;
139        }
140
141        /**
142         * Reset the {@link MockFilterChain} allowing it to be invoked again.
143         */
144        public void reset() {
145                this.request = null;
146                this.response = null;
147                this.iterator = null;
148        }
149
150
151        /**
152         * A filter that simply delegates to a Servlet.
153         */
154        private static final class ServletFilterProxy implements Filter {
155
156                private final Servlet delegateServlet;
157
158                private ServletFilterProxy(Servlet servlet) {
159                        Assert.notNull(servlet, "servlet cannot be null");
160                        this.delegateServlet = servlet;
161                }
162
163                @Override
164                public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
165                                throws IOException, ServletException {
166
167                        this.delegateServlet.service(request, response);
168                }
169
170                @Override
171                public void init(FilterConfig filterConfig) throws ServletException {
172                }
173
174                @Override
175                public void destroy() {
176                }
177
178                @Override
179                public String toString() {
180                        return this.delegateServlet.toString();
181                }
182        }
183
184}