001/*
002 * Copyright 2002-2020 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.htmlunit;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022
023import com.gargoylesoftware.htmlunit.WebClient;
024import com.gargoylesoftware.htmlunit.WebConnection;
025
026import org.springframework.test.web.servlet.MockMvc;
027import org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection.DelegateWebConnection;
028import org.springframework.test.web.servlet.setup.MockMvcBuilders;
029import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
030import org.springframework.util.Assert;
031import org.springframework.web.context.WebApplicationContext;
032
033/**
034 * Support class that simplifies the creation of a {@link WebConnection} that
035 * uses {@link MockMvc} and optionally delegates to a real {@link WebConnection}
036 * for specific requests.
037 *
038 * <p>The default is to use {@link MockMvc} for requests to {@code localhost}
039 * and otherwise use a real {@link WebConnection}.
040 *
041 * @author Rob Winch
042 * @author Sam Brannen
043 * @since 4.2
044 * @param <T> a self reference to the builder type
045 */
046public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebConnectionBuilderSupport<T>> {
047
048        private final MockMvc mockMvc;
049
050        private final List<WebRequestMatcher> requestMatchers = new ArrayList<>();
051
052        private String contextPath = "";
053
054        private boolean alwaysUseMockMvc;
055
056
057        /**
058         * Create a new instance using the supplied {@link MockMvc} instance.
059         * @param mockMvc the {@code MockMvc} instance to use; never {@code null}
060         */
061        protected MockMvcWebConnectionBuilderSupport(MockMvc mockMvc) {
062                Assert.notNull(mockMvc, "MockMvc must not be null");
063                this.mockMvc = mockMvc;
064                this.requestMatchers.add(new HostRequestMatcher("localhost"));
065        }
066
067        /**
068         * Create a new instance using the supplied {@link WebApplicationContext}.
069         * @param context the {@code WebApplicationContext} to create a {@code MockMvc}
070         * instance from; never {@code null}
071         */
072        protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context) {
073                this(MockMvcBuilders.webAppContextSetup(context).build());
074        }
075
076        /**
077         * Create a new instance using the supplied {@link WebApplicationContext}
078         * and {@link MockMvcConfigurer}.
079         * @param context the {@code WebApplicationContext} to create a {@code MockMvc}
080         * instance from; never {@code null}
081         * @param configurer the MockMvcConfigurer to apply; never {@code null}
082         */
083        protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context, MockMvcConfigurer configurer) {
084                this(MockMvcBuilders.webAppContextSetup(context).apply(configurer).build());
085        }
086
087
088        /**
089         * Set the context path to use.
090         * <p>If the supplied value is {@code null} or empty, the first path
091         * segment of the request URL is assumed to be the context path.
092         * <p>Default is {@code ""}.
093         * @param contextPath the context path to use
094         * @return this builder for further customization
095         */
096        @SuppressWarnings("unchecked")
097        public T contextPath(String contextPath) {
098                this.contextPath = contextPath;
099                return (T) this;
100        }
101
102        /**
103         * Specify that {@link MockMvc} should always be used regardless of
104         * what the request looks like.
105         * @return this builder for further customization
106         */
107        @SuppressWarnings("unchecked")
108        public T alwaysUseMockMvc() {
109                this.alwaysUseMockMvc = true;
110                return (T) this;
111        }
112
113        /**
114         * Add additional {@link WebRequestMatcher} instances that will ensure
115         * that {@link MockMvc} is used to process the request, if such a matcher
116         * matches against the web request.
117         * @param matchers additional {@code WebRequestMatcher} instances
118         * @return this builder for further customization
119         */
120        @SuppressWarnings("unchecked")
121        public T useMockMvc(WebRequestMatcher... matchers) {
122                Collections.addAll(this.requestMatchers, matchers);
123                return (T) this;
124        }
125
126        /**
127         * Add additional {@link WebRequestMatcher} instances that return {@code true}
128         * if a supplied host matches &mdash; for example, {@code "example.com"} or
129         * {@code "example.com:8080"}.
130         * @param hosts additional hosts that ensure {@code MockMvc} gets invoked
131         * @return this builder for further customization
132         */
133        @SuppressWarnings("unchecked")
134        public T useMockMvcForHosts(String... hosts) {
135                this.requestMatchers.add(new HostRequestMatcher(hosts));
136                return (T) this;
137        }
138
139        /**
140         * Create a new {@link WebConnection} that will use a {@link MockMvc}
141         * instance if one of the specified {@link WebRequestMatcher} instances
142         * matches.
143         * @param webClient the WebClient to use if none of the specified
144         * {@code WebRequestMatcher} instances matches (never {@code null})
145         * @return a new {@code WebConnection} that will use a {@code MockMvc}
146         * instance if one of the specified {@code WebRequestMatcher} matches
147         * @since 4.3
148         * @see #alwaysUseMockMvc()
149         * @see #useMockMvc(WebRequestMatcher...)
150         * @see #useMockMvcForHosts(String...)
151         */
152        protected final WebConnection createConnection(WebClient webClient) {
153                Assert.notNull(webClient, "WebClient must not be null");
154                return createConnection(webClient, webClient.getWebConnection());
155        }
156
157        private WebConnection createConnection(WebClient webClient, WebConnection defaultConnection) {
158                WebConnection connection = new MockMvcWebConnection(this.mockMvc, webClient, this.contextPath);
159                if (this.alwaysUseMockMvc) {
160                        return connection;
161                }
162                List<DelegateWebConnection> delegates = new ArrayList<>(this.requestMatchers.size());
163                for (WebRequestMatcher matcher : this.requestMatchers) {
164                        delegates.add(new DelegateWebConnection(matcher, connection));
165                }
166                return new DelegatingWebConnection(defaultConnection, delegates);
167        }
168
169}