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.context.web; 018 019import javax.servlet.ServletContext; 020 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023 024import org.springframework.beans.factory.support.DefaultListableBeanFactory; 025import org.springframework.context.ApplicationContext; 026import org.springframework.context.ConfigurableApplicationContext; 027import org.springframework.context.annotation.AnnotationConfigUtils; 028import org.springframework.core.io.DefaultResourceLoader; 029import org.springframework.core.io.FileSystemResourceLoader; 030import org.springframework.core.io.ResourceLoader; 031import org.springframework.mock.web.MockServletContext; 032import org.springframework.test.context.MergedContextConfiguration; 033import org.springframework.test.context.support.AbstractContextLoader; 034import org.springframework.util.Assert; 035import org.springframework.web.context.WebApplicationContext; 036import org.springframework.web.context.support.GenericWebApplicationContext; 037 038/** 039 * Abstract, generic extension of {@link AbstractContextLoader} that loads a 040 * {@link GenericWebApplicationContext}. 041 * 042 * <p>If instances of concrete subclasses are invoked via the 043 * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader} 044 * SPI, the context will be loaded from the {@link MergedContextConfiguration} 045 * provided to {@link #loadContext(MergedContextConfiguration)}. In such cases, a 046 * {@code SmartContextLoader} will decide whether to load the context from 047 * <em>locations</em> or <em>annotated classes</em>. Note that {@code 048 * AbstractGenericWebContextLoader} does not support the {@code 049 * loadContext(String... locations)} method from the legacy 050 * {@link org.springframework.test.context.ContextLoader ContextLoader} SPI. 051 * 052 * <p>Concrete subclasses must provide an appropriate implementation of 053 * {@link #loadBeanDefinitions}. 054 * 055 * @author Sam Brannen 056 * @author Phillip Webb 057 * @since 3.2 058 * @see #loadContext(MergedContextConfiguration) 059 * @see #loadContext(String...) 060 */ 061public abstract class AbstractGenericWebContextLoader extends AbstractContextLoader { 062 063 protected static final Log logger = LogFactory.getLog(AbstractGenericWebContextLoader.class); 064 065 066 // SmartContextLoader 067 068 /** 069 * Load a Spring {@link WebApplicationContext} from the supplied 070 * {@link MergedContextConfiguration}. 071 * <p>Implementation details: 072 * <ul> 073 * <li>Calls {@link #validateMergedContextConfiguration(WebMergedContextConfiguration)} 074 * to allow subclasses to validate the supplied configuration before proceeding.</li> 075 * <li>Creates a {@link GenericWebApplicationContext} instance.</li> 076 * <li>If the supplied {@code MergedContextConfiguration} references a 077 * {@linkplain MergedContextConfiguration#getParent() parent configuration}, 078 * the corresponding {@link MergedContextConfiguration#getParentApplicationContext() 079 * ApplicationContext} will be retrieved and 080 * {@linkplain GenericWebApplicationContext#setParent(ApplicationContext) set as the parent} 081 * for the context created by this method.</li> 082 * <li>Delegates to {@link #configureWebResources} to create the 083 * {@link MockServletContext} and set it in the {@code WebApplicationContext}.</li> 084 * <li>Calls {@link #prepareContext} to allow for customizing the context 085 * before bean definitions are loaded.</li> 086 * <li>Calls {@link #customizeBeanFactory} to allow for customizing the 087 * context's {@code DefaultListableBeanFactory}.</li> 088 * <li>Delegates to {@link #loadBeanDefinitions} to populate the context 089 * from the locations or classes in the supplied {@code MergedContextConfiguration}.</li> 090 * <li>Delegates to {@link AnnotationConfigUtils} for 091 * {@linkplain AnnotationConfigUtils#registerAnnotationConfigProcessors registering} 092 * annotation configuration processors.</li> 093 * <li>Calls {@link #customizeContext} to allow for customizing the context 094 * before it is refreshed.</li> 095 * <li>{@link ConfigurableApplicationContext#refresh Refreshes} the 096 * context and registers a JVM shutdown hook for it.</li> 097 * </ul> 098 * @return a new web application context 099 * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration) 100 * @see GenericWebApplicationContext 101 */ 102 @Override 103 public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { 104 if (!(mergedConfig instanceof WebMergedContextConfiguration)) { 105 throw new IllegalArgumentException(String.format( 106 "Cannot load WebApplicationContext from non-web merged context configuration %s. " + 107 "Consider annotating your test class with @WebAppConfiguration.", mergedConfig)); 108 } 109 WebMergedContextConfiguration webMergedConfig = (WebMergedContextConfiguration) mergedConfig; 110 111 if (logger.isDebugEnabled()) { 112 logger.debug(String.format("Loading WebApplicationContext for merged context configuration %s.", 113 webMergedConfig)); 114 } 115 116 validateMergedContextConfiguration(webMergedConfig); 117 118 GenericWebApplicationContext context = new GenericWebApplicationContext(); 119 120 ApplicationContext parent = mergedConfig.getParentApplicationContext(); 121 if (parent != null) { 122 context.setParent(parent); 123 } 124 configureWebResources(context, webMergedConfig); 125 prepareContext(context, webMergedConfig); 126 customizeBeanFactory(context.getDefaultListableBeanFactory(), webMergedConfig); 127 loadBeanDefinitions(context, webMergedConfig); 128 AnnotationConfigUtils.registerAnnotationConfigProcessors(context); 129 customizeContext(context, webMergedConfig); 130 context.refresh(); 131 context.registerShutdownHook(); 132 return context; 133 } 134 135 /** 136 * Validate the supplied {@link WebMergedContextConfiguration} with respect to 137 * what this context loader supports. 138 * <p>The default implementation is a <em>no-op</em> but can be overridden by 139 * subclasses as appropriate. 140 * @param mergedConfig the merged configuration to validate 141 * @throws IllegalStateException if the supplied configuration is not valid 142 * for this context loader 143 * @since 4.0.4 144 */ 145 protected void validateMergedContextConfiguration(WebMergedContextConfiguration mergedConfig) { 146 /* no-op */ 147 } 148 149 /** 150 * Configures web resources for the supplied web application context (WAC). 151 * <h4>Implementation Details</h4> 152 * <p>If the supplied WAC has no parent or its parent is not a WAC, the 153 * supplied WAC will be configured as the Root WAC (see "<em>Root WAC 154 * Configuration</em>" below). 155 * <p>Otherwise the context hierarchy of the supplied WAC will be traversed 156 * to find the top-most WAC (i.e., the root); and the {@link ServletContext} 157 * of the Root WAC will be set as the {@code ServletContext} for the supplied 158 * WAC. 159 * <h4>Root WAC Configuration</h4> 160 * <ul> 161 * <li>The resource base path is retrieved from the supplied 162 * {@code WebMergedContextConfiguration}.</li> 163 * <li>A {@link ResourceLoader} is instantiated for the {@link MockServletContext}: 164 * if the resource base path is prefixed with "{@code classpath:}", a 165 * {@link DefaultResourceLoader} will be used; otherwise, a 166 * {@link FileSystemResourceLoader} will be used.</li> 167 * <li>A {@code MockServletContext} will be created using the resource base 168 * path and resource loader.</li> 169 * <li>The supplied {@link GenericWebApplicationContext} is then stored in 170 * the {@code MockServletContext} under the 171 * {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} key.</li> 172 * <li>Finally, the {@code MockServletContext} is set in the 173 * {@code WebApplicationContext}.</li> 174 * @param context the web application context for which to configure the web resources 175 * @param webMergedConfig the merged context configuration to use to load the web application context 176 */ 177 protected void configureWebResources(GenericWebApplicationContext context, 178 WebMergedContextConfiguration webMergedConfig) { 179 180 ApplicationContext parent = context.getParent(); 181 182 // If the WebApplicationContext has no parent or the parent is not a WebApplicationContext, 183 // set the current context as the root WebApplicationContext: 184 if (parent == null || (!(parent instanceof WebApplicationContext))) { 185 String resourceBasePath = webMergedConfig.getResourceBasePath(); 186 ResourceLoader resourceLoader = (resourceBasePath.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX) ? 187 new DefaultResourceLoader() : new FileSystemResourceLoader()); 188 ServletContext servletContext = new MockServletContext(resourceBasePath, resourceLoader); 189 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context); 190 context.setServletContext(servletContext); 191 } 192 else { 193 ServletContext servletContext = null; 194 // Find the root WebApplicationContext 195 while (parent != null) { 196 if (parent instanceof WebApplicationContext && !(parent.getParent() instanceof WebApplicationContext)) { 197 servletContext = ((WebApplicationContext) parent).getServletContext(); 198 break; 199 } 200 parent = parent.getParent(); 201 } 202 Assert.state(servletContext != null, "Failed to find root WebApplicationContext in the context hierarchy"); 203 context.setServletContext(servletContext); 204 } 205 } 206 207 /** 208 * Customize the internal bean factory of the {@code WebApplicationContext} 209 * created by this context loader. 210 * <p>The default implementation is empty but can be overridden in subclasses 211 * to customize {@code DefaultListableBeanFactory}'s standard settings. 212 * @param beanFactory the bean factory created by this context loader 213 * @param webMergedConfig the merged context configuration to use to load the 214 * web application context 215 * @see #loadContext(MergedContextConfiguration) 216 * @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding 217 * @see DefaultListableBeanFactory#setAllowEagerClassLoading 218 * @see DefaultListableBeanFactory#setAllowCircularReferences 219 * @see DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping 220 */ 221 protected void customizeBeanFactory( 222 DefaultListableBeanFactory beanFactory, WebMergedContextConfiguration webMergedConfig) { 223 } 224 225 /** 226 * Load bean definitions into the supplied {@link GenericWebApplicationContext context} 227 * from the locations or classes in the supplied {@code WebMergedContextConfiguration}. 228 * <p>Concrete subclasses must provide an appropriate implementation. 229 * @param context the context into which the bean definitions should be loaded 230 * @param webMergedConfig the merged context configuration to use to load the 231 * web application context 232 * @see #loadContext(MergedContextConfiguration) 233 */ 234 protected abstract void loadBeanDefinitions( 235 GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig); 236 237 /** 238 * Customize the {@link GenericWebApplicationContext} created by this context 239 * loader <i>after</i> bean definitions have been loaded into the context but 240 * <i>before</i> the context is refreshed. 241 * <p>The default implementation simply delegates to 242 * {@link AbstractContextLoader#customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)}. 243 * @param context the newly created web application context 244 * @param webMergedConfig the merged context configuration to use to load the 245 * web application context 246 * @see #loadContext(MergedContextConfiguration) 247 * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration) 248 */ 249 protected void customizeContext( 250 GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig) { 251 252 super.customizeContext(context, webMergedConfig); 253 } 254 255 256 // ContextLoader 257 258 /** 259 * {@code AbstractGenericWebContextLoader} should be used as a 260 * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}, 261 * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}. 262 * Consequently, this method is not supported. 263 * @see org.springframework.test.context.ContextLoader#loadContext(java.lang.String[]) 264 * @throws UnsupportedOperationException in this implementation 265 */ 266 @Override 267 public final ApplicationContext loadContext(String... locations) throws Exception { 268 throw new UnsupportedOperationException( 269 "AbstractGenericWebContextLoader does not support the loadContext(String... locations) method"); 270 } 271 272}