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}