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 Buffer buffer = new IndirectNIOBuffer(len);
292 InputStream is = resource.getInputStream();
293 buffer.readFrom(is,len);
294 is.close();
295 return buffer;
296 }
297 catch(IOException e)
298 {
299 Log.warn(e);
300 return null;
301 }
302 }
303
304
305 protected Buffer getDirectBuffer(Resource resource)
306 {
307 try
308 {
309 if (_useFileMappedBuffer && resource.getFile()!=null)
310 return new DirectNIOBuffer(resource.getFile());
311
312 int len=(int)resource.length();
313 Buffer buffer = new DirectNIOBuffer(len);
314 InputStream is = resource.getInputStream();
315 buffer.readFrom(is,len);
316 is.close();
317 return buffer;
318 }
319 catch(IOException e)
320 {
321 Log.warn(e);
322 return null;
323 }
324 }
325
326
327 public String toString()
328 {
329 return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
330 }
331
332
333
334
335
336 public class Content implements HttpContent
337 {
338 final Resource _resource;
339 final int _length;
340 final String _key;
341 final long _lastModified;
342 final Buffer _lastModifiedBytes;
343 final Buffer _contentType;
344
345 volatile long _lastAccessed;
346 AtomicReference<Buffer> _indirectBuffer=new AtomicReference<Buffer>();
347 AtomicReference<Buffer> _directBuffer=new AtomicReference<Buffer>();
348
349
350 Content(String pathInContext,Resource resource)
351 {
352 _key=pathInContext;
353 _resource=resource;
354
355 _contentType=_mimeTypes.getMimeByExtension(_resource.toString());
356 boolean exists=resource.exists();
357 _lastModified=exists?resource.lastModified():-1;
358 _lastModifiedBytes=_lastModified<0?null:new ByteArrayBuffer(HttpFields.formatDate(_lastModified));
359
360 _length=exists?(int)resource.length():0;
361 _cachedSize.addAndGet(_length);
362 _cachedFiles.incrementAndGet();
363 _lastAccessed=System.currentTimeMillis();
364 }
365
366
367
368 public String getKey()
369 {
370 return _key;
371 }
372
373
374 public boolean isCached()
375 {
376 return _key!=null;
377 }
378
379
380 public boolean isMiss()
381 {
382 return false;
383 }
384
385
386 public Resource getResource()
387 {
388 return _resource;
389 }
390
391
392 boolean isValid()
393 {
394 if (_lastModified==_resource.lastModified())
395 {
396 _lastAccessed=System.currentTimeMillis();
397 return true;
398 }
399
400 if (this==_cache.remove(_key))
401 invalidate();
402 return false;
403 }
404
405
406 protected void invalidate()
407 {
408
409 _cachedSize.addAndGet(-_length);
410 _cachedFiles.decrementAndGet();
411 _resource.release();
412 }
413
414
415 public Buffer getLastModified()
416 {
417 return _lastModifiedBytes;
418 }
419
420
421 public Buffer getContentType()
422 {
423 return _contentType;
424 }
425
426
427 public void release()
428 {
429
430 }
431
432
433 public Buffer getIndirectBuffer()
434 {
435 Buffer buffer = _indirectBuffer.get();
436 if (buffer==null)
437 {
438 synchronized (_indirectBuffer)
439 {
440 if (_indirectBuffer.get()==null)
441 {
442 buffer=ResourceCache.this.getIndirectBuffer(_resource);
443 _indirectBuffer.set(buffer);
444 }
445 }
446 }
447 if (buffer==null)
448 return null;
449 return new View(buffer);
450 }
451
452
453
454 public Buffer getDirectBuffer()
455 {
456 Buffer buffer = _directBuffer.get();
457 if (buffer==null)
458 {
459 synchronized (_directBuffer)
460 {
461 if (_directBuffer.get()==null)
462 {
463 buffer=ResourceCache.this.getDirectBuffer(_resource);
464 _directBuffer.set(buffer);
465 }
466 }
467 }
468 if (buffer==null)
469 return null;
470
471 return new View(buffer);
472 }
473
474
475 public long getContentLength()
476 {
477 return _length;
478 }
479
480
481 public InputStream getInputStream() throws IOException
482 {
483 Buffer indirect = getIndirectBuffer();
484 if (indirect!=null && indirect.array()!=null)
485 return new ByteArrayInputStream(indirect.array(),indirect.getIndex(),indirect.length());
486
487 return _resource.getInputStream();
488 }
489
490
491 @Override
492 public String toString()
493 {
494 return "{"+_resource+","+_contentType+","+_lastModifiedBytes+"}";
495 }
496 }
497 }