1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.server;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.ReadableByteChannel;
26 import java.util.Comparator;
27 import java.util.SortedSet;
28 import java.util.TreeSet;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31 import java.util.concurrent.atomic.AtomicInteger;
32 import java.util.concurrent.atomic.AtomicReference;
33
34 import org.eclipse.jetty.http.DateGenerator;
35 import org.eclipse.jetty.http.HttpContent;
36 import org.eclipse.jetty.http.MimeTypes;
37 import org.eclipse.jetty.util.BufferUtil;
38 import org.eclipse.jetty.util.log.Log;
39 import org.eclipse.jetty.util.log.Logger;
40 import org.eclipse.jetty.util.resource.Resource;
41 import org.eclipse.jetty.util.resource.ResourceFactory;
42
43
44
45
46
47
48 public class ResourceCache
49 {
50 private static final Logger LOG = Log.getLogger(ResourceCache.class);
51
52 private final ConcurrentMap<String,Content> _cache;
53 private final AtomicInteger _cachedSize;
54 private final AtomicInteger _cachedFiles;
55 private final ResourceFactory _factory;
56 private final ResourceCache _parent;
57 private final MimeTypes _mimeTypes;
58 private final boolean _etagSupported;
59 private final boolean _useFileMappedBuffer;
60
61 private int _maxCachedFileSize =4*1024*1024;
62 private int _maxCachedFiles=2048;
63 private int _maxCacheSize =32*1024*1024;
64
65
66
67
68
69 public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
70 {
71 _factory = factory;
72 _cache=new ConcurrentHashMap<String,Content>();
73 _cachedSize=new AtomicInteger();
74 _cachedFiles=new AtomicInteger();
75 _mimeTypes=mimeTypes;
76 _parent=parent;
77 _useFileMappedBuffer=useFileMappedBuffer;
78 _etagSupported=etags;
79 }
80
81
82 public int getCachedSize()
83 {
84 return _cachedSize.get();
85 }
86
87
88 public int getCachedFiles()
89 {
90 return _cachedFiles.get();
91 }
92
93
94 public int getMaxCachedFileSize()
95 {
96 return _maxCachedFileSize;
97 }
98
99
100 public void setMaxCachedFileSize(int maxCachedFileSize)
101 {
102 _maxCachedFileSize = maxCachedFileSize;
103 shrinkCache();
104 }
105
106
107 public int getMaxCacheSize()
108 {
109 return _maxCacheSize;
110 }
111
112
113 public void setMaxCacheSize(int maxCacheSize)
114 {
115 _maxCacheSize = maxCacheSize;
116 shrinkCache();
117 }
118
119
120
121
122
123 public int getMaxCachedFiles()
124 {
125 return _maxCachedFiles;
126 }
127
128
129
130
131
132 public void setMaxCachedFiles(int maxCachedFiles)
133 {
134 _maxCachedFiles = maxCachedFiles;
135 shrinkCache();
136 }
137
138
139 public boolean isUseFileMappedBuffer()
140 {
141 return _useFileMappedBuffer;
142 }
143
144
145 public void flushCache()
146 {
147 if (_cache!=null)
148 {
149 while (_cache.size()>0)
150 {
151 for (String path : _cache.keySet())
152 {
153 Content content = _cache.remove(path);
154 if (content!=null)
155 content.invalidate();
156 }
157 }
158 }
159 }
160
161
162
163
164
165
166
167
168
169
170
171
172 public HttpContent lookup(String pathInContext)
173 throws IOException
174 {
175
176 Content content =_cache.get(pathInContext);
177 if (content!=null && (content).isValid())
178 return content;
179
180
181 Resource resource=_factory.getResource(pathInContext);
182 HttpContent loaded = load(pathInContext,resource);
183 if (loaded!=null)
184 return loaded;
185
186
187 if (_parent!=null)
188 {
189 HttpContent httpContent=_parent.lookup(pathInContext);
190 if (httpContent!=null)
191 return httpContent;
192 }
193
194 return null;
195 }
196
197
198
199
200
201
202 protected boolean isCacheable(Resource resource)
203 {
204 long len = resource.length();
205
206
207 return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
208 }
209
210
211 private HttpContent load(String pathInContext, Resource resource)
212 throws IOException
213 {
214 Content content=null;
215
216 if (resource==null || !resource.exists())
217 return null;
218
219
220 if (!resource.isDirectory() && isCacheable(resource))
221 {
222
223 content = new Content(pathInContext,resource);
224
225
226 shrinkCache();
227
228
229 Content added = _cache.putIfAbsent(pathInContext,content);
230 if (added!=null)
231 {
232 content.invalidate();
233 content=added;
234 }
235
236 return content;
237 }
238
239 return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
240
241 }
242
243
244 private void shrinkCache()
245 {
246
247 while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
248 {
249
250 SortedSet<Content> sorted= new TreeSet<Content>(
251 new Comparator<Content>()
252 {
253 public int compare(Content c1, Content c2)
254 {
255 if (c1._lastAccessed<c2._lastAccessed)
256 return -1;
257
258 if (c1._lastAccessed>c2._lastAccessed)
259 return 1;
260
261 if (c1._length<c2._length)
262 return -1;
263
264 return c1._key.compareTo(c2._key);
265 }
266 });
267 for (Content content : _cache.values())
268 sorted.add(content);
269
270
271 for (Content content : sorted)
272 {
273 if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
274 break;
275 if (content==_cache.remove(content.getKey()))
276 content.invalidate();
277 }
278 }
279 }
280
281
282 protected ByteBuffer getIndirectBuffer(Resource resource)
283 {
284 try
285 {
286 return BufferUtil.toBuffer(resource,true);
287 }
288 catch(IOException|IllegalArgumentException e)
289 {
290 LOG.warn(e);
291 return null;
292 }
293 }
294
295
296 protected ByteBuffer getDirectBuffer(Resource resource)
297 {
298 try
299 {
300 if (_useFileMappedBuffer && resource.getFile()!=null)
301 return BufferUtil.toMappedBuffer(resource.getFile());
302
303 return BufferUtil.toBuffer(resource,true);
304 }
305 catch(IOException|IllegalArgumentException e)
306 {
307 LOG.warn(e);
308 return null;
309 }
310 }
311
312
313 @Override
314 public String toString()
315 {
316 return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
317 }
318
319
320
321
322
323 public class Content implements HttpContent
324 {
325 final Resource _resource;
326 final int _length;
327 final String _key;
328 final long _lastModified;
329 final ByteBuffer _lastModifiedBytes;
330 final ByteBuffer _contentType;
331 final String _etag;
332
333 volatile long _lastAccessed;
334 AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
335 AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
336
337
338 Content(String pathInContext,Resource resource)
339 {
340 _key=pathInContext;
341 _resource=resource;
342
343 String mimeType = _mimeTypes.getMimeByExtension(_resource.toString());
344 _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType));
345 boolean exists=resource.exists();
346 _lastModified=exists?resource.lastModified():-1;
347 _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified));
348
349 _length=exists?(int)resource.length():0;
350 _cachedSize.addAndGet(_length);
351 _cachedFiles.incrementAndGet();
352 _lastAccessed=System.currentTimeMillis();
353
354 _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null;
355 }
356
357
358
359 public String getKey()
360 {
361 return _key;
362 }
363
364
365 public boolean isCached()
366 {
367 return _key!=null;
368 }
369
370
371 public boolean isMiss()
372 {
373 return false;
374 }
375
376
377 @Override
378 public Resource getResource()
379 {
380 return _resource;
381 }
382
383
384 @Override
385 public String getETag()
386 {
387 return _etag;
388 }
389
390
391 boolean isValid()
392 {
393 if (_lastModified==_resource.lastModified() && _length==_resource.length())
394 {
395 _lastAccessed=System.currentTimeMillis();
396 return true;
397 }
398
399 if (this==_cache.remove(_key))
400 invalidate();
401 return false;
402 }
403
404
405 protected void invalidate()
406 {
407
408 _cachedSize.addAndGet(-_length);
409 _cachedFiles.decrementAndGet();
410 _resource.close();
411 }
412
413
414 @Override
415 public String getLastModified()
416 {
417 return BufferUtil.toString(_lastModifiedBytes);
418 }
419
420
421 @Override
422 public String getContentType()
423 {
424 return BufferUtil.toString(_contentType);
425 }
426
427
428 @Override
429 public void release()
430 {
431
432 }
433
434
435 @Override
436 public ByteBuffer getIndirectBuffer()
437 {
438 ByteBuffer buffer = _indirectBuffer.get();
439 if (buffer==null)
440 {
441 ByteBuffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
442
443 if (buffer2==null)
444 LOG.warn("Could not load "+this);
445 else if (_indirectBuffer.compareAndSet(null,buffer2))
446 buffer=buffer2;
447 else
448 buffer=_indirectBuffer.get();
449 }
450 if (buffer==null)
451 return null;
452 return buffer.slice();
453 }
454
455
456
457 @Override
458 public ByteBuffer getDirectBuffer()
459 {
460 ByteBuffer buffer = _directBuffer.get();
461 if (buffer==null)
462 {
463 ByteBuffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
464
465 if (buffer2==null)
466 LOG.warn("Could not load "+this);
467 else if (_directBuffer.compareAndSet(null,buffer2))
468 buffer=buffer2;
469 else
470 buffer=_directBuffer.get();
471 }
472 if (buffer==null)
473 return null;
474 return buffer.asReadOnlyBuffer();
475 }
476
477
478 @Override
479 public long getContentLength()
480 {
481 return _length;
482 }
483
484
485 @Override
486 public InputStream getInputStream() throws IOException
487 {
488 ByteBuffer indirect = getIndirectBuffer();
489 if (indirect!=null && indirect.hasArray())
490 return new ByteArrayInputStream(indirect.array(),indirect.arrayOffset()+indirect.position(),indirect.remaining());
491
492 return _resource.getInputStream();
493 }
494
495
496 @Override
497 public ReadableByteChannel getReadableByteChannel() throws IOException
498 {
499 return _resource.getReadableByteChannel();
500 }
501
502
503
504 @Override
505 public String toString()
506 {
507 return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s}",hashCode(),_resource,_resource.exists(),BufferUtil.toString(_lastModifiedBytes),_contentType);
508 }
509 }
510 }