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