View Javadoc

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