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 — 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}