001/*
002 * Copyright 2002-2014 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.guava;
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 com.google.common.cache.CacheBuilder;
027import com.google.common.cache.CacheBuilderSpec;
028import com.google.common.cache.CacheLoader;
029
030import org.springframework.cache.Cache;
031import org.springframework.cache.CacheManager;
032import org.springframework.util.Assert;
033import org.springframework.util.ObjectUtils;
034
035/**
036 * {@link CacheManager} implementation that lazily builds {@link GuavaCache}
037 * instances for each {@link #getCache} request. Also supports a 'static' mode
038 * where the set of cache names is pre-defined through {@link #setCacheNames},
039 * with no dynamic creation of further cache regions at runtime.
040 *
041 * <p>The configuration of the underlying cache can be fine-tuned through a
042 * Guava {@link CacheBuilder} or {@link CacheBuilderSpec}, passed into this
043 * CacheManager through {@link #setCacheBuilder}/{@link #setCacheBuilderSpec}.
044 * A {@link CacheBuilderSpec}-compliant expression value can also be applied
045 * via the {@link #setCacheSpecification "cacheSpecification"} bean property.
046 *
047 * <p>Requires Google Guava 12.0 or higher.
048 *
049 * @author Juergen Hoeller
050 * @author Stephane Nicoll
051 * @since 4.0
052 * @see GuavaCache
053 */
054public class GuavaCacheManager implements CacheManager {
055
056        private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
057
058        private boolean dynamic = true;
059
060        private CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder();
061
062        private CacheLoader<Object, Object> cacheLoader;
063
064        private boolean allowNullValues = true;
065
066
067        /**
068         * Construct a dynamic GuavaCacheManager,
069         * lazily creating cache instances as they are being requested.
070         */
071        public GuavaCacheManager() {
072        }
073
074        /**
075         * Construct a static GuavaCacheManager,
076         * managing caches for the specified cache names only.
077         */
078        public GuavaCacheManager(String... cacheNames) {
079                setCacheNames(Arrays.asList(cacheNames));
080        }
081
082
083        /**
084         * Specify the set of cache names for this CacheManager's 'static' mode.
085         * <p>The number of caches and their names will be fixed after a call to this method,
086         * with no creation of further cache regions at runtime.
087         * <p>Calling this with a {@code null} collection argument resets the
088         * mode to 'dynamic', allowing for further creation of caches again.
089         */
090        public void setCacheNames(Collection<String> cacheNames) {
091                if (cacheNames != null) {
092                        for (String name : cacheNames) {
093                                this.cacheMap.put(name, createGuavaCache(name));
094                        }
095                        this.dynamic = false;
096                }
097                else {
098                        this.dynamic = true;
099                }
100        }
101
102        /**
103         * Set the Guava CacheBuilder to use for building each individual
104         * {@link GuavaCache} instance.
105         * @see #createNativeGuavaCache
106         * @see com.google.common.cache.CacheBuilder#build()
107         */
108        public void setCacheBuilder(CacheBuilder<Object, Object> cacheBuilder) {
109                Assert.notNull(cacheBuilder, "CacheBuilder must not be null");
110                doSetCacheBuilder(cacheBuilder);
111        }
112
113        /**
114         * Set the Guava CacheBuilderSpec to use for building each individual
115         * {@link GuavaCache} instance.
116         * @see #createNativeGuavaCache
117         * @see com.google.common.cache.CacheBuilder#from(CacheBuilderSpec)
118         */
119        public void setCacheBuilderSpec(CacheBuilderSpec cacheBuilderSpec) {
120                doSetCacheBuilder(CacheBuilder.from(cacheBuilderSpec));
121        }
122
123        /**
124         * Set the Guava cache specification String to use for building each
125         * individual {@link GuavaCache} instance. The given value needs to
126         * comply with Guava's {@link CacheBuilderSpec} (see its javadoc).
127         * @see #createNativeGuavaCache
128         * @see com.google.common.cache.CacheBuilder#from(String)
129         */
130        public void setCacheSpecification(String cacheSpecification) {
131                doSetCacheBuilder(CacheBuilder.from(cacheSpecification));
132        }
133
134        /**
135         * Set the Guava CacheLoader to use for building each individual
136         * {@link GuavaCache} instance, turning it into a LoadingCache.
137         * @see #createNativeGuavaCache
138         * @see com.google.common.cache.CacheBuilder#build(CacheLoader)
139         * @see com.google.common.cache.LoadingCache
140         */
141        public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
142                if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) {
143                        this.cacheLoader = cacheLoader;
144                        refreshKnownCaches();
145                }
146        }
147
148        /**
149         * Specify whether to accept and convert {@code null} values for all caches
150         * in this cache manager.
151         * <p>Default is "true", despite Guava itself not supporting {@code null} values.
152         * An internal holder object will be used to store user-level {@code null}s.
153         */
154        public void setAllowNullValues(boolean allowNullValues) {
155                if (this.allowNullValues != allowNullValues) {
156                        this.allowNullValues = allowNullValues;
157                        refreshKnownCaches();
158                }
159        }
160
161        /**
162         * Return whether this cache manager accepts and converts {@code null} values
163         * for all of its caches.
164         */
165        public boolean isAllowNullValues() {
166                return this.allowNullValues;
167        }
168
169
170        @Override
171        public Collection<String> getCacheNames() {
172                return Collections.unmodifiableSet(this.cacheMap.keySet());
173        }
174
175        @Override
176        public Cache getCache(String name) {
177                Cache cache = this.cacheMap.get(name);
178                if (cache == null && this.dynamic) {
179                        synchronized (this.cacheMap) {
180                                cache = this.cacheMap.get(name);
181                                if (cache == null) {
182                                        cache = createGuavaCache(name);
183                                        this.cacheMap.put(name, cache);
184                                }
185                        }
186                }
187                return cache;
188        }
189
190        /**
191         * Create a new GuavaCache instance for the specified cache name.
192         * @param name the name of the cache
193         * @return the Spring GuavaCache adapter (or a decorator thereof)
194         */
195        protected Cache createGuavaCache(String name) {
196                return new GuavaCache(name, createNativeGuavaCache(name), isAllowNullValues());
197        }
198
199        /**
200         * Create a native Guava Cache instance for the specified cache name.
201         * @param name the name of the cache
202         * @return the native Guava Cache instance
203         */
204        protected com.google.common.cache.Cache<Object, Object> createNativeGuavaCache(String name) {
205                if (this.cacheLoader != null) {
206                        return this.cacheBuilder.build(this.cacheLoader);
207                }
208                else {
209                        return this.cacheBuilder.build();
210                }
211        }
212
213        private void doSetCacheBuilder(CacheBuilder<Object, Object> cacheBuilder) {
214                if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) {
215                        this.cacheBuilder = cacheBuilder;
216                        refreshKnownCaches();
217                }
218        }
219
220        /**
221         * Create the known caches again with the current state of this manager.
222         */
223        private void refreshKnownCaches() {
224                for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
225                        entry.setValue(createGuavaCache(entry.getKey()));
226                }
227        }
228
229}