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