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.cache.support;
018
019import java.util.Collection;
020import java.util.Collections;
021import java.util.LinkedHashSet;
022import java.util.Set;
023import java.util.concurrent.ConcurrentHashMap;
024import java.util.concurrent.ConcurrentMap;
025
026import org.springframework.beans.factory.InitializingBean;
027import org.springframework.cache.Cache;
028import org.springframework.cache.CacheManager;
029
030/**
031 * Abstract base class implementing the common {@link CacheManager} methods.
032 * Useful for 'static' environments where the backing caches do not change.
033 *
034 * @author Costin Leau
035 * @author Juergen Hoeller
036 * @author Stephane Nicoll
037 * @since 3.1
038 */
039public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
040
041        private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
042
043        private volatile Set<String> cacheNames = Collections.emptySet();
044
045
046        // Early cache initialization on startup
047
048        @Override
049        public void afterPropertiesSet() {
050                initializeCaches();
051        }
052
053        /**
054         * Initialize the static configuration of caches.
055         * <p>Triggered on startup through {@link #afterPropertiesSet()};
056         * can also be called to re-initialize at runtime.
057         * @since 4.2.2
058         * @see #loadCaches()
059         */
060        public void initializeCaches() {
061                Collection<? extends Cache> caches = loadCaches();
062
063                synchronized (this.cacheMap) {
064                        this.cacheNames = Collections.emptySet();
065                        this.cacheMap.clear();
066                        Set<String> cacheNames = new LinkedHashSet<String>(caches.size());
067                        for (Cache cache : caches) {
068                                String name = cache.getName();
069                                this.cacheMap.put(name, decorateCache(cache));
070                                cacheNames.add(name);
071                        }
072                        this.cacheNames = Collections.unmodifiableSet(cacheNames);
073                }
074        }
075
076        /**
077         * Load the initial caches for this cache manager.
078         * <p>Called by {@link #afterPropertiesSet()} on startup.
079         * The returned collection may be empty but must not be {@code null}.
080         */
081        protected abstract Collection<? extends Cache> loadCaches();
082
083
084        // Lazy cache initialization on access
085
086        @Override
087        public Cache getCache(String name) {
088                Cache cache = this.cacheMap.get(name);
089                if (cache != null) {
090                        return cache;
091                }
092                else {
093                        // Fully synchronize now for missing cache creation...
094                        synchronized (this.cacheMap) {
095                                cache = this.cacheMap.get(name);
096                                if (cache == null) {
097                                        cache = getMissingCache(name);
098                                        if (cache != null) {
099                                                cache = decorateCache(cache);
100                                                this.cacheMap.put(name, cache);
101                                                updateCacheNames(name);
102                                        }
103                                }
104                                return cache;
105                        }
106                }
107        }
108
109        @Override
110        public Collection<String> getCacheNames() {
111                return this.cacheNames;
112        }
113
114
115        // Common cache initialization delegates for subclasses
116
117        /**
118         * Check for a registered cache of the given name.
119         * In contrast to {@link #getCache(String)}, this method does not trigger
120         * the lazy creation of missing caches via {@link #getMissingCache(String)}.
121         * @param name the cache identifier (must not be {@code null})
122         * @return the associated Cache instance, or {@code null} if none found
123         * @since 4.1
124         * @see #getCache(String)
125         * @see #getMissingCache(String)
126         */
127        protected final Cache lookupCache(String name) {
128                return this.cacheMap.get(name);
129        }
130
131        /**
132         * Dynamically register an additional Cache with this manager.
133         * @param cache the Cache to register
134         * @deprecated as of Spring 4.3, in favor of {@link #getMissingCache(String)}
135         */
136        @Deprecated
137        protected final void addCache(Cache cache) {
138                String name = cache.getName();
139                synchronized (this.cacheMap) {
140                        if (this.cacheMap.put(name, decorateCache(cache)) == null) {
141                                updateCacheNames(name);
142                        }
143                }
144        }
145
146        /**
147         * Update the exposed {@link #cacheNames} set with the given name.
148         * <p>This will always be called within a full {@link #cacheMap} lock
149         * and effectively behaves like a {@code CopyOnWriteArraySet} with
150         * preserved order but exposed as an unmodifiable reference.
151         * @param name the name of the cache to be added
152         */
153        private void updateCacheNames(String name) {
154                Set<String> cacheNames = new LinkedHashSet<String>(this.cacheNames.size() + 1);
155                cacheNames.addAll(this.cacheNames);
156                cacheNames.add(name);
157                this.cacheNames = Collections.unmodifiableSet(cacheNames);
158        }
159
160
161        // Overridable template methods for cache initialization
162
163        /**
164         * Decorate the given Cache object if necessary.
165         * @param cache the Cache object to be added to this CacheManager
166         * @return the decorated Cache object to be used instead,
167         * or simply the passed-in Cache object by default
168         */
169        protected Cache decorateCache(Cache cache) {
170                return cache;
171        }
172
173        /**
174         * Return a missing cache with the specified {@code name}, or {@code null} if
175         * such a cache does not exist or could not be created on demand.
176         * <p>Caches may be lazily created at runtime if the native provider supports it.
177         * If a lookup by name does not yield any result, an {@code AbstractCacheManager}
178         * subclass gets a chance to register such a cache at runtime. The returned cache
179         * will be automatically added to this cache manager.
180         * @param name the name of the cache to retrieve
181         * @return the missing cache, or {@code null} if no such cache exists or could be
182         * created on demand
183         * @since 4.1
184         * @see #getCache(String)
185         */
186        protected Cache getMissingCache(String name) {
187                return null;
188        }
189
190}