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