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     */
017    package org.apache.camel.util;
018    
019    import java.io.Serializable;
020    import java.util.Collection;
021    import java.util.Map;
022    import java.util.Set;
023    import java.util.concurrent.atomic.AtomicLong;
024    
025    import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
026    import com.googlecode.concurrentlinkedhashmap.EvictionListener;
027    import org.slf4j.Logger;
028    import org.slf4j.LoggerFactory;
029    
030    /**
031     * A Least Recently Used Cache.
032     * <p/>
033     * If this cache stores {@link org.apache.camel.Service} then this implementation will on eviction
034     * invoke the {@link org.apache.camel.Service#stop()} method, to auto-stop the service.
035     *
036     * @see LRUSoftCache
037     * @see LRUWeakCache
038     */
039    public class LRUCache<K, V> implements Map<K, V>, EvictionListener<K, V>, Serializable {
040        private static final long serialVersionUID = -342098639681884414L;
041        private static final Logger LOG = LoggerFactory.getLogger(LRUCache.class);
042        
043        private int maxCacheSize = 10000;
044        private boolean stopOnEviction;
045        private final AtomicLong hits = new AtomicLong();
046        private final AtomicLong misses = new AtomicLong();
047        private final AtomicLong evicted = new AtomicLong();
048        private ConcurrentLinkedHashMap<K, V> map;
049    
050        /**
051         * Constructs an empty <tt>LRUCache</tt> instance with the
052         * specified maximumCacheSize, and will stop on eviction.
053         *
054         * @param maximumCacheSize the max capacity.
055         * @throws IllegalArgumentException if the initial capacity is negative
056         */
057        public LRUCache(int maximumCacheSize) {
058            this(maximumCacheSize, maximumCacheSize);
059        }
060    
061        /**
062         * Constructs an empty <tt>LRUCache</tt> instance with the
063         * specified initial capacity, maximumCacheSize, and will stop on eviction.
064         *
065         * @param initialCapacity  the initial capacity.
066         * @param maximumCacheSize the max capacity.
067         * @throws IllegalArgumentException if the initial capacity is negative
068         */
069        public LRUCache(int initialCapacity, int maximumCacheSize) {
070            this(initialCapacity, maximumCacheSize, true);
071        }
072    
073        /**
074         * Constructs an empty <tt>LRUCache</tt> instance with the
075         * specified initial capacity, maximumCacheSize,load factor and ordering mode.
076         *
077         * @param initialCapacity  the initial capacity.
078         * @param maximumCacheSize the max capacity.
079         * @param stopOnEviction   whether to stop service on eviction.
080         * @throws IllegalArgumentException if the initial capacity is negative
081         */
082        public LRUCache(int initialCapacity, int maximumCacheSize, boolean stopOnEviction) {
083            map = new ConcurrentLinkedHashMap.Builder<K, V>()
084                    .initialCapacity(initialCapacity)
085                    .maximumWeightedCapacity(maximumCacheSize)
086                    .listener(this).build();
087            this.maxCacheSize = maximumCacheSize;
088            this.stopOnEviction = stopOnEviction;
089        }
090    
091        @Override
092        public V get(Object o) {
093            V answer = map.get(o);
094            if (answer != null) {
095                hits.incrementAndGet();
096            } else {
097                misses.incrementAndGet();
098            }
099            return answer;
100        }
101    
102        @Override
103        public int size() {
104            return map.size();
105        }
106    
107        @Override
108        public boolean isEmpty() {
109            return map.isEmpty();
110        }
111    
112        @Override
113        public boolean containsKey(Object o) {
114            return map.containsKey(o);
115        }
116    
117        @Override
118        public boolean containsValue(Object o) {
119            return map.containsValue(0);
120        }
121    
122        @Override
123        public V put(K k, V v) {
124            return map.put(k, v);
125        }
126    
127        @Override
128        public V remove(Object o) {
129            return map.remove(o);
130        }
131    
132        public void putAll(Map<? extends K, ? extends V> map) {
133            this.map.putAll(map);
134        }
135    
136        @Override
137        public void clear() {
138            map.clear();
139            resetStatistics();
140        }
141    
142        @Override
143        public Set<K> keySet() {
144            return map.ascendingKeySet();
145        }
146    
147        @Override
148        public Collection<V> values() {
149            return map.ascendingMap().values();
150        }
151    
152        @Override
153        public Set<Entry<K, V>> entrySet() {
154            return map.ascendingMap().entrySet();
155        }
156    
157        @Override
158        public void onEviction(K key, V value) {
159            evicted.incrementAndGet();
160            LOG.trace("onEviction {} -> {}", key, value);
161            if (stopOnEviction) {
162                try {
163                    // stop service as its evicted from cache
164                    ServiceHelper.stopService(value);
165                } catch (Exception e) {
166                    LOG.warn("Error stopping service: " + value + ". This exception will be ignored.", e);
167                }
168            }
169        }
170    
171        /**
172         * Gets the number of cache hits
173         */
174        public long getHits() {
175            return hits.get();
176        }
177    
178        /**
179         * Gets the number of cache misses.
180         */
181        public long getMisses() {
182            return misses.get();
183        }
184    
185        /**
186         * Gets the number of evicted entries.
187         */
188        public long getEvicted() {
189            return evicted.get();
190        }
191    
192        /**
193         * Returns the maxCacheSize.
194         */
195        public int getMaxCacheSize() {
196            return maxCacheSize;
197        }
198    
199        /**
200         * Rest the cache statistics such as hits and misses.
201         */
202        public void resetStatistics() {
203            hits.set(0);
204            misses.set(0);
205            evicted.set(0);
206        }
207    
208        @Override
209        public String toString() {
210            return "LRUCache@" + ObjectHelper.getIdentityHashCode(this);
211        }
212    }