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.support;
018
019import org.springframework.cache.Cache;
020import org.springframework.lang.Nullable;
021
022/**
023 * Common base class for {@link Cache} implementations that need to adapt
024 * {@code null} values (and potentially other such special values) before
025 * passing them on to the underlying store.
026 *
027 * <p>Transparently replaces given {@code null} user values with an internal
028 * {@link NullValue#INSTANCE}, if configured to support {@code null} values
029 * (as indicated by {@link #isAllowNullValues()}.
030 *
031 * @author Juergen Hoeller
032 * @since 4.2.2
033 */
034public abstract class AbstractValueAdaptingCache implements Cache {
035
036        private final boolean allowNullValues;
037
038
039        /**
040         * Create an {@code AbstractValueAdaptingCache} with the given setting.
041         * @param allowNullValues whether to allow for {@code null} values
042         */
043        protected AbstractValueAdaptingCache(boolean allowNullValues) {
044                this.allowNullValues = allowNullValues;
045        }
046
047
048        /**
049         * Return whether {@code null} values are allowed in this cache.
050         */
051        public final boolean isAllowNullValues() {
052                return this.allowNullValues;
053        }
054
055        @Override
056        @Nullable
057        public ValueWrapper get(Object key) {
058                return toValueWrapper(lookup(key));
059        }
060
061        @Override
062        @SuppressWarnings("unchecked")
063        @Nullable
064        public <T> T get(Object key, @Nullable Class<T> type) {
065                Object value = fromStoreValue(lookup(key));
066                if (value != null && type != null && !type.isInstance(value)) {
067                        throw new IllegalStateException(
068                                        "Cached value is not of required type [" + type.getName() + "]: " + value);
069                }
070                return (T) value;
071        }
072
073        /**
074         * Perform an actual lookup in the underlying store.
075         * @param key the key whose associated value is to be returned
076         * @return the raw store value for the key, or {@code null} if none
077         */
078        @Nullable
079        protected abstract Object lookup(Object key);
080
081
082        /**
083         * Convert the given value from the internal store to a user value
084         * returned from the get method (adapting {@code null}).
085         * @param storeValue the store value
086         * @return the value to return to the user
087         */
088        @Nullable
089        protected Object fromStoreValue(@Nullable Object storeValue) {
090                if (this.allowNullValues && storeValue == NullValue.INSTANCE) {
091                        return null;
092                }
093                return storeValue;
094        }
095
096        /**
097         * Convert the given user value, as passed into the put method,
098         * to a value in the internal store (adapting {@code null}).
099         * @param userValue the given user value
100         * @return the value to store
101         */
102        protected Object toStoreValue(@Nullable Object userValue) {
103                if (userValue == null) {
104                        if (this.allowNullValues) {
105                                return NullValue.INSTANCE;
106                        }
107                        throw new IllegalArgumentException(
108                                        "Cache '" + getName() + "' is configured to not allow null values but null was provided");
109                }
110                return userValue;
111        }
112
113        /**
114         * Wrap the given store value with a {@link SimpleValueWrapper}, also going
115         * through {@link #fromStoreValue} conversion. Useful for {@link #get(Object)}
116         * and {@link #putIfAbsent(Object, Object)} implementations.
117         * @param storeValue the original value
118         * @return the wrapped value
119         */
120        @Nullable
121        protected Cache.ValueWrapper toValueWrapper(@Nullable Object storeValue) {
122                return (storeValue != null ? new SimpleValueWrapper(fromStoreValue(storeValue)) : null);
123        }
124
125}