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