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.ehcache;
018
019import java.util.concurrent.Callable;
020
021import net.sf.ehcache.Ehcache;
022import net.sf.ehcache.Element;
023import net.sf.ehcache.Status;
024
025import org.springframework.cache.Cache;
026import org.springframework.cache.support.SimpleValueWrapper;
027import org.springframework.lang.Nullable;
028import org.springframework.util.Assert;
029
030/**
031 * {@link Cache} implementation on top of an {@link Ehcache} instance.
032 *
033 * @author Costin Leau
034 * @author Juergen Hoeller
035 * @author Stephane Nicoll
036 * @since 3.1
037 * @see EhCacheCacheManager
038 */
039public class EhCacheCache implements Cache {
040
041        private final Ehcache cache;
042
043
044        /**
045         * Create an {@link EhCacheCache} instance.
046         * @param ehcache the backing Ehcache instance
047         */
048        public EhCacheCache(Ehcache ehcache) {
049                Assert.notNull(ehcache, "Ehcache must not be null");
050                Status status = ehcache.getStatus();
051                if (!Status.STATUS_ALIVE.equals(status)) {
052                        throw new IllegalArgumentException(
053                                        "An 'alive' Ehcache is required - current cache is " + status.toString());
054                }
055                this.cache = ehcache;
056        }
057
058
059        @Override
060        public final String getName() {
061                return this.cache.getName();
062        }
063
064        @Override
065        public final Ehcache getNativeCache() {
066                return this.cache;
067        }
068
069        @Override
070        @Nullable
071        public ValueWrapper get(Object key) {
072                Element element = lookup(key);
073                return toValueWrapper(element);
074        }
075
076        @SuppressWarnings("unchecked")
077        @Override
078        @Nullable
079        public <T> T get(Object key, @Nullable Class<T> type) {
080                Element element = this.cache.get(key);
081                Object value = (element != null ? element.getObjectValue() : null);
082                if (value != null && type != null && !type.isInstance(value)) {
083                        throw new IllegalStateException(
084                                        "Cached value is not of required type [" + type.getName() + "]: " + value);
085                }
086                return (T) value;
087        }
088
089        @SuppressWarnings("unchecked")
090        @Override
091        @Nullable
092        public <T> T get(Object key, Callable<T> valueLoader) {
093                Element element = lookup(key);
094                if (element != null) {
095                        return (T) element.getObjectValue();
096                }
097                else {
098                        this.cache.acquireWriteLockOnKey(key);
099                        try {
100                                element = lookup(key);  // one more attempt with the write lock
101                                if (element != null) {
102                                        return (T) element.getObjectValue();
103                                }
104                                else {
105                                        return loadValue(key, valueLoader);
106                                }
107                        }
108                        finally {
109                                this.cache.releaseWriteLockOnKey(key);
110                        }
111                }
112        }
113
114        private <T> T loadValue(Object key, Callable<T> valueLoader) {
115                T value;
116                try {
117                        value = valueLoader.call();
118                }
119                catch (Throwable ex) {
120                        throw new ValueRetrievalException(key, valueLoader, ex);
121                }
122                put(key, value);
123                return value;
124        }
125
126        @Override
127        public void put(Object key, @Nullable Object value) {
128                this.cache.put(new Element(key, value));
129        }
130
131        @Override
132        @Nullable
133        public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
134                Element existingElement = this.cache.putIfAbsent(new Element(key, value));
135                return toValueWrapper(existingElement);
136        }
137
138        @Override
139        public void evict(Object key) {
140                this.cache.remove(key);
141        }
142
143        @Override
144        public boolean evictIfPresent(Object key) {
145                return this.cache.remove(key);
146        }
147
148        @Override
149        public void clear() {
150                this.cache.removeAll();
151        }
152
153        @Override
154        public boolean invalidate() {
155                boolean notEmpty = (this.cache.getSize() > 0);
156                this.cache.removeAll();
157                return notEmpty;
158        }
159
160
161        @Nullable
162        private Element lookup(Object key) {
163                return this.cache.get(key);
164        }
165
166        @Nullable
167        private ValueWrapper toValueWrapper(@Nullable Element element) {
168                return (element != null ? new SimpleValueWrapper(element.getObjectValue()) : null);
169        }
170
171}