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.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 }