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
61 private boolean _useFileMappedBuffer=true;
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 setUseFileMappedBuffer(boolean useFileMappedBuffer)
147 {
148 _useFileMappedBuffer = useFileMappedBuffer;
149 }
150
151
152 public void flushCache()
153 {
154 if (_cache!=null)
155 {
156 while (_cache.size()>0)
157 {
158 for (String path : _cache.keySet())
159 {
160 Content content = _cache.remove(path);
161 if (content!=null)
162 content.invalidate();
163 }
164 }
165 }
166 }
167
168
169
170
171
172
173
174
175
176
177
178
179 public HttpContent lookup(String pathInContext)
180 throws IOException
181 {
182
183 Content content =_cache.get(pathInContext);
184 if (content!=null && (content).isValid())
185 return content;
186
187
188 Resource resource=_factory.getResource(pathInContext);
189 HttpContent loaded = load(pathInContext,resource);
190 if (loaded!=null)
191 return loaded;
192
193
194 if (_parent!=null)
195 {
196 HttpContent httpContent=_parent.lookup(pathInContext);
197 if (httpContent!=null)
198 return httpContent;
199 }
200
201 return null;
202 }
203
204
205
206
207
208
209 protected boolean isCacheable(Resource resource)
210 {
211 long len = resource.length();
212
213
214 return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
215 }
216
217
218 private HttpContent load(String pathInContext, Resource resource)
219 throws IOException
220 {
221 Content content=null;
222
223 if (resource==null || !resource.exists())
224 return null;
225
226
227 if (!resource.isDirectory() && isCacheable(resource))
228 {
229
230 content = new Content(pathInContext,resource);
231
232
233 shrinkCache();
234
235
236 Content added = _cache.putIfAbsent(pathInContext,content);
237 if (added!=null)
238 {
239 content.invalidate();
240 content=added;
241 }
242
243 return content;
244 }
245
246 return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
247
248 }
249
250
251 private void shrinkCache()
252 {
253
254 while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
255 {
256
257 SortedSet<Content> sorted= new TreeSet<Content>(
258 new Comparator<Content>()
259 {
260 public int compare(Content c1, Content c2)
261 {
262 if (c1._lastAccessed<c2._lastAccessed)
263 return -1;
264
265 if (c1._lastAccessed>c2._lastAccessed)
266 return 1;
267
268 if (c1._length<c2._length)
269 return -1;
270
271 return c1._key.compareTo(c2._key);
272 }
273 });
274 for (Content content : _cache.values())
275 sorted.add(content);
276
277
278 for (Content content : sorted)
279 {
280 if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
281 break;
282 if (content==_cache.remove(content.getKey()))
283 content.invalidate();
284 }
285 }
286 }
287
288
289 protected ByteBuffer getIndirectBuffer(Resource resource)
290 {
291 try
292 {
293 int len=(int)resource.length();
294 if (len<0)
295 {
296 LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
297 return null;
298 }
299 ByteBuffer buffer = BufferUtil.allocate(len);
300 int pos=BufferUtil.flipToFill(buffer);
301 if (resource.getFile()!=null)
302 BufferUtil.readFrom(resource.getFile(),buffer);
303 else
304 {
305 InputStream is = resource.getInputStream();
306 BufferUtil.readFrom(is,len,buffer);
307 is.close();
308 }
309 BufferUtil.flipToFlush(buffer,pos);
310 return buffer;
311 }
312 catch(IOException e)
313 {
314 LOG.warn(e);
315 return null;
316 }
317 }
318
319
320 protected ByteBuffer getDirectBuffer(Resource resource)
321 {
322 try
323 {
324 if (_useFileMappedBuffer && resource.getFile()!=null)
325 return BufferUtil.toBuffer(resource.getFile());
326
327 int len=(int)resource.length();
328 if (len<0)
329 {
330 LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
331 return null;
332 }
333 ByteBuffer buffer = BufferUtil.allocateDirect(len);
334
335 int pos=BufferUtil.flipToFill(buffer);
336 if (resource.getFile()!=null)
337 BufferUtil.readFrom(resource.getFile(),buffer);
338 else
339 {
340 InputStream is = resource.getInputStream();
341 BufferUtil.readFrom(is,len,buffer);
342 is.close();
343 }
344 BufferUtil.flipToFlush(buffer,pos);
345
346 return buffer;
347 }
348 catch(IOException e)
349 {
350 LOG.warn(e);
351 return null;
352 }
353 }
354
355
356 @Override
357 public String toString()
358 {
359 return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
360 }
361
362
363
364
365
366 public class Content implements HttpContent
367 {
368 final Resource _resource;
369 final int _length;
370 final String _key;
371 final long _lastModified;
372 final ByteBuffer _lastModifiedBytes;
373 final ByteBuffer _contentType;
374 final String _etag;
375
376 volatile long _lastAccessed;
377 AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
378 AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
379
380
381 Content(String pathInContext,Resource resource)
382 {
383 _key=pathInContext;
384 _resource=resource;
385
386 String mimeType = _mimeTypes.getMimeByExtension(_resource.toString());
387 _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType));
388 boolean exists=resource.exists();
389 _lastModified=exists?resource.lastModified():-1;
390 _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(HttpFields.formatDate(_lastModified));
391
392 _length=exists?(int)resource.length():0;
393 _cachedSize.addAndGet(_length);
394 _cachedFiles.incrementAndGet();
395 _lastAccessed=System.currentTimeMillis();
396
397 _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null;
398 }
399
400
401
402 public String getKey()
403 {
404 return _key;
405 }
406
407
408 public boolean isCached()
409 {
410 return _key!=null;
411 }
412
413
414 public boolean isMiss()
415 {
416 return false;
417 }
418
419
420 @Override
421 public Resource getResource()
422 {
423 return _resource;
424 }
425
426
427 @Override
428 public String getETag()
429 {
430 return _etag;
431 }
432
433
434 boolean isValid()
435 {
436 if (_lastModified==_resource.lastModified() && _length==_resource.length())
437 {
438 _lastAccessed=System.currentTimeMillis();
439 return true;
440 }
441
442 if (this==_cache.remove(_key))
443 invalidate();
444 return false;
445 }
446
447
448 protected void invalidate()
449 {
450
451 _cachedSize.addAndGet(-_length);
452 _cachedFiles.decrementAndGet();
453 _resource.release();
454 }
455
456
457 @Override
458 public String getLastModified()
459 {
460 return BufferUtil.toString(_lastModifiedBytes);
461 }
462
463
464 @Override
465 public String getContentType()
466 {
467 return BufferUtil.toString(_contentType);
468 }
469
470
471 @Override
472 public void release()
473 {
474
475 }
476
477
478 @Override
479 public ByteBuffer getIndirectBuffer()
480 {
481 ByteBuffer buffer = _indirectBuffer.get();
482 if (buffer==null)
483 {
484 ByteBuffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
485
486 if (buffer2==null)
487 LOG.warn("Could not load "+this);
488 else if (_indirectBuffer.compareAndSet(null,buffer2))
489 buffer=buffer2;
490 else
491 buffer=_indirectBuffer.get();
492 }
493 if (buffer==null)
494 return null;
495 return buffer.asReadOnlyBuffer();
496 }
497
498
499
500 @Override
501 public ByteBuffer getDirectBuffer()
502 {
503 ByteBuffer buffer = _directBuffer.get();
504 if (buffer==null)
505 {
506 ByteBuffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
507
508 if (buffer2==null)
509 LOG.warn("Could not load "+this);
510 else if (_directBuffer.compareAndSet(null,buffer2))
511 buffer=buffer2;
512 else
513 buffer=_directBuffer.get();
514 }
515 if (buffer==null)
516 return null;
517 return buffer.asReadOnlyBuffer();
518 }
519
520
521 @Override
522 public long getContentLength()
523 {
524 return _length;
525 }
526
527
528 @Override
529 public InputStream getInputStream() throws IOException
530 {
531 ByteBuffer indirect = getIndirectBuffer();
532 if (indirect!=null && indirect.hasArray())
533 return new ByteArrayInputStream(indirect.array(),indirect.arrayOffset()+indirect.position(),indirect.remaining());
534
535 return _resource.getInputStream();
536 }
537
538
539 @Override
540 public ReadableByteChannel getReadableByteChannel() throws IOException
541 {
542 return _resource.getReadableByteChannel();
543 }
544
545
546
547 @Override
548 public String toString()
549 {
550 return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
551 }
552 }
553 }