1 // ========================================================================
2 // Copyright (c) 2000-2009 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // All rights reserved. This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v1.0
6 // and Apache License v2.0 which accompanies this distribution.
7 // The Eclipse Public License is available at
8 // http://www.eclipse.org/legal/epl-v10.html
9 // The Apache License v2.0 is available at
10 // http://www.opensource.org/licenses/apache2.0.php
11 // You may elect to redistribute this code under either of these licenses.
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 /** Constructor.
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 * @return Returns the maxCachedFiles.
101 */
102 public int getMaxCachedFiles()
103 {
104 return _maxCachedFiles;
105 }
106
107 /* ------------------------------------------------------------ */
108 /**
109 * @param maxCachedFiles The maxCachedFiles to set.
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 /** Get a Entry from the cache.
139 * Get either a valid entry object or create a new one if possible.
140 *
141 * @param pathInContext The key into the cache
142 * @param factory If no matching entry is found, this {@link ResourceFactory} will be used to create the {@link Resource}
143 * for the new enry that is created.
144 * @return The entry matching <code>pathInContext</code>, or a new entry if no matching entry was found
145 */
146 public Content lookup(String pathInContext, ResourceFactory factory)
147 throws IOException
148 {
149 Content content=null;
150
151 // Look up cache operations
152 synchronized(_cache)
153 {
154 // Look for it in the cache
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 // Look up cache operations
173 synchronized(_cache)
174 {
175 // Look for it in the cache
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 // check the cache is not full of locked content before loading content
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 // check that somebody else did not fill this spot.
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; // this could waste an allocated File or DirectBuffer
227
228 content.cache(pathInContext);
229
230 return content;
231 }
232 }
233 }
234
235 return null;
236 }
237
238 /* ------------------------------------------------------------ */
239 /** Remember a Resource Miss!
240 * @param pathInContext
241 * @param resource
242 * @throws IOException
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 // check that somebody else did not fill this spot.
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 /** Stop the context.
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 /** MetaData associated with a context Resource.
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 * @return true if the content is locked in the cache
339 */
340 public boolean isLocked()
341 {
342 return _locked;
343 }
344
345
346 /* ------------------------------------------------------------ */
347 /**
348 * @param locked true if the content is locked in the cache
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 // Invalidate it
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 /** MetaData associated with a context Resource.
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 }