001/*
002 * Copyright 2002-2016 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.concurrent;
018
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Map;
023import java.util.concurrent.ConcurrentHashMap;
024import java.util.concurrent.ConcurrentMap;
025
026import org.springframework.beans.factory.BeanClassLoaderAware;
027import org.springframework.cache.Cache;
028import org.springframework.cache.CacheManager;
029import org.springframework.core.serializer.support.SerializationDelegate;
030
031/**
032 * {@link CacheManager} implementation that lazily builds {@link ConcurrentMapCache}
033 * instances for each {@link #getCache} request. Also supports a 'static' mode where
034 * the set of cache names is pre-defined through {@link #setCacheNames}, with no
035 * dynamic creation of further cache regions at runtime.
036 *
037 * <p>Note: This is by no means a sophisticated CacheManager; it comes with no
038 * cache configuration options. However, it may be useful for testing or simple
039 * caching scenarios. For advanced local caching needs, consider
040 * {@link org.springframework.cache.jcache.JCacheCacheManager},
041 * {@link org.springframework.cache.ehcache.EhCacheCacheManager},
042 * {@link org.springframework.cache.caffeine.CaffeineCacheManager} or
043 * {@link org.springframework.cache.guava.GuavaCacheManager}.
044 *
045 * @author Juergen Hoeller
046 * @since 3.1
047 * @see ConcurrentMapCache
048 */
049public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
050
051        private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
052
053        private boolean dynamic = true;
054
055        private boolean allowNullValues = true;
056
057        private boolean storeByValue = false;
058
059        private SerializationDelegate serialization;
060
061
062        /**
063         * Construct a dynamic ConcurrentMapCacheManager,
064         * lazily creating cache instances as they are being requested.
065         */
066        public ConcurrentMapCacheManager() {
067        }
068
069        /**
070         * Construct a static ConcurrentMapCacheManager,
071         * managing caches for the specified cache names only.
072         */
073        public ConcurrentMapCacheManager(String... cacheNames) {
074                setCacheNames(Arrays.asList(cacheNames));
075        }
076
077
078        /**
079         * Specify the set of cache names for this CacheManager's 'static' mode.
080         * <p>The number of caches and their names will be fixed after a call to this method,
081         * with no creation of further cache regions at runtime.
082         * <p>Calling this with a {@code null} collection argument resets the
083         * mode to 'dynamic', allowing for further creation of caches again.
084         */
085        public void setCacheNames(Collection<String> cacheNames) {
086                if (cacheNames != null) {
087                        for (String name : cacheNames) {
088                                this.cacheMap.put(name, createConcurrentMapCache(name));
089                        }
090                        this.dynamic = false;
091                }
092                else {
093                        this.dynamic = true;
094                }
095        }
096
097        /**
098         * Specify whether to accept and convert {@code null} values for all caches
099         * in this cache manager.
100         * <p>Default is "true", despite ConcurrentHashMap itself not supporting {@code null}
101         * values. An internal holder object will be used to store user-level {@code null}s.
102         * <p>Note: A change of the null-value setting will reset all existing caches,
103         * if any, to reconfigure them with the new null-value requirement.
104         */
105        public void setAllowNullValues(boolean allowNullValues) {
106                if (allowNullValues != this.allowNullValues) {
107                        this.allowNullValues = allowNullValues;
108                        // Need to recreate all Cache instances with the new null-value configuration...
109                        recreateCaches();
110                }
111        }
112
113        /**
114         * Return whether this cache manager accepts and converts {@code null} values
115         * for all of its caches.
116         */
117        public boolean isAllowNullValues() {
118                return this.allowNullValues;
119        }
120
121        /**
122         * Specify whether this cache manager stores a copy of each entry ({@code true}
123         * or the reference ({@code false} for all of its caches.
124         * <p>Default is "false" so that the value itself is stored and no serializable
125         * contract is required on cached values.
126         * <p>Note: A change of the store-by-value setting will reset all existing caches,
127         * if any, to reconfigure them with the new store-by-value requirement.
128         * @since 4.3
129         */
130        public void setStoreByValue(boolean storeByValue) {
131                if (storeByValue != this.storeByValue) {
132                        this.storeByValue = storeByValue;
133                        // Need to recreate all Cache instances with the new store-by-value configuration...
134                        recreateCaches();
135                }
136        }
137
138        /**
139         * Return whether this cache manager stores a copy of each entry or
140         * a reference for all its caches. If store by value is enabled, any
141         * cache entry must be serializable.
142         * @since 4.3
143         */
144        public boolean isStoreByValue() {
145                return this.storeByValue;
146        }
147
148        @Override
149        public void setBeanClassLoader(ClassLoader classLoader) {
150                this.serialization = new SerializationDelegate(classLoader);
151                // Need to recreate all Cache instances with new ClassLoader in store-by-value mode...
152                if (isStoreByValue()) {
153                        recreateCaches();
154                }
155        }
156
157
158        @Override
159        public Collection<String> getCacheNames() {
160                return Collections.unmodifiableSet(this.cacheMap.keySet());
161        }
162
163        @Override
164        public Cache getCache(String name) {
165                Cache cache = this.cacheMap.get(name);
166                if (cache == null && this.dynamic) {
167                        synchronized (this.cacheMap) {
168                                cache = this.cacheMap.get(name);
169                                if (cache == null) {
170                                        cache = createConcurrentMapCache(name);
171                                        this.cacheMap.put(name, cache);
172                                }
173                        }
174                }
175                return cache;
176        }
177
178        private void recreateCaches() {
179                for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
180                        entry.setValue(createConcurrentMapCache(entry.getKey()));
181                }
182        }
183
184        /**
185         * Create a new ConcurrentMapCache instance for the specified cache name.
186         * @param name the name of the cache
187         * @return the ConcurrentMapCache (or a decorator thereof)
188         */
189        protected Cache createConcurrentMapCache(String name) {
190                SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
191                return new ConcurrentMapCache(name, new ConcurrentHashMap<Object, Object>(256),
192                                isAllowNullValues(), actualSerialization);
193
194        }
195
196}