001/*
002 * Copyright 2002-2020 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.concurrent.Callable;
020import java.util.concurrent.ConcurrentHashMap;
021import java.util.concurrent.ConcurrentMap;
022
023import org.springframework.cache.support.AbstractValueAdaptingCache;
024import org.springframework.core.serializer.support.SerializationDelegate;
025import org.springframework.lang.Nullable;
026import org.springframework.util.Assert;
027
028/**
029 * Simple {@link org.springframework.cache.Cache} implementation based on the
030 * core JDK {@code java.util.concurrent} package.
031 *
032 * <p>Useful for testing or simple caching scenarios, typically in combination
033 * with {@link org.springframework.cache.support.SimpleCacheManager} or
034 * dynamically through {@link ConcurrentMapCacheManager}.
035 *
036 * <p><b>Note:</b> As {@link ConcurrentHashMap} (the default implementation used)
037 * does not allow for {@code null} values to be stored, this class will replace
038 * them with a predefined internal object. This behavior can be changed through the
039 * {@link #ConcurrentMapCache(String, ConcurrentMap, boolean)} constructor.
040 *
041 * @author Costin Leau
042 * @author Juergen Hoeller
043 * @author Stephane Nicoll
044 * @since 3.1
045 * @see ConcurrentMapCacheManager
046 */
047public class ConcurrentMapCache extends AbstractValueAdaptingCache {
048
049        private final String name;
050
051        private final ConcurrentMap<Object, Object> store;
052
053        @Nullable
054        private final SerializationDelegate serialization;
055
056
057        /**
058         * Create a new ConcurrentMapCache with the specified name.
059         * @param name the name of the cache
060         */
061        public ConcurrentMapCache(String name) {
062                this(name, new ConcurrentHashMap<>(256), true);
063        }
064
065        /**
066         * Create a new ConcurrentMapCache with the specified name.
067         * @param name the name of the cache
068         * @param allowNullValues whether to accept and convert {@code null}
069         * values for this cache
070         */
071        public ConcurrentMapCache(String name, boolean allowNullValues) {
072                this(name, new ConcurrentHashMap<>(256), allowNullValues);
073        }
074
075        /**
076         * Create a new ConcurrentMapCache with the specified name and the
077         * given internal {@link ConcurrentMap} to use.
078         * @param name the name of the cache
079         * @param store the ConcurrentMap to use as an internal store
080         * @param allowNullValues whether to allow {@code null} values
081         * (adapting them to an internal null holder value)
082         */
083        public ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues) {
084                this(name, store, allowNullValues, null);
085        }
086
087        /**
088         * Create a new ConcurrentMapCache with the specified name and the
089         * given internal {@link ConcurrentMap} to use. If the
090         * {@link SerializationDelegate} is specified,
091         * {@link #isStoreByValue() store-by-value} is enabled
092         * @param name the name of the cache
093         * @param store the ConcurrentMap to use as an internal store
094         * @param allowNullValues whether to allow {@code null} values
095         * (adapting them to an internal null holder value)
096         * @param serialization the {@link SerializationDelegate} to use
097         * to serialize cache entry or {@code null} to store the reference
098         * @since 4.3
099         */
100        protected ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store,
101                        boolean allowNullValues, @Nullable SerializationDelegate serialization) {
102
103                super(allowNullValues);
104                Assert.notNull(name, "Name must not be null");
105                Assert.notNull(store, "Store must not be null");
106                this.name = name;
107                this.store = store;
108                this.serialization = serialization;
109        }
110
111
112        /**
113         * Return whether this cache stores a copy of each entry ({@code true}) or
114         * a reference ({@code false}, default). If store by value is enabled, each
115         * entry in the cache must be serializable.
116         * @since 4.3
117         */
118        public final boolean isStoreByValue() {
119                return (this.serialization != null);
120        }
121
122        @Override
123        public final String getName() {
124                return this.name;
125        }
126
127        @Override
128        public final ConcurrentMap<Object, Object> getNativeCache() {
129                return this.store;
130        }
131
132        @Override
133        @Nullable
134        protected Object lookup(Object key) {
135                return this.store.get(key);
136        }
137
138        @SuppressWarnings("unchecked")
139        @Override
140        @Nullable
141        public <T> T get(Object key, Callable<T> valueLoader) {
142                return (T) fromStoreValue(this.store.computeIfAbsent(key, k -> {
143                        try {
144                                return toStoreValue(valueLoader.call());
145                        }
146                        catch (Throwable ex) {
147                                throw new ValueRetrievalException(key, valueLoader, ex);
148                        }
149                }));
150        }
151
152        @Override
153        public void put(Object key, @Nullable Object value) {
154                this.store.put(key, toStoreValue(value));
155        }
156
157        @Override
158        @Nullable
159        public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
160                Object existing = this.store.putIfAbsent(key, toStoreValue(value));
161                return toValueWrapper(existing);
162        }
163
164        @Override
165        public void evict(Object key) {
166                this.store.remove(key);
167        }
168
169        @Override
170        public boolean evictIfPresent(Object key) {
171                return (this.store.remove(key) != null);
172        }
173
174        @Override
175        public void clear() {
176                this.store.clear();
177        }
178
179        @Override
180        public boolean invalidate() {
181                boolean notEmpty = !this.store.isEmpty();
182                this.store.clear();
183                return notEmpty;
184        }
185
186        @Override
187        protected Object toStoreValue(@Nullable Object userValue) {
188                Object storeValue = super.toStoreValue(userValue);
189                if (this.serialization != null) {
190                        try {
191                                return this.serialization.serializeToByteArray(storeValue);
192                        }
193                        catch (Throwable ex) {
194                                throw new IllegalArgumentException("Failed to serialize cache value '" + userValue +
195                                                "'. Does it implement Serializable?", ex);
196                        }
197                }
198                else {
199                        return storeValue;
200                }
201        }
202
203        @Override
204        protected Object fromStoreValue(@Nullable Object storeValue) {
205                if (storeValue != null && this.serialization != null) {
206                        try {
207                                return super.fromStoreValue(this.serialization.deserializeFromByteArray((byte[]) storeValue));
208                        }
209                        catch (Throwable ex) {
210                                throw new IllegalArgumentException("Failed to deserialize cache value '" + storeValue + "'", ex);
211                        }
212                }
213                else {
214                        return super.fromStoreValue(storeValue);
215                }
216        }
217
218}