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