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.test.web.servlet.setup;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import javax.servlet.Filter;
023import javax.servlet.ServletContext;
024
025import org.springframework.lang.Nullable;
026import org.springframework.mock.web.MockServletConfig;
027import org.springframework.test.web.servlet.DispatcherServletCustomizer;
028import org.springframework.test.web.servlet.MockMvc;
029import org.springframework.test.web.servlet.MockMvcBuilder;
030import org.springframework.test.web.servlet.MockMvcBuilderSupport;
031import org.springframework.test.web.servlet.RequestBuilder;
032import org.springframework.test.web.servlet.ResultHandler;
033import org.springframework.test.web.servlet.ResultMatcher;
034import org.springframework.test.web.servlet.request.ConfigurableSmartRequestBuilder;
035import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
036import org.springframework.test.web.servlet.request.RequestPostProcessor;
037import org.springframework.util.Assert;
038import org.springframework.web.context.WebApplicationContext;
039
040/**
041 * Abstract implementation of {@link MockMvcBuilder} with common methods for
042 * configuring filters, default request properties, global expectations and
043 * global result actions.
044 *
045 * <p>Subclasses can use different strategies to prepare the Spring
046 * {@code WebApplicationContext} that will be passed to the
047 * {@code DispatcherServlet}.
048 *
049 * @author Rossen Stoyanchev
050 * @author Stephane Nicoll
051 * @since 4.0
052 * @param <B> a self reference to the builder type
053 */
054public abstract class AbstractMockMvcBuilder<B extends AbstractMockMvcBuilder<B>>
055                extends MockMvcBuilderSupport implements ConfigurableMockMvcBuilder<B> {
056
057        private List<Filter> filters = new ArrayList<>();
058
059        @Nullable
060        private RequestBuilder defaultRequestBuilder;
061
062        private final List<ResultMatcher> globalResultMatchers = new ArrayList<>();
063
064        private final List<ResultHandler> globalResultHandlers = new ArrayList<>();
065
066        private final List<DispatcherServletCustomizer> dispatcherServletCustomizers = new ArrayList<>();
067
068        private final List<MockMvcConfigurer> configurers = new ArrayList<>(4);
069
070
071        @Override
072        public final <T extends B> T addFilters(Filter... filters) {
073                Assert.notNull(filters, "filters cannot be null");
074                for (Filter f : filters) {
075                        Assert.notNull(f, "filters cannot contain null values");
076                        this.filters.add(f);
077                }
078                return self();
079        }
080
081        @Override
082        public final <T extends B> T addFilter(Filter filter, String... urlPatterns) {
083                Assert.notNull(filter, "filter cannot be null");
084                Assert.notNull(urlPatterns, "urlPatterns cannot be null");
085                if (urlPatterns.length > 0) {
086                        filter = new PatternMappingFilterProxy(filter, urlPatterns);
087                }
088                this.filters.add(filter);
089                return self();
090        }
091
092        @Override
093        public final <T extends B> T defaultRequest(RequestBuilder requestBuilder) {
094                this.defaultRequestBuilder = requestBuilder;
095                return self();
096        }
097
098        @Override
099        public final <T extends B> T alwaysExpect(ResultMatcher resultMatcher) {
100                this.globalResultMatchers.add(resultMatcher);
101                return self();
102        }
103
104        @Override
105        public final <T extends B> T alwaysDo(ResultHandler resultHandler) {
106                this.globalResultHandlers.add(resultHandler);
107                return self();
108        }
109
110        public final <T extends B> T addDispatcherServletCustomizer(DispatcherServletCustomizer customizer) {
111                this.dispatcherServletCustomizers.add(customizer);
112                return self();
113        }
114
115        @Override
116        public final <T extends B> T dispatchOptions(boolean dispatchOptions) {
117                return addDispatcherServletCustomizer(
118                                dispatcherServlet -> dispatcherServlet.setDispatchOptionsRequest(dispatchOptions));
119        }
120
121        @Override
122        public final <T extends B> T apply(MockMvcConfigurer configurer) {
123                configurer.afterConfigurerAdded(this);
124                this.configurers.add(configurer);
125                return self();
126        }
127
128        @SuppressWarnings("unchecked")
129        protected <T extends B> T self() {
130                return (T) this;
131        }
132
133
134        /**
135         * Build a {@link org.springframework.test.web.servlet.MockMvc} instance.
136         */
137        @Override
138        @SuppressWarnings("rawtypes")
139        public final MockMvc build() {
140                WebApplicationContext wac = initWebAppContext();
141                ServletContext servletContext = wac.getServletContext();
142                MockServletConfig mockServletConfig = new MockServletConfig(servletContext);
143
144                for (MockMvcConfigurer configurer : this.configurers) {
145                        RequestPostProcessor processor = configurer.beforeMockMvcCreated(this, wac);
146                        if (processor != null) {
147                                if (this.defaultRequestBuilder == null) {
148                                        this.defaultRequestBuilder = MockMvcRequestBuilders.get("/");
149                                }
150                                if (this.defaultRequestBuilder instanceof ConfigurableSmartRequestBuilder) {
151                                        ((ConfigurableSmartRequestBuilder) this.defaultRequestBuilder).with(processor);
152                                }
153                        }
154                }
155
156                Filter[] filterArray = this.filters.toArray(new Filter[0]);
157
158                return super.createMockMvc(filterArray, mockServletConfig, wac, this.defaultRequestBuilder,
159                                this.globalResultMatchers, this.globalResultHandlers, this.dispatcherServletCustomizers);
160        }
161
162        /**
163         * A method to obtain the {@code WebApplicationContext} to be passed to the
164         * {@code DispatcherServlet}. Invoked from {@link #build()} before the
165         * {@link MockMvc} instance is created.
166         */
167        protected abstract WebApplicationContext initWebAppContext();
168
169}