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