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}