001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.io.Serializable;
020import java.util.Collection;
021import java.util.Map;
022import java.util.Set;
023import java.util.concurrent.atomic.AtomicLong;
024
025import com.github.benmanes.caffeine.cache.Cache;
026import com.github.benmanes.caffeine.cache.Caffeine;
027import com.github.benmanes.caffeine.cache.RemovalCause;
028import com.github.benmanes.caffeine.cache.RemovalListener;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * A cache that uses a near optional LRU Cache.
034 * <p/>
035 * The Cache is implemented by Caffeine which provides an <a href="https://github.com/ben-manes/caffeine/wiki/Efficiency">efficient cache</a>.
036 * <p/>
037 * If this cache stores {@link org.apache.camel.Service} then this implementation will on eviction
038 * invoke the {@link org.apache.camel.Service#stop()} method, to auto-stop the service.
039 *
040 * @see LRUSoftCache
041 * @see LRUWeakCache
042 */
043public class LRUCache<K, V> implements Map<K, V>, RemovalListener<K, V>, Serializable {
044    private static final Logger LOG = LoggerFactory.getLogger(LRUCache.class);
045
046    protected final AtomicLong hits = new AtomicLong();
047    protected final AtomicLong misses = new AtomicLong();
048    protected final AtomicLong evicted = new AtomicLong();
049
050    private int maxCacheSize = 10000;
051    private boolean stopOnEviction;
052    private final Cache<K, V> cache;
053    private final Map<K, V> map;
054
055    /**
056     * Constructs an empty <tt>LRUCache</tt> instance with the
057     * specified maximumCacheSize, and will stop on eviction.
058     *
059     * @param maximumCacheSize the max capacity.
060     * @throws IllegalArgumentException if the initial capacity is negative
061     */
062    public LRUCache(int maximumCacheSize) {
063        this(16, maximumCacheSize); // 16 is the default initial capacity in ConcurrentLinkedHashMap
064    }
065
066    /**
067     * Constructs an empty <tt>LRUCache</tt> instance with the
068     * specified initial capacity, maximumCacheSize, and will stop on eviction.
069     *
070     * @param initialCapacity  the initial capacity.
071     * @param maximumCacheSize the max capacity.
072     * @throws IllegalArgumentException if the initial capacity is negative
073     */
074    public LRUCache(int initialCapacity, int maximumCacheSize) {
075        //Do not stop service if ConcurrentLinkedHashMap try to evict entry when its max capacity is zero.
076        this(initialCapacity, maximumCacheSize, maximumCacheSize > 0);
077    }
078
079    /**
080     * Constructs an empty <tt>LRUCache</tt> instance with the
081     * specified initial capacity, maximumCacheSize,load factor and ordering mode.
082     *
083     * @param initialCapacity  the initial capacity.
084     * @param maximumCacheSize the max capacity.
085     * @param stopOnEviction   whether to stop service on eviction.
086     * @throws IllegalArgumentException if the initial capacity is negative
087     */
088    public LRUCache(int initialCapacity, int maximumCacheSize, boolean stopOnEviction) {
089        this(initialCapacity, maximumCacheSize, stopOnEviction, false, false, false);
090    }
091
092    /**
093     * Constructs an empty <tt>LRUCache</tt> instance with the
094     * specified initial capacity, maximumCacheSize,load factor and ordering mode.
095     *
096     * @param initialCapacity  the initial capacity.
097     * @param maximumCacheSize the max capacity.
098     * @param stopOnEviction   whether to stop service on eviction.
099     * @param soft             whether to use soft values a soft cache  (default is false)
100     * @param weak             whether to use weak keys/values as a weak cache  (default is false)
101     * @param syncListener     whether to use synchronous call for the eviction listener (default is false)
102     * @throws IllegalArgumentException if the initial capacity is negative
103     */
104    public LRUCache(int initialCapacity, int maximumCacheSize, boolean stopOnEviction,
105                    boolean soft, boolean weak, boolean syncListener) {
106        Caffeine<K, V> caffeine = Caffeine.newBuilder()
107                .initialCapacity(initialCapacity)
108                .maximumSize(maximumCacheSize)
109                .removalListener(this);
110        if (soft) {
111            caffeine.softValues();
112        }
113        if (weak) {
114            caffeine.weakKeys();
115            caffeine.weakValues();
116        }
117        if (syncListener) {
118            caffeine.executor(Runnable::run);
119        }
120
121        this.cache = caffeine.build();
122        this.map = cache.asMap();
123        this.maxCacheSize = maximumCacheSize;
124        this.stopOnEviction = stopOnEviction;
125    }
126
127    @Override
128    public V get(Object o) {
129        V answer = map.get(o);
130        if (answer != null) {
131            hits.incrementAndGet();
132        } else {
133            misses.incrementAndGet();
134        }
135        return answer;
136    }
137
138    @Override
139    public int size() {
140        return map.size();
141    }
142
143    @Override
144    public boolean isEmpty() {
145        return map.isEmpty();
146    }
147
148    @Override
149    public boolean containsKey(Object o) {
150        return map.containsKey(o);
151    }
152
153    @Override
154    public boolean containsValue(Object o) {
155        return map.containsValue(0);
156    }
157
158    @Override
159    public V put(K k, V v) {
160        return map.put(k, v);
161    }
162
163    @Override
164    public V remove(Object o) {
165        return map.remove(o);
166    }
167
168    public void putAll(Map<? extends K, ? extends V> map) {
169        this.cache.putAll(map);
170    }
171
172    @Override
173    public void clear() {
174        map.clear();
175        resetStatistics();
176    }
177
178    @Override
179    public Set<K> keySet() {
180        return map.keySet();
181    }
182
183    @Override
184    public Collection<V> values() {
185        return map.values();
186    }
187
188    @Override
189    public Set<Entry<K, V>> entrySet() {
190        return map.entrySet();
191    }
192
193    @Override
194    public void onRemoval(K key, V value, RemovalCause cause) {
195        if (cause.wasEvicted()) {
196            evicted.incrementAndGet();
197            LOG.trace("onRemoval {} -> {}", key, value);
198            if (stopOnEviction) {
199                try {
200                    // stop service as its evicted from cache
201                    ServiceHelper.stopService(value);
202                } catch (Exception e) {
203                    LOG.warn("Error stopping service: " + value + ". This exception will be ignored.", e);
204                }
205            }
206        }
207    }
208
209    /**
210     * Gets the number of cache hits
211     */
212    public long getHits() {
213        return hits.get();
214    }
215
216    /**
217     * Gets the number of cache misses.
218     */
219    public long getMisses() {
220        return misses.get();
221    }
222
223    /**
224     * Gets the number of evicted entries.
225     */
226    public long getEvicted() {
227        return evicted.get();
228    }
229
230    /**
231     * Returns the maxCacheSize.
232     */
233    public int getMaxCacheSize() {
234        return maxCacheSize;
235    }
236
237    /**
238     * Rest the cache statistics such as hits and misses.
239     */
240    public void resetStatistics() {
241        hits.set(0);
242        misses.set(0);
243        evicted.set(0);
244    }
245
246    public void cleanUp() {
247        cache.cleanUp();
248    }
249
250    @Override
251    public String toString() {
252        return "LRUCache@" + ObjectHelper.getIdentityHashCode(this);
253    }
254}