001/*
002 * Copyright 2002-2018 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.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.util.concurrent.Callable;
023import java.util.concurrent.ConcurrentHashMap;
024import java.util.concurrent.ConcurrentMap;
025
026import org.springframework.cache.support.AbstractValueAdaptingCache;
027import org.springframework.core.serializer.support.SerializationDelegate;
028import org.springframework.util.Assert;
029
030/**
031 * Simple {@link org.springframework.cache.Cache} implementation based on the
032 * core JDK {@code java.util.concurrent} package.
033 *
034 * <p>Useful for testing or simple caching scenarios, typically in combination
035 * with {@link org.springframework.cache.support.SimpleCacheManager} or
036 * dynamically through {@link ConcurrentMapCacheManager}.
037 *
038 * <p><b>Note:</b> As {@link ConcurrentHashMap} (the default implementation used)
039 * does not allow for {@code null} values to be stored, this class will replace
040 * them with a predefined internal object. This behavior can be changed through the
041 * {@link #ConcurrentMapCache(String, ConcurrentMap, boolean)} constructor.
042 *
043 * @author Costin Leau
044 * @author Juergen Hoeller
045 * @author Stephane Nicoll
046 * @since 3.1
047 */
048public class ConcurrentMapCache extends AbstractValueAdaptingCache {
049
050        private final String name;
051
052        private final ConcurrentMap<Object, Object> store;
053
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<Object, Object>(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<Object, Object>(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, 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        protected Object lookup(Object key) {
134                return this.store.get(key);
135        }
136
137        @SuppressWarnings("unchecked")
138        @Override
139        public <T> T get(Object key, Callable<T> valueLoader) {
140                // Try efficient lookup on the ConcurrentHashMap first...
141                ValueWrapper storeValue = get(key);
142                if (storeValue != null) {
143                        return (T) storeValue.get();
144                }
145
146                // No value found -> load value within full synchronization.
147                synchronized (this.store) {
148                        storeValue = get(key);
149                        if (storeValue != null) {
150                                return (T) storeValue.get();
151                        }
152
153                        T value;
154                        try {
155                                value = valueLoader.call();
156                        }
157                        catch (Throwable ex) {
158                                throw new ValueRetrievalException(key, valueLoader, ex);
159                        }
160                        put(key, value);
161                        return value;
162                }
163        }
164
165        @Override
166        public void put(Object key, Object value) {
167                this.store.put(key, toStoreValue(value));
168        }
169
170        @Override
171        public ValueWrapper putIfAbsent(Object key, Object value) {
172                Object existing = this.store.putIfAbsent(key, toStoreValue(value));
173                return toValueWrapper(existing);
174        }
175
176        @Override
177        public void evict(Object key) {
178                this.store.remove(key);
179        }
180
181        @Override
182        public void clear() {
183                this.store.clear();
184        }
185
186        @Override
187        protected Object toStoreValue(Object userValue) {
188                Object storeValue = super.toStoreValue(userValue);
189                if (this.serialization != null) {
190                        try {
191                                return serializeValue(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        private Object serializeValue(Object storeValue) throws IOException {
204                ByteArrayOutputStream out = new ByteArrayOutputStream();
205                try {
206                        this.serialization.serialize(storeValue, out);
207                        return out.toByteArray();
208                }
209                finally {
210                        out.close();
211                }
212        }
213
214        @Override
215        protected Object fromStoreValue(Object storeValue) {
216                if (this.serialization != null) {
217                        try {
218                                return super.fromStoreValue(deserializeValue(storeValue));
219                        }
220                        catch (Throwable ex) {
221                                throw new IllegalArgumentException("Failed to deserialize cache value '" + storeValue + "'", ex);
222                        }
223                }
224                else {
225                        return super.fromStoreValue(storeValue);
226                }
227
228        }
229
230        private Object deserializeValue(Object storeValue) throws IOException {
231                ByteArrayInputStream in = new ByteArrayInputStream((byte[]) storeValue);
232                try {
233                        return this.serialization.deserialize(in);
234                }
235                finally {
236                        in.close();
237                }
238        }
239
240}