1
17 package org.apache.catalina.webresources;
18
19 import java.util.Comparator;
20 import java.util.Iterator;
21 import java.util.TreeSet;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.ConcurrentMap;
24 import java.util.concurrent.atomic.AtomicLong;
25
26 import org.apache.catalina.WebResource;
27 import org.apache.juli.logging.Log;
28 import org.apache.juli.logging.LogFactory;
29 import org.apache.tomcat.util.res.StringManager;
30
31 public class Cache {
32
33 private static final Log log = LogFactory.getLog(Cache.class);
34 protected static final StringManager sm = StringManager.getManager(Cache.class);
35
36 private static final long TARGET_FREE_PERCENT_GET = 5;
37 private static final long TARGET_FREE_PERCENT_BACKGROUND = 10;
38
39
40 private static final int OBJECT_MAX_SIZE_FACTOR = 20;
41
42 private final StandardRoot root;
43 private final AtomicLong size = new AtomicLong(0);
44
45 private long ttl = 5000;
46 private long maxSize = 10 * 1024 * 1024;
47 private int objectMaxSize = (int) maxSize/OBJECT_MAX_SIZE_FACTOR;
48
49 private AtomicLong lookupCount = new AtomicLong(0);
50 private AtomicLong hitCount = new AtomicLong(0);
51
52 private final ConcurrentMap<String,CachedResource> resourceCache =
53 new ConcurrentHashMap<>();
54
55 public Cache(StandardRoot root) {
56 this.root = root;
57 }
58
59 protected WebResource getResource(String path, boolean useClassLoaderResources) {
60
61 if (noCache(path)) {
62 return root.getResourceInternal(path, useClassLoaderResources);
63 }
64
65 lookupCount.incrementAndGet();
66
67 CachedResource cacheEntry = resourceCache.get(path);
68
69 if (cacheEntry != null && !cacheEntry.validateResource(useClassLoaderResources)) {
70 removeCacheEntry(path);
71 cacheEntry = null;
72 }
73
74 if (cacheEntry == null) {
75
76 int objectMaxSizeBytes = getObjectMaxSizeBytes();
77 CachedResource newCacheEntry = new CachedResource(this, root, path, getTtl(),
78 objectMaxSizeBytes, useClassLoaderResources);
79
80
81
82 cacheEntry = resourceCache.putIfAbsent(path, newCacheEntry);
83
84 if (cacheEntry == null) {
85
86 cacheEntry = newCacheEntry;
87 cacheEntry.validateResource(useClassLoaderResources);
88
89
90
91
92 long delta = cacheEntry.getSize();
93 size.addAndGet(delta);
94
95 if (size.get() > maxSize) {
96
97
98
99
100 long targetSize = maxSize * (100 - TARGET_FREE_PERCENT_GET) / 100;
101 long newSize = evict(targetSize, resourceCache.values().iterator());
102 if (newSize > maxSize) {
103
104
105 removeCacheEntry(path);
106 log.warn(sm.getString("cache.addFail", path, root.getContext().getName()));
107 }
108 }
109 } else {
110
111
112 cacheEntry.validateResource(useClassLoaderResources);
113 }
114 } else {
115 hitCount.incrementAndGet();
116 }
117
118 return cacheEntry;
119 }
120
121 protected WebResource[] getResources(String path, boolean useClassLoaderResources) {
122 lookupCount.incrementAndGet();
123
124
125
126
127 CachedResource cacheEntry = resourceCache.get(path);
128
129 if (cacheEntry != null && !cacheEntry.validateResources(useClassLoaderResources)) {
130 removeCacheEntry(path);
131 cacheEntry = null;
132 }
133
134 if (cacheEntry == null) {
135
136 int objectMaxSizeBytes = getObjectMaxSizeBytes();
137 CachedResource newCacheEntry = new CachedResource(this, root, path, getTtl(),
138 objectMaxSizeBytes, useClassLoaderResources);
139
140
141
142 cacheEntry = resourceCache.putIfAbsent(path, newCacheEntry);
143
144 if (cacheEntry == null) {
145
146 cacheEntry = newCacheEntry;
147 cacheEntry.validateResources(useClassLoaderResources);
148
149
150 long delta = cacheEntry.getSize();
151 size.addAndGet(delta);
152
153 if (size.get() > maxSize) {
154
155
156
157
158 long targetSize = maxSize * (100 - TARGET_FREE_PERCENT_GET) / 100;
159 long newSize = evict(targetSize, resourceCache.values().iterator());
160 if (newSize > maxSize) {
161
162
163 removeCacheEntry(path);
164 log.warn(sm.getString("cache.addFail", path));
165 }
166 }
167 } else {
168
169
170 cacheEntry.validateResources(useClassLoaderResources);
171 }
172 } else {
173 hitCount.incrementAndGet();
174 }
175
176 return cacheEntry.getWebResources();
177 }
178
179 protected void backgroundProcess() {
180
181
182
183 TreeSet<CachedResource> orderedResources =
184 new TreeSet<>(new EvictionOrder());
185 orderedResources.addAll(resourceCache.values());
186
187 Iterator<CachedResource> iter = orderedResources.iterator();
188
189 long targetSize =
190 maxSize * (100 - TARGET_FREE_PERCENT_BACKGROUND) / 100;
191 long newSize = evict(targetSize, iter);
192
193 if (newSize > targetSize) {
194 log.info(sm.getString("cache.backgroundEvictFail",
195 Long.valueOf(TARGET_FREE_PERCENT_BACKGROUND),
196 root.getContext().getName(),
197 Long.valueOf(newSize / 1024)));
198 }
199 }
200
201 private boolean noCache(String path) {
202
203
204 if ((path.endsWith(".class") &&
205 (path.startsWith("/WEB-INF/classes/") || path.startsWith("/WEB-INF/lib/")))
206 ||
207 (path.startsWith("/WEB-INF/lib/") && path.endsWith(".jar"))) {
208 return true;
209 }
210 return false;
211 }
212
213 private long evict(long targetSize, Iterator<CachedResource> iter) {
214
215 long now = System.currentTimeMillis();
216
217 long newSize = size.get();
218
219 while (newSize > targetSize && iter.hasNext()) {
220 CachedResource resource = iter.next();
221
222
223 if (resource.getNextCheck() > now) {
224 continue;
225 }
226
227
228 removeCacheEntry(resource.getWebappPath());
229
230 newSize = size.get();
231 }
232
233 return newSize;
234 }
235
236 void removeCacheEntry(String path) {
237
238
239 CachedResource cachedResource = resourceCache.remove(path);
240 if (cachedResource != null) {
241 long delta = cachedResource.getSize();
242 size.addAndGet(-delta);
243 }
244 }
245
246 public long getTtl() {
247 return ttl;
248 }
249
250 public void setTtl(long ttl) {
251 this.ttl = ttl;
252 }
253
254 public long getMaxSize() {
255
256 return maxSize / 1024;
257 }
258
259 public void setMaxSize(long maxSize) {
260
261 this.maxSize = maxSize * 1024;
262 }
263
264 public long getLookupCount() {
265 return lookupCount.get();
266 }
267
268 public long getHitCount() {
269 return hitCount.get();
270 }
271
272 public void setObjectMaxSize(int objectMaxSize) {
273 if (objectMaxSize * 1024L > Integer.MAX_VALUE) {
274 log.warn(sm.getString("cache.objectMaxSizeTooBigBytes", Integer.valueOf(objectMaxSize)));
275 this.objectMaxSize = Integer.MAX_VALUE;
276 }
277
278 this.objectMaxSize = objectMaxSize * 1024;
279 }
280
281 public int getObjectMaxSize() {
282
283 return objectMaxSize / 1024;
284 }
285
286 public int getObjectMaxSizeBytes() {
287 return objectMaxSize;
288 }
289
290 void enforceObjectMaxSizeLimit() {
291 long limit = maxSize / OBJECT_MAX_SIZE_FACTOR;
292 if (limit > Integer.MAX_VALUE) {
293 return;
294 }
295 if (objectMaxSize > limit) {
296 log.warn(sm.getString("cache.objectMaxSizeTooBig",
297 Integer.valueOf(objectMaxSize / 1024), Integer.valueOf((int)limit / 1024)));
298 objectMaxSize = (int) limit;
299 }
300 }
301
302 public void clear() {
303 resourceCache.clear();
304 size.set(0);
305 }
306
307 public long getSize() {
308 return size.get() / 1024;
309 }
310
311 private static class EvictionOrder implements Comparator<CachedResource> {
312
313 @Override
314 public int compare(CachedResource cr1, CachedResource cr2) {
315 long nc1 = cr1.getNextCheck();
316 long nc2 = cr2.getNextCheck();
317
318
319
320 if (nc1 == nc2) {
321 return 0;
322 } else if (nc1 > nc2) {
323 return -1;
324 } else {
325 return 1;
326 }
327 }
328 }
329 }
330