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}