/* * Copyright 2002-2008 the original author or authors. * * 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; import java.io.Serializable; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; /** * Cache for Spring {@link ApplicationContext ApplicationContexts} * in a test environment. * * <p>Maintains a cache of {@link ApplicationContext contexts} by * {@link Serializable serializable} key. 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 * {@link org.springframework.orm.hibernate3.LocalSessionFactoryBean LocalSessionFactoryBean} * for working with Hibernate, may take some time to initialize. Hence it often * makes sense to perform that initialization once. * * @author Sam Brannen * @author Juergen Hoeller * @since 2.5 */ class ContextCache { /** * Map of context keys to Spring ApplicationContext instances. */ private final Map<String, ApplicationContext> contextKeyToContextMap = new ConcurrentHashMap<String, ApplicationContext>(); private int hitCount; private int missCount; /** * Clears all contexts from the cache. */ void clear() { this.contextKeyToContextMap.clear(); } /** * Clears hit and miss count statistics for the cache (i.e., resets counters * to zero). */ void clearStatistics() { this.hitCount = 0; this.missCount = 0; } /** * Return whether there is a cached context for the given key. * @param key the context key (never <code>null</code>) */ boolean contains(String key) { Assert.notNull(key, "Key must not be null"); return this.contextKeyToContextMap.containsKey(key); } /** * Obtain a cached ApplicationContext for the given key. * <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} * counts will be updated accordingly. * @param key the context key (never <code>null</code>) * @return the corresponding ApplicationContext instance, * or <code>null</code> if not found in the cache. * @see #remove */ ApplicationContext get(String key) { Assert.notNull(key, "Key must not be null"); ApplicationContext context = this.contextKeyToContextMap.get(key); if (context == null) { incrementMissCount(); } else { incrementHitCount(); } return context; } /** * Increment the hit count by one. A <em>hit</em> is an access to the * cache, which returned a non-null context for a queried key. */ private void incrementHitCount() { this.hitCount++; } /** * Increment the miss count by one. A <em>miss</em> is an access to the * cache, which returned a <code>null</code> context for a queried key. */ private void incrementMissCount() { this.missCount++; } /** * Get the overall hit count for this cache. A <em>hit</em> is an access * to the cache, which returned a non-null context for a queried key. */ int getHitCount() { return this.hitCount; } /** * Get the overall miss count for this cache. A <em>miss</em> is an * access to the cache, which returned a <code>null</code> context for a * queried key. */ int getMissCount() { return this.missCount; } /** * Explicitly add a ApplicationContext instance to the cache under the given key. * @param key the context key (never <code>null</code>) * @param context the ApplicationContext instance (never <code>null</code>) */ void put(String key, ApplicationContext context) { Assert.notNull(key, "Key must not be null"); Assert.notNull(context, "ApplicationContext must not be null"); this.contextKeyToContextMap.put(key, context); } /** * Remove the context with the given key. * @param key the context key (never <code>null</code>) * @return the corresponding ApplicationContext instance, * or <code>null</code> if not found in the cache. * @see #setDirty */ ApplicationContext remove(String key) { return this.contextKeyToContextMap.remove(key); } /** * Mark the context with the given key as dirty, effectively * {@link #remove removing} the context from the cache and explicitly * {@link ConfigurableApplicationContext#close() closing} it if * it is an instance of {@link ConfigurableApplicationContext}. * <p>Generally speaking, you would only call this method only if you change * the state of a singleton bean, potentially affecting future interaction * with the context. * @param key the context key (never <code>null</code>) * @see #remove */ void setDirty(String key) { Assert.notNull(key, "Key must not be null"); ApplicationContext context = remove(key); if (context instanceof ConfigurableApplicationContext) { ((ConfigurableApplicationContext) context).close(); } } /** * Determine the number of contexts currently stored in the cache. If the * cache contains more than <tt>Integer.MAX_VALUE</tt> elements, returns * <tt>Integer.MAX_VALUE</tt>. */ int size() { return this.contextKeyToContextMap.size(); } /** * Generates a text string, which contains the {@link #size() size} as well * as the {@link #hitCount hit} and {@link #missCount miss} counts. */ public String toString() { return new ToStringCreator(this) .append("size", size()) .append("hitCount", getHitCount()) .append("missCount",getMissCount()) .toString(); } }