ContextCache.java 8.8 KB
Newer Older
A
Arjen Poutsma 已提交
1
/*
2
 * Copyright 2002-2014 the original author or authors.
A
Arjen Poutsma 已提交
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.test.context;

19 20 21
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
A
Arjen Poutsma 已提交
22
import java.util.Map;
23
import java.util.Set;
A
Arjen Poutsma 已提交
24
import java.util.concurrent.ConcurrentHashMap;
25
import java.util.concurrent.atomic.AtomicInteger;
A
Arjen Poutsma 已提交
26 27 28 29

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
30
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
A
Arjen Poutsma 已提交
31 32 33
import org.springframework.util.Assert;

/**
34
 * Cache for Spring {@link ApplicationContext ApplicationContexts} in a test environment.
A
Arjen Poutsma 已提交
35
 *
36 37 38 39 40 41 42 43
 * <p>Maintains a cache of {@code ApplicationContexts} keyed by
 * {@link MergedContextConfiguration} instances.
 *
 * <p>This has significant performance benefits if initializing the context would take time.
 * While initializing a Spring context itself is very quick, some beans in a context, such
 * as a {@code LocalSessionFactoryBean} for working with Hibernate, may take some time to
 * initialize. Hence it often makes sense to perform that initialization only once per
 * test suite.
A
Arjen Poutsma 已提交
44 45 46 47 48 49 50
 *
 * @author Sam Brannen
 * @author Juergen Hoeller
 * @since 2.5
 */
class ContextCache {

51 52 53
	/**
	 * Map of context keys to Spring {@code ApplicationContext} instances.
	 */
54 55
	private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
			new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>(64);
56

A
Arjen Poutsma 已提交
57
	/**
58 59 60 61
	 * Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
	 * of context hierarchies. This information is used for determining which subtrees
	 * need to be recursively removed and closed when removing a context that is a parent
	 * of other contexts.
A
Arjen Poutsma 已提交
62
	 */
63 64
	private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
			new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
A
Arjen Poutsma 已提交
65

66
	private final AtomicInteger hitCount = new AtomicInteger();
A
Arjen Poutsma 已提交
67

68
	private final AtomicInteger missCount = new AtomicInteger();
A
Arjen Poutsma 已提交
69 70 71


	/**
S
Sam Brannen 已提交
72
	 * Clear all contexts from the cache and clear context hierarchy information as well.
A
Arjen Poutsma 已提交
73
	 */
74 75 76
	public void clear() {
		this.contextMap.clear();
		this.hierarchyMap.clear();
A
Arjen Poutsma 已提交
77 78 79
	}

	/**
S
Sam Brannen 已提交
80
	 * Clear hit and miss count statistics for the cache (i.e., reset counters to zero).
A
Arjen Poutsma 已提交
81
	 */
82
	public void clearStatistics() {
83 84
		this.hitCount.set(0);
		this.missCount.set(0);
A
Arjen Poutsma 已提交
85 86 87
	}

	/**
S
Sam Brannen 已提交
88
	 * Determine whether there is a cached context for the given key.
89
	 * @param key the context key (never {@code null})
S
Sam Brannen 已提交
90
	 * @return {@code true} if the cache contains a context with the given key
A
Arjen Poutsma 已提交
91
	 */
92
	public boolean contains(MergedContextConfiguration key) {
A
Arjen Poutsma 已提交
93
		Assert.notNull(key, "Key must not be null");
94
		return this.contextMap.containsKey(key);
A
Arjen Poutsma 已提交
95 96 97
	}

	/**
98
	 * Obtain a cached {@code ApplicationContext} for the given key.
99 100
	 * <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will
	 * be updated accordingly.
101
	 * @param key the context key (never {@code null})
102 103
	 * @return the corresponding {@code ApplicationContext} instance, or {@code null}
	 * if not found in the cache
A
Arjen Poutsma 已提交
104 105
	 * @see #remove
	 */
106
	public ApplicationContext get(MergedContextConfiguration key) {
A
Arjen Poutsma 已提交
107
		Assert.notNull(key, "Key must not be null");
108 109 110 111 112 113
		ApplicationContext context = this.contextMap.get(key);
		if (context == null) {
			this.missCount.incrementAndGet();
		}
		else {
			this.hitCount.incrementAndGet();
A
Arjen Poutsma 已提交
114
		}
115
		return context;
A
Arjen Poutsma 已提交
116 117 118
	}

	/**
119
	 * Get the overall hit count for this cache.
120 121
	 * <p>A <em>hit</em> is an access to the cache, which returned a non-null context
	 * for a queried key.
A
Arjen Poutsma 已提交
122
	 */
123
	public int getHitCount() {
124
		return this.hitCount.get();
A
Arjen Poutsma 已提交
125 126 127
	}

	/**
128 129 130
	 * Get the overall miss count for this cache.
	 * <p>A <em>miss</em> is an access to the cache, which returned a {@code null} context
	 * for a queried key.
A
Arjen Poutsma 已提交
131
	 */
132
	public int getMissCount() {
133
		return this.missCount.get();
A
Arjen Poutsma 已提交
134 135 136
	}

