001/*
002 * Copyright 2002-2015 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.concurrent.Callable;
020import java.util.concurrent.ExecutionException;
021
022import com.google.common.cache.LoadingCache;
023import com.google.common.util.concurrent.UncheckedExecutionException;
024
025import org.springframework.cache.support.AbstractValueAdaptingCache;
026import org.springframework.util.Assert;
027
028/**
029 * Spring {@link org.springframework.cache.Cache} adapter implementation
030 * on top of a Guava {@link com.google.common.cache.Cache} instance.
031 *
032 * <p>Requires Google Guava 12.0 or higher.
033 *
034 * @author Juergen Hoeller
035 * @author Stephane Nicoll
036 * @since 4.0
037 */
038public class GuavaCache extends AbstractValueAdaptingCache {
039
040        private final String name;
041
042        private final com.google.common.cache.Cache<Object, Object> cache;
043
044
045        /**
046         * Create a {@link GuavaCache} instance with the specified name and the
047         * given internal {@link com.google.common.cache.Cache} to use.
048         * @param name the name of the cache
049         * @param cache the backing Guava Cache instance
050         */
051        public GuavaCache(String name, com.google.common.cache.Cache<Object, Object> cache) {
052                this(name, cache, true);
053        }
054
055        /**
056         * Create a {@link GuavaCache} instance with the specified name and the
057         * given internal {@link com.google.common.cache.Cache} to use.
058         * @param name the name of the cache
059         * @param cache the backing Guava Cache instance
060         * @param allowNullValues whether to accept and convert {@code null}
061         * values for this cache
062         */
063        public GuavaCache(String name, com.google.common.cache.Cache<Object, Object> cache, boolean allowNullValues) {
064                super(allowNullValues);
065                Assert.notNull(name, "Name must not be null");
066                Assert.notNull(cache, "Cache must not be null");
067                this.name = name;
068                this.cache = cache;
069        }
070
071
072        @Override
073        public final String getName() {
074                return this.name;
075        }
076
077        @Override
078        public final com.google.common.cache.Cache<Object, Object> getNativeCache() {
079                return this.cache;
080        }
081
082        @Override
083        public ValueWrapper get(Object key) {
084                if (this.cache instanceof LoadingCache) {
085                        try {
086                                Object value = ((LoadingCache<Object, Object>) this.cache).get(key);
087                                return toValueWrapper(value);
088                        }
089                        catch (ExecutionException ex) {
090                                throw new UncheckedExecutionException(ex.getMessage(), ex);
091                        }
092                }
093                return super.get(key);
094        }
095
096        @SuppressWarnings("unchecked")
097        @Override
098        public <T> T get(Object key, final Callable<T> valueLoader) {
099                try {
100                        return (T) fromStoreValue(this.cache.get(key, new Callable<Object>() {
101                                @Override
102                                public Object call() throws Exception {
103                                        return toStoreValue(valueLoader.call());
104                                }
105                        }));
106                }
107                catch (ExecutionException ex) {
108                        throw new ValueRetrievalException(key, valueLoader, ex.getCause());
109                }
110                catch (UncheckedExecutionException ex) {
111                        throw new ValueRetrievalException(key, valueLoader, ex.getCause());
112                }
113        }
114
115        @Override
116        protected Object lookup(Object key) {
117                return this.cache.getIfPresent(key);
118        }
119
120        @Override
121        public void put(Object key, Object value) {
122                this.cache.put(key, toStoreValue(value));
123        }
124
125        @Override
126        public ValueWrapper putIfAbsent(Object key, final Object value) {
127                try {
128                        PutIfAbsentCallable callable = new PutIfAbsentCallable(value);
129                        Object result = this.cache.get(key, callable);
130                        return (callable.called ? null : toValueWrapper(result));
131                }
132                catch (ExecutionException ex) {
133                        throw new IllegalStateException(ex);
134                }
135        }
136
137        @Override
138        public void evict(Object key) {
139                this.cache.invalidate(key);
140        }
141
142        @Override
143        public void clear() {
144                this.cache.invalidateAll();
145        }
146
147
148        private class PutIfAbsentCallable implements Callable<Object> {
149
150                private final Object value;
151
152                private boolean called;
153
154                public PutIfAbsentCallable(Object value) {
155                        this.value = value;
156                }
157
158                @Override
159                public Object call() throws Exception {
160                        this.called = true;
161                        return toStoreValue(this.value);
162                }
163        }
164
165}