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.util.LinkedHashMap;
020import java.util.Map;
021import java.util.concurrent.atomic.AtomicBoolean;
022
023import org.apache.camel.util.concurrent.ThreadHelper;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027/**
028 * Factory to create {@link LRUCache} instances.
029 */
030public final class LRUCacheFactory {
031
032    private static final Logger LOG = LoggerFactory.getLogger(LRUCacheFactory.class);
033
034    private static final AtomicBoolean INIT = new AtomicBoolean();
035
036    private static final boolean USE_SIMPLE_CACHE;
037
038    private LRUCacheFactory() {
039    }
040
041    static {
042        USE_SIMPLE_CACHE = "true".equalsIgnoreCase(System.getProperty("CamelSimpleLRUCacheFactory", "false"));
043        if (!USE_SIMPLE_CACHE) {
044            boolean warmUp = "true".equalsIgnoreCase(System.getProperty("CamelWarmUpLRUCacheFactory", "true"));
045            if (warmUp) {
046                // warm-up LRUCache which happens in a background test, which can speedup starting Camel
047                // as the warm-up can run concurrently with starting up Camel and the runtime container Camel may be running inside
048                warmUp();
049            }
050        }
051    }
052
053
054    /**
055     * Warm-up the LRUCache to startup Apache Camel faster.
056     */
057    public static void warmUp() {
058        // create a dummy map in a separate thread to warm-up the Caffeine cache concurrently
059        // while Camel is starting up. This allows us to overall startup Camel a bit faster
060        // as Caffeine takes 150+ millis to initialize.
061        if (INIT.compareAndSet(false, true)) {
062            // only need to init Caffeine once in the JVM/classloader
063            Runnable task = () -> {
064                StopWatch watch = new StopWatch();
065                LOG.debug("Warming up LRUCache ...");
066                newLRUCache(16);
067                LOG.debug("Warming up LRUCache complete in {} millis", watch.taken());
068            };
069
070            String threadName = ThreadHelper.resolveThreadName(null, "LRUCacheFactory");
071
072            Thread thread = new Thread(task, threadName);
073            thread.start();
074        }
075    }
076
077    /**
078     * Constructs an empty <tt>LRUCache</tt> instance with the
079     * specified maximumCacheSize, and will stop on eviction.
080     *
081     * @param maximumCacheSize the max capacity.
082     * @throws IllegalArgumentException if the initial capacity is negative
083     */
084    public static <K, V> Map<K, V> newLRUCache(int maximumCacheSize) {
085        LOG.trace("Creating LRUCache with maximumCacheSize: {}", maximumCacheSize);
086        if (USE_SIMPLE_CACHE) {
087            return new SimpleLRUCache<>(maximumCacheSize);
088        }
089        return new LRUCache<>(maximumCacheSize);
090    }
091
092    /**
093     * Constructs an empty <tt>LRUCache</tt> instance with the
094     * specified initial capacity, maximumCacheSize, and will stop on eviction.
095     *
096     * @param initialCapacity  the initial capacity.
097     * @param maximumCacheSize the max capacity.
098     * @throws IllegalArgumentException if the initial capacity is negative
099     */
100    public static <K, V> Map<K, V> newLRUCache(int initialCapacity, int maximumCacheSize) {
101        LOG.trace("Creating LRUCache with initialCapacity: {}, maximumCacheSize: {}", initialCapacity, maximumCacheSize);
102        if (USE_SIMPLE_CACHE) {
103            return new SimpleLRUCache<>(initialCapacity, maximumCacheSize);
104        }
105        return new LRUCache<>(initialCapacity, maximumCacheSize);
106    }
107
108    /**
109     * Constructs an empty <tt>LRUCache</tt> instance with the
110     * specified initial capacity, maximumCacheSize,load factor and ordering mode.
111     *
112     * @param initialCapacity  the initial capacity.
113     * @param maximumCacheSize the max capacity.
114     * @param stopOnEviction   whether to stop service on eviction.
115     * @throws IllegalArgumentException if the initial capacity is negative
116     */
117    public static <K, V> Map<K, V> newLRUCache(int initialCapacity, int maximumCacheSize, boolean stopOnEviction) {
118        LOG.trace("Creating LRUCache with initialCapacity: {}, maximumCacheSize: {}, stopOnEviction: {}", initialCapacity, maximumCacheSize, stopOnEviction);
119        if (USE_SIMPLE_CACHE) {
120            return new SimpleLRUCache<>(initialCapacity, maximumCacheSize, stopOnEviction);
121        }
122        return new LRUCache<>(initialCapacity, maximumCacheSize, stopOnEviction);
123    }
124
125    /**
126     * Constructs an empty <tt>LRUSoftCache</tt> instance with the
127     * specified maximumCacheSize, and will stop on eviction.
128     *
129     * @param maximumCacheSize the max capacity.
130     * @throws IllegalArgumentException if the initial capacity is negative
131     */
132    public static <K, V> Map<K, V> newLRUSoftCache(int maximumCacheSize) {
133        LOG.trace("Creating LRUSoftCache with maximumCacheSize: {}", maximumCacheSize);
134        if (USE_SIMPLE_CACHE) {
135            return new SimpleLRUCache<>(maximumCacheSize);
136        }
137        return new LRUSoftCache<>(maximumCacheSize);
138    }
139
140    /**
141     * Constructs an empty <tt>LRUWeakCache</tt> instance with the
142     * specified maximumCacheSize, and will stop on eviction.
143     *
144     * @param maximumCacheSize the max capacity.
145     * @throws IllegalArgumentException if the initial capacity is negative
146     */
147    public static <K, V> Map<K, V> newLRUWeakCache(int maximumCacheSize) {
148        LOG.trace("Creating LRUWeakCache with maximumCacheSize: {}", maximumCacheSize);
149        if (USE_SIMPLE_CACHE) {
150            return new SimpleLRUCache<>(maximumCacheSize);
151        }
152        return new LRUWeakCache<>(maximumCacheSize);
153    }
154
155    private static class SimpleLRUCache<K, V> extends LinkedHashMap<K, V> {
156
157        static final float DEFAULT_LOAD_FACTOR = 0.75f;
158
159        private final int maximumCacheSize;
160        private final boolean stopOnEviction;
161
162        public SimpleLRUCache(int maximumCacheSize) {
163            this(16, maximumCacheSize, maximumCacheSize > 0);
164        }
165
166        public SimpleLRUCache(int initialCapacity, int maximumCacheSize) {
167            this(initialCapacity, maximumCacheSize, maximumCacheSize > 0);
168        }
169
170        public SimpleLRUCache(int initialCapacity, int maximumCacheSize, boolean stopOnEviction) {
171            super(initialCapacity, DEFAULT_LOAD_FACTOR, true);
172            this.maximumCacheSize = maximumCacheSize;
173            this.stopOnEviction = stopOnEviction;
174        }
175
176        @Override
177        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
178            if (size() >= maximumCacheSize) {
179                if (stopOnEviction) {
180                    Object value = eldest.getValue();
181                    try {
182                        // stop service as its evicted from cache
183                        ServiceHelper.stopService(value);
184                    } catch (Exception e) {
185                        LOG.warn("Error stopping service: " + value + ". This exception will be ignored.", e);
186                    }
187                }
188                return true;
189            }
190            return false;
191        }
192    }
193
194}