	/**
137
	 * Explicitly add an {@code ApplicationContext} instance to the cache under the given key.
138
	 * @param key the context key (never {@code null})
139
	 * @param context the {@code ApplicationContext} instance (never {@code null})
A
Arjen Poutsma 已提交
140
	 */
141
	public void put(MergedContextConfiguration key, ApplicationContext context) {
A
Arjen Poutsma 已提交
142 143 144
		Assert.notNull(key, "Key must not be null");
		Assert.notNull(context, "ApplicationContext must not be null");

145 146 147 148 149 150 151 152
		this.contextMap.put(key, context);
		MergedContextConfiguration child = key;
		MergedContextConfiguration parent = child.getParent();
		while (parent != null) {
			Set<MergedContextConfiguration> list = this.hierarchyMap.get(parent);
			if (list == null) {
				list = new HashSet<MergedContextConfiguration>();
				this.hierarchyMap.put(parent, list);
153
			}
154 155 156
			list.add(child);
			child = parent;
			parent = child.getParent();
157
		}
A
Arjen Poutsma 已提交
158 159 160
	}

	/**
161 162
	 * Remove the context with the given key from the cache and explicitly
	 * {@linkplain ConfigurableApplicationContext#close() close} it if it is an
163
	 * instance of {@link ConfigurableApplicationContext}.
164 165 166
	 * <p>Generally speaking, you would only call this method if you change the
	 * state of a singleton bean, potentially affecting future interaction with
	 * the context.
167 168 169 170 171
	 * <p>In addition, the semantics of the supplied {@code HierarchyMode} will
	 * be honored. See the Javadoc for {@link HierarchyMode} for details.
	 * @param key the context key; never {@code null}
	 * @param hierarchyMode the hierarchy mode; may be {@code null} if the context
	 * is not part of a hierarchy
A
Arjen Poutsma 已提交
172
	 */
173
	public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
A
Arjen Poutsma 已提交
174
		Assert.notNull(key, "Key must not be null");
175 176 177 178 179 180 181 182 183 184

		// startKey is the level at which to begin clearing the cache, depending
		// on the configured hierarchy mode.
		MergedContextConfiguration startKey = key;
		if (hierarchyMode == HierarchyMode.EXHAUSTIVE) {
			while (startKey.getParent() != null) {
				startKey = startKey.getParent();
			}
		}

185 186
		List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
		remove(removedContexts, startKey);
187

188 189 190 191 192
		// Remove all remaining references to any removed contexts from the
		// hierarchy map.
		for (MergedContextConfiguration currentKey : removedContexts) {
			for (Set<MergedContextConfiguration> children : this.hierarchyMap.values()) {
				children.remove(currentKey);
193
			}
194
		}
195

196 197 198 199
		// Remove empty entries from the hierarchy map.
		for (MergedContextConfiguration currentKey : this.hierarchyMap.keySet()) {
			if (this.hierarchyMap.get(currentKey).isEmpty()) {
				this.hierarchyMap.remove(currentKey);
200 201 202 203 204 205 206
			}
		}
	}

	private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
		Assert.notNull(key, "Key must not be null");

207 208 209 210 211
		Set<MergedContextConfiguration> children = this.hierarchyMap.get(key);
		if (children != null) {
			for (MergedContextConfiguration child : children) {
				// Recurse through lower levels
				remove(removedContexts, child);
212
			}
213 214 215
			// Remove the set of children for the current context from the hierarchy map.
			this.hierarchyMap.remove(key);
		}
216

217 218 219 220 221
		// Physically remove and close leaf nodes first (i.e., on the way back up the
		// stack as opposed to prior to the recursive call).
		ApplicationContext context = this.contextMap.remove(key);
		if (context instanceof ConfigurableApplicationContext) {
			((ConfigurableApplicationContext) context).close();
A
Arjen Poutsma 已提交
222
		}
223
		removedContexts.add(key);
A
Arjen Poutsma 已提交
224 225 226
	}

	/**
S
Sam Brannen 已提交
227 228 229
	 * Determine the number of contexts currently stored in the cache.
	 * <p>If the cache contains more than {@code Integer.MAX_VALUE} elements,
	 * this method returns {@code Integer.MAX_VALUE}.
A
Arjen Poutsma 已提交
230
	 */
231 232
	public int size() {
		return this.contextMap.size();
233 234 235 236 237
	}

	/**
	 * Determine the number of parent contexts currently tracked within the cache.
	 */
238 239
	public int getParentContextCount() {
		return this.hierarchyMap.size();
A
Arjen Poutsma 已提交
240 241 242
	}

	/**
S
Sam Brannen 已提交
243
	 * Generate a text string, which contains the {@linkplain #size} as well
244 245
	 * as the {@linkplain #getHitCount() hit}, {@linkplain #getMissCount() miss},
	 * and {@linkplain #getParentContextCount() parent context} counts.
A
Arjen Poutsma 已提交
246
	 */
247
	@Override
A
Arjen Poutsma 已提交
248
	public String toString() {
249 250 251 252 253 254
		return new ToStringCreator(this)
				.append("size", size())
				.append("hitCount", getHitCount())
				.append("missCount", getMissCount())
				.append("parentContextCount", getParentContextCount())
				.toString();
A
Arjen Poutsma 已提交
255 256 257
	}

}