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