1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jetty.server;
15
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.Map;
21
22 import org.eclipse.jetty.http.HttpContent;
23 import org.eclipse.jetty.http.HttpFields;
24 import org.eclipse.jetty.http.MimeTypes;
25 import org.eclipse.jetty.io.Buffer;
26 import org.eclipse.jetty.io.ByteArrayBuffer;
27 import org.eclipse.jetty.io.View;
28 import org.eclipse.jetty.util.component.AbstractLifeCycle;
29 import org.eclipse.jetty.util.resource.Resource;
30 import org.eclipse.jetty.util.resource.ResourceFactory;
31
32
33
34
35
36
37 public class ResourceCache extends AbstractLifeCycle
38 {
39 protected final Map _cache;
40 private final MimeTypes _mimeTypes;
41 private int _maxCachedFileSize =1024*1024;
42 private int _maxCachedFiles=2048;
43 private int _maxCacheSize =16*1024*1024;
44
45 protected int _cachedSize;
46 protected int _cachedFiles;
47 protected Content _mostRecentlyUsed;
48 protected Content _leastRecentlyUsed;
49
50
51
52
53 public ResourceCache(MimeTypes mimeTypes)
54 {
55 _cache=new HashMap();
56 _mimeTypes=mimeTypes;
57 }
58
59
60 public int getCachedSize()
61 {
62 return _cachedSize;
63 }
64
65
66 public int getCachedFiles()
67 {
68 return _cachedFiles;
69 }
70
71
72
73 public int getMaxCachedFileSize()
74 {
75 return _maxCachedFileSize;
76 }
77
78
79 public void setMaxCachedFileSize(int maxCachedFileSize)
80 {
81 _maxCachedFileSize = maxCachedFileSize;
82 flushCache();
83 }
84
85
86 public int getMaxCacheSize()
87 {
88 return _maxCacheSize;
89 }
90
91
92 public void setMaxCacheSize(int maxCacheSize)
93 {
94 _maxCacheSize = maxCacheSize;
95 flushCache();
96 }
97
98
99
100
101
102 public int getMaxCachedFiles()
103 {
104 return _maxCachedFiles;
105 }
106
107
108
109
110
111 public void setMaxCachedFiles(int maxCachedFiles)
112 {
113 _maxCachedFiles = maxCachedFiles;
114 }
115
116
117 public void flushCache()
118 {
119 if (_cache!=null)
120 {
121 synchronized(this)
122 {
123 ArrayList<Content> values=new ArrayList<Content>(_cache.values());
124 for (Content content : values)
125 content.invalidate();
126
127 _cache.clear();
128
129 _cachedSize=0;
130 _cachedFiles=0;
131 _mostRecentlyUsed=null;
132 _leastRecentlyUsed=null;
133 }
134 }
135 }
136
137
138
139
140
141
142
143
144
145
146 public Content lookup(String pathInContext, ResourceFactory factory)
147 throws IOException
148 {
149 Content content=null;
150
151
152 synchronized(_cache)
153 {
154
155 content = (Content)_cache.get(pathInContext);
156
157 if (content!=null && content.isValid())
158 {
159 return content;
160 }
161 }
162 Resource resource=factory.getResource(pathInContext);
163 return load(pathInContext,resource);
164 }
165
166
167 public Content lookup(String pathInContext, Resource resource)
168 throws IOException
169 {
170 Content content=null;
171
172
173 synchronized(_cache)
174 {
175
176 content = (Content)_cache.get(pathInContext);
177
178 if (content!=null && content.isValid())
179 {
180 return content;
181 }
182 }
183 return load(pathInContext,resource);
184 }
185
186
187 private Content load(String pathInContext, Resource resource)
188 throws IOException
189 {
190 Content content=null;
191 if (resource!=null && resource.exists() && !resource.isDirectory())
192 {
193 long len = resource.length();
194 if (len>0 && len<_maxCachedFileSize && len<_maxCacheSize)
195 {
196 int must_be_smaller_than=_maxCacheSize-(int)len;
197
198 synchronized(_cache)
199 {
200
201
202 while(_leastRecentlyUsed!=null && (_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles)))
203 _leastRecentlyUsed.invalidate();
204
205 if(_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles))
206 return null;
207 }
208
209 content = new Content(resource);
210 fill(content);
211
212 synchronized(_cache)
213 {
214
215 Content content2 =(Content)_cache.get(pathInContext);
216 if (content2!=null)
217 {
218 content.release();
219 return content2;
220 }
221
222 while(_leastRecentlyUsed!=null && (_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles)))
223 _leastRecentlyUsed.invalidate();
224
225 if(_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles))
226 return null;
227
228 content.cache(pathInContext);
229
230 return content;
231 }
232 }
233 }
234
235 return null;
236 }
237
238
239
240
241
242
243
244 public void miss(String pathInContext, Resource resource)
245 throws IOException
246 {
247 synchronized(_cache)
248 {
249 while(_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles && _leastRecentlyUsed!=null)
250 _leastRecentlyUsed.invalidate();
251 if (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles)
252 return;
253
254
255 Miss miss = new Miss(resource);
256 Content content2 =(Content)_cache.get(pathInContext);
257 if (content2!=null)
258 {
259 miss.release();
260 return;
261 }
262
263 miss.cache(pathInContext);
264 }
265 }
266
267
268 @Override
269 public synchronized void doStart()
270 throws Exception
271 {
272 _cache.clear();
273 _cachedSize=0;
274 _cachedFiles=0;
275 }
276
277
278
279
280 @Override
281 public void doStop()
282 throws InterruptedException
283 {
284 flushCache();
285 }
286
287
288 protected void fill(Content content)
289 throws IOException
290 {
291 try
292 {
293 InputStream in = content.getResource().getInputStream();
294 int len=(int)content.getResource().length();
295 Buffer buffer = new ByteArrayBuffer(len);
296 buffer.readFrom(in,len);
297 in.close();
298 content.setBuffer(buffer);
299 }
300 finally
301 {
302 content.getResource().release();
303 }
304 }
305
306
307
308
309
310 public class Content implements HttpContent
311 {
312 final Resource _resource;
313 final long _lastModified;
314 boolean _locked;
315 String _key;
316 Content _prev;
317 Content _next;
318
319 Buffer _lastModifiedBytes;
320 Buffer _contentType;
321 Buffer _buffer;
322
323
324 Content(Resource resource)
325 {
326 _resource=resource;
327
328 _next=this;
329 _prev=this;
330 _contentType=_mimeTypes.getMimeByExtension(_resource.toString());
331
332 _lastModified=resource.lastModified();
333 }
334
335
336
337
338
339
340 public boolean isLocked()
341 {
342 return _locked;
343 }
344
345
346
347
348
349
350 public void setLocked(boolean locked)
351 {
352 synchronized (_cache)
353 {
354 if (_locked && !locked)
355 {
356 _locked = locked;
357 _next=_mostRecentlyUsed;
358 _mostRecentlyUsed=this;
359 if (_next!=null)
360 _next._prev=this;
361 _prev=null;
362 if (_leastRecentlyUsed==null)
363 _leastRecentlyUsed=this;
364 }
365 else if (!_locked && locked)
366 {
367 if (_prev!=null)
368 _prev._next=_next;
369 if (_next!=null)
370 _next._prev=_prev;
371 _next=_prev=null;
372 }
373 else
374 _locked = locked;
375 }
376 }
377
378
379
380 void cache(String pathInContext)
381 {
382 _key=pathInContext;
383
384 if (!_locked)
385 {
386 _next=_mostRecentlyUsed;
387 _mostRecentlyUsed=this;
388 if (_next!=null)
389 _next._prev=this;
390 _prev=null;
391 if (_leastRecentlyUsed==null)
392 _leastRecentlyUsed=this;
393 }
394 _cache.put(_key,this);
395 if (_buffer!=null)
396 _cachedSize+=_buffer.length();
397 _cachedFiles++;
398 if (_lastModified!=-1)
399 _lastModifiedBytes=new ByteArrayBuffer(HttpFields.formatDate(_lastModified));
400 }
401
402
403 public String getKey()
404 {
405 return _key;
406 }
407
408
409 public boolean isCached()
410 {
411 return _key!=null;
412 }
413
414
415 public Resource getResource()
416 {
417 return _resource;
418 }
419
420
421 boolean isValid()
422 {
423 if (_lastModified==_resource.lastModified())
424 {
425 if (!_locked && _mostRecentlyUsed!=this)
426 {
427 Content tp = _prev;
428 Content tn = _next;
429
430 _next=_mostRecentlyUsed;
431 _mostRecentlyUsed=this;
432 if (_next!=null)
433 _next._prev=this;
434 _prev=null;
435
436 if (tp!=null)
437 tp._next=tn;
438 if (tn!=null)
439 tn._prev=tp;
440
441 if (_leastRecentlyUsed==this && tp!=null)
442 _leastRecentlyUsed=tp;
443 }
444 return true;
445 }
446
447 invalidate();
448 return false;
449 }
450
451
452 public void invalidate()
453 {
454 synchronized(this)
455 {
456
457 _cache.remove(_key);
458 _key=null;
459 if (_buffer!=null)
460 _cachedSize=_cachedSize-_buffer.length();
461 _cachedFiles--;
462
463 if (_mostRecentlyUsed==this)
464 _mostRecentlyUsed=_next;
465 else
466 _prev._next=_next;
467
468 if (_leastRecentlyUsed==this)
469 _leastRecentlyUsed=_prev;
470 else
471 _next._prev=_prev;
472
473 _prev=null;
474 _next=null;
475 _resource.release();
476
477 }
478 }
479
480
481 public Buffer getLastModified()
482 {
483 return _lastModifiedBytes;
484 }
485
486
487 public Buffer getContentType()
488 {
489 return _contentType;
490 }
491
492
493 public void setContentType(Buffer type)
494 {
495 _contentType=type;
496 }
497
498
499 public void release()
500 {
501 }
502
503
504 public Buffer getBuffer()
505 {
506 if (_buffer==null)
507 return null;
508 return new View(_buffer);
509 }
510
511
512 public void setBuffer(Buffer buffer)
513 {
514 _buffer=buffer;
515 }
516
517
518 public long getContentLength()
519 {
520 if (_buffer==null)
521 {
522 if (_resource!=null)
523 return _resource.length();
524 return -1;
525 }
526 return _buffer.length();
527 }
528
529
530 public InputStream getInputStream() throws IOException
531 {
532 return _resource.getInputStream();
533 }
534
535
536 @Override
537 public String toString()
538 {
539 return "{"+_resource+","+_contentType+","+_lastModifiedBytes+"}";
540 }
541
542
543 }
544
545
546
547
548
549
550 public class Miss extends Content
551 {
552 Miss(Resource resource)
553 {
554 super(resource);
555 }
556
557
558 @Override
559 boolean isValid()
560 {
561 if (_resource.exists())
562 {
563 invalidate();
564 return false;
565 }
566 return true;
567 }
568 }
569 }