001/* 002 * Copyright 2002-2019 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.cache; 018 019import org.apache.commons.logging.Log; 020import org.apache.commons.logging.LogFactory; 021 022import org.springframework.context.ApplicationContext; 023import org.springframework.lang.Nullable; 024import org.springframework.test.annotation.DirtiesContext.HierarchyMode; 025import org.springframework.test.context.CacheAwareContextLoaderDelegate; 026import org.springframework.test.context.ContextLoader; 027import org.springframework.test.context.MergedContextConfiguration; 028import org.springframework.test.context.SmartContextLoader; 029import org.springframework.util.Assert; 030 031/** 032 * Default implementation of the {@link CacheAwareContextLoaderDelegate} interface. 033 * 034 * <p>To use a static {@link DefaultContextCache}, invoke the 035 * {@link #DefaultCacheAwareContextLoaderDelegate()} constructor; otherwise, 036 * invoke the {@link #DefaultCacheAwareContextLoaderDelegate(ContextCache)} 037 * and provide a custom {@link ContextCache} implementation. 038 * 039 * @author Sam Brannen 040 * @since 4.1 041 */ 042public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderDelegate { 043 044 private static final Log logger = LogFactory.getLog(DefaultCacheAwareContextLoaderDelegate.class); 045 046 /** 047 * Default static cache of Spring application contexts. 048 */ 049 static final ContextCache defaultContextCache = new DefaultContextCache(); 050 051 private final ContextCache contextCache; 052 053 054 /** 055 * Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using 056 * a static {@link DefaultContextCache}. 057 * <p>This default cache is static so that each context can be cached 058 * and reused for all subsequent tests that declare the same unique 059 * context configuration within the same JVM process. 060 * @see #DefaultCacheAwareContextLoaderDelegate(ContextCache) 061 */ 062 public DefaultCacheAwareContextLoaderDelegate() { 063 this(defaultContextCache); 064 } 065 066 /** 067 * Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using 068 * the supplied {@link ContextCache}. 069 * @see #DefaultCacheAwareContextLoaderDelegate() 070 */ 071 public DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) { 072 Assert.notNull(contextCache, "ContextCache must not be null"); 073 this.contextCache = contextCache; 074 } 075 076 /** 077 * Get the {@link ContextCache} used by this context loader delegate. 078 */ 079 protected ContextCache getContextCache() { 080 return this.contextCache; 081 } 082 083 /** 084 * Load the {@code ApplicationContext} for the supplied merged context configuration. 085 * <p>Supports both the {@link SmartContextLoader} and {@link ContextLoader} SPIs. 086 * @throws Exception if an error occurs while loading the application context 087 */ 088 protected ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration) 089 throws Exception { 090 091 ContextLoader contextLoader = mergedContextConfiguration.getContextLoader(); 092 Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. " + 093 "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy."); 094 095 ApplicationContext applicationContext; 096 097 if (contextLoader instanceof SmartContextLoader) { 098 SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; 099 applicationContext = smartContextLoader.loadContext(mergedContextConfiguration); 100 } 101 else { 102 String[] locations = mergedContextConfiguration.getLocations(); 103 Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. " + 104 "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy."); 105 applicationContext = contextLoader.loadContext(locations); 106 } 107 108 return applicationContext; 109 } 110 111 @Override 112 public boolean isContextLoaded(MergedContextConfiguration mergedContextConfiguration) { 113 synchronized (this.contextCache) { 114 return this.contextCache.contains(mergedContextConfiguration); 115 } 116 } 117 118 @Override 119 public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) { 120 synchronized (this.contextCache) { 121 ApplicationContext context = this.contextCache.get(mergedContextConfiguration); 122 if (context == null) { 123 try { 124 context = loadContextInternal(mergedContextConfiguration); 125 if (logger.isDebugEnabled()) { 126 logger.debug(String.format("Storing ApplicationContext [%s] in cache under key [%s]", 127 System.identityHashCode(context), mergedContextConfiguration)); 128 } 129 this.contextCache.put(mergedContextConfiguration, context); 130 } 131 catch (Exception ex) { 132 throw new IllegalStateException("Failed to load ApplicationContext", ex); 133 } 134 } 135 else { 136 if (logger.isDebugEnabled()) { 137 logger.debug(String.format("Retrieved ApplicationContext [%s] from cache with key [%s]", 138 System.identityHashCode(context), mergedContextConfiguration)); 139 } 140 } 141 142 this.contextCache.logStatistics(); 143 144 return context; 145 } 146 } 147 148 @Override 149 public void closeContext(MergedContextConfiguration mergedContextConfiguration, @Nullable HierarchyMode hierarchyMode) { 150 synchronized (this.contextCache) { 151 this.contextCache.remove(mergedContextConfiguration, hierarchyMode); 152 } 153 } 154 155}