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