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