1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jetty.server;
15
16 import java.io.ByteArrayInputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.util.Comparator;
20 import java.util.SortedSet;
21 import java.util.TreeSet;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.ConcurrentMap;
24 import java.util.concurrent.atomic.AtomicInteger;
25 import java.util.concurrent.atomic.AtomicReference;
26
27 import org.eclipse.jetty.http.HttpContent;
28 import org.eclipse.jetty.http.HttpFields;
29 import org.eclipse.jetty.http.MimeTypes;
30 import org.eclipse.jetty.io.Buffer;
31 import org.eclipse.jetty.io.ByteArrayBuffer;
32 import org.eclipse.jetty.io.View;
33 import org.eclipse.jetty.io.nio.DirectNIOBuffer;
34 import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
35 import org.eclipse.jetty.util.log.Log;
36 import org.eclipse.jetty.util.log.Logger;
37 import org.eclipse.jetty.util.resource.Resource;
38 import org.eclipse.jetty.util.resource.ResourceFactory;
39
40
41
42
43
44
45 public class ResourceCache
46 {
47 private static final Logger LOG = Log.getLogger(ResourceCache.class);
48
49 private final ConcurrentMap<String,Content> _cache;
50 private final AtomicInteger _cachedSize;
51 private final AtomicInteger _cachedFiles;
52 private final ResourceFactory _factory;
53 private final ResourceCache _parent;
54 private final MimeTypes _mimeTypes;
55
56 private boolean _useFileMappedBuffer=true;
57 private int _maxCachedFileSize =4*1024*1024;
58 private int _maxCachedFiles=2048;
59 private int _maxCacheSize =32*1024*1024;
60
61
62 public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer)
63 {
64 this(parent,factory,mimeTypes);
65 setUseFileMappedBuffer(useFileMappedBuffer);
66 }
67
68
69
70
71
72 public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes)
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 }
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());
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 Buffer 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 Buffer buffer = new IndirectNIOBuffer(len);
300 InputStream is = resource.getInputStream();
301 buffer.readFrom(is,len);
302 is.close();
303 return buffer;
304 }
305 catch(IOException e)
306 {
307 LOG.warn(e);
308 return null;
309 }
310 }
311
312
313 protected Buffer getDirectBuffer(Resource resource)
314 {
315 try
316 {
317 if (_useFileMappedBuffer && resource.getFile()!=null)
318 return new DirectNIOBuffer(resource.getFile());
319
320 int len=(int)resource.length();
321 if (len<0)
322 {
323 LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
324 return null;
325 }
326 Buffer buffer = new DirectNIOBuffer(len);
327 InputStream is = resource.getInputStream();
328 buffer.readFrom(is,len);
329 is.close();
330 return buffer;
331 }
332 catch(IOException e)
333 {
334 LOG.warn(e);
335 return null;
336 }
337 }
338
339
340 public String toString()
341 {
342 return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
343 }
344
345
346
347
348
349 public class Content implements HttpContent
350 {
351 final Resource _resource;
352 final int _length;
353 final String _key;
354 final long _lastModified;
355 final Buffer _lastModifiedBytes;
356 final Buffer _contentType;
357
358 volatile long _lastAccessed;
359 AtomicReference<Buffer> _indirectBuffer=new AtomicReference<Buffer>();
360 AtomicReference<Buffer> _directBuffer=new AtomicReference<Buffer>();
361
362
363 Content(String pathInContext,Resource resource)
364 {
365 _key=pathInContext;
366 _resource=resource;
367
368 _contentType=_mimeTypes.getMimeByExtension(_resource.toString());
369 boolean exists=resource.exists();
370 _lastModified=exists?resource.lastModified():-1;
371 _lastModifiedBytes=_lastModified<0?null:new ByteArrayBuffer(HttpFields.formatDate(_lastModified));
372
373 _length=exists?(int)resource.length():0;
374 _cachedSize.addAndGet(_length);
375 _cachedFiles.incrementAndGet();
376 _lastAccessed=System.currentTimeMillis();
377 }
378
379
380
381 public String getKey()
382 {
383 return _key;
384 }
385
386
387 public boolean isCached()
388 {
389 return _key!=null;
390 }
391
392
393 public boolean isMiss()
394 {
395 return false;
396 }
397
398
399 public Resource getResource()
400 {
401 return _resource;
402 }
403
404
405 boolean isValid()
406 {
407 if (_lastModified==_resource.lastModified())
408 {
409 _lastAccessed=System.currentTimeMillis();
410 return true;
411 }
412
413 if (this==_cache.remove(_key))
414 invalidate();
415 return false;
416 }
417
418
419 protected void invalidate()
420 {
421
422 _cachedSize.addAndGet(-_length);
423 _cachedFiles.decrementAndGet();
424 _resource.release();
425 }
426
427
428 public Buffer getLastModified()
429 {
430 return _lastModifiedBytes;
431 }
432
433
434 public Buffer getContentType()
435 {
436 return _contentType;
437 }
438
439
440 public void release()
441 {
442
443 }
444
445
446 public Buffer getIndirectBuffer()
447 {
448 Buffer buffer = _indirectBuffer.get();
449 if (buffer==null)
450 {
451 Buffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
452
453 if (buffer2==null)
454 LOG.warn("Could not load "+this);
455 else if (_indirectBuffer.compareAndSet(null,buffer2))
456 buffer=buffer2;
457 else
458 buffer=_indirectBuffer.get();
459 }
460 if (buffer==null)
461 return null;
462 return new View(buffer);
463 }
464
465
466
467 public Buffer getDirectBuffer()
468 {
469 Buffer buffer = _directBuffer.get();
470 if (buffer==null)
471 {
472 Buffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
473
474 if (buffer2==null)
475 LOG.warn("Could not load "+this);
476 else if (_directBuffer.compareAndSet(null,buffer2))
477 buffer=buffer2;
478 else
479 buffer=_directBuffer.get();
480 }
481 if (buffer==null)
482 return null;
483
484 return new View(buffer);
485 }
486
487
488 public long getContentLength()
489 {
490 return _length;
491 }
492
493
494 public InputStream getInputStream() throws IOException
495 {
496 Buffer indirect = getIndirectBuffer();
497 if (indirect!=null && indirect.array()!=null)
498 return new ByteArrayInputStream(indirect.array(),indirect.getIndex(),indirect.length());
499
500 return _resource.getInputStream();
501 }
502
503
504 @Override
505 public String toString()
506 {
507 return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
508 }
509 }
510 }