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