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