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.ByteArrayInputStream;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.util.Comparator;
20  import java.util.SortedSet;
21  import java.util.TreeSet;
22  import java.util.concurrent.ConcurrentHashMap;
23  import java.util.concurrent.ConcurrentMap;
24  import java.util.concurrent.atomic.AtomicInteger;
25  import java.util.concurrent.atomic.AtomicReference;
26  
27  import org.eclipse.jetty.http.HttpContent;
28  import org.eclipse.jetty.http.HttpFields;
29  import org.eclipse.jetty.http.MimeTypes;
30  import org.eclipse.jetty.io.Buffer;
31  import org.eclipse.jetty.io.ByteArrayBuffer;
32  import org.eclipse.jetty.io.View;
33  import org.eclipse.jetty.io.nio.DirectNIOBuffer;
34  import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.resource.Resource;
37  import org.eclipse.jetty.util.resource.ResourceFactory;
38  
39  
40  /* ------------------------------------------------------------ */
41  /** 
42   * 
43   */
44  public class ResourceCache
45  {
46      private final ConcurrentMap<String,Content> _cache;
47      private final AtomicInteger _cachedSize;
48      private final AtomicInteger _cachedFiles;
49      private final boolean  _useFileMappedBuffer;
50      private final ResourceFactory _factory;
51      
52      private final MimeTypes _mimeTypes;
53      private int _maxCachedFileSize =4*1024*1024;
54      private int _maxCachedFiles=2048;
55      private int _maxCacheSize =32*1024*1024;
56  
57      /* ------------------------------------------------------------ */
58      /** Constructor.
59       * @param mimeTypes Mimetype to use for meta data
60       * @param fileMappedBuffers True if file mapped buffers can be used for DirectBuffers
61       */
62      public ResourceCache(ResourceFactory factory, MimeTypes mimeTypes,boolean fileMappedBuffers)
63      {
64          _factory = factory;
65          _cache=new ConcurrentHashMap<String,Content>();
66          _cachedSize=new AtomicInteger();
67          _cachedFiles=new AtomicInteger();
68          _useFileMappedBuffer=fileMappedBuffers;
69          _mimeTypes=mimeTypes;
70      }
71  
72      /* ------------------------------------------------------------ */
73      public int getCachedSize()
74      {
75          return _cachedSize.get();
76      }
77      
78      /* ------------------------------------------------------------ */
79      public int getCachedFiles()
80      {
81          return _cachedFiles.get();
82      }
83      
84      /* ------------------------------------------------------------ */
85      public int getMaxCachedFileSize()
86      {
87          return _maxCachedFileSize;
88      }
89  
90      /* ------------------------------------------------------------ */
91      public void setMaxCachedFileSize(int maxCachedFileSize)
92      {
93          _maxCachedFileSize = maxCachedFileSize;
94          shrinkCache();
95      }
96  
97      /* ------------------------------------------------------------ */
98      public int getMaxCacheSize()
99      {
100         return _maxCacheSize;
101     }
102 
103     /* ------------------------------------------------------------ */
104     public void setMaxCacheSize(int maxCacheSize)
105     {
106         _maxCacheSize = maxCacheSize;
107         shrinkCache();
108     }
109 
110     /* ------------------------------------------------------------ */
111     /**
112      * @return Returns the maxCachedFiles.
113      */
114     public int getMaxCachedFiles()
115     {
116         return _maxCachedFiles;
117     }
118     
119     /* ------------------------------------------------------------ */
120     /**
121      * @param maxCachedFiles The maxCachedFiles to set.
122      */
123     public void setMaxCachedFiles(int maxCachedFiles)
124     {
125         _maxCachedFiles = maxCachedFiles;
126         shrinkCache();
127     }
128     
129     /* ------------------------------------------------------------ */
130     public void flushCache()
131     {
132         if (_cache!=null)
133         {
134             while (_cache.size()>0)
135             {
136                 for (String path : _cache.keySet())
137                 {
138                     Content content = _cache.remove(path);
139                     if (content!=null)
140                         content.invalidate();
141                 }
142             }
143         }
144     }
145 
146     /* ------------------------------------------------------------ */
147     /** Get a Entry from the cache.
148      * Get either a valid entry object or create a new one if possible.
149      *
150      * @param pathInContext The key into the cache
151      * @return The entry matching <code>pathInContext</code>, or a new entry if no matching entry was found
152      * @throws IOException Problem loading the resource
153      */
154     public Content lookup(String pathInContext)
155         throws IOException
156     {
157         Content content=null;
158 
159         // Look up cache operations
160         content = _cache.get(pathInContext);
161         
162         if (content!=null && content.isValid())
163             return content;
164        
165         Resource resource=_factory.getResource(pathInContext);
166         Content loaded = load(pathInContext,resource);
167 
168         return loaded;
169     }
170 
171     /* ------------------------------------------------------------ */
172     private Content load(String pathInContext, Resource resource)
173         throws IOException
174     {
175         Content content=null;
176         if (resource!=null && resource.exists() && !resource.isDirectory())
177         {
178             long len = resource.length();
179             
180             // Will it fit in the cache?
181             if (len>0 && len<_maxCachedFileSize && len<_maxCacheSize)
182             {   
183                 // Create the Content (to increment the cache sizes before adding the content 
184                 content = new Content(pathInContext,resource);
185                 
186                 // reduce the cache to an acceptable size.
187                 shrinkCache();
188                 
189                 // Add it to the cache.
190                 Content added = _cache.putIfAbsent(pathInContext,content);
191                 if (added!=null)
192                 {
193                     content.invalidate();
194                     content=added;
195                 }
196                 
197                 return content;
198             }
199         }
200 
201         return null; 
202     }
203     
204     /* ------------------------------------------------------------ */
205     private void shrinkCache()
206     {
207         // While we need to shrink
208         while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
209         {
210             // Scan the entire cache and generate an ordered list by last accessed time.
211             SortedSet<Content> sorted= new TreeSet<Content>(
212                     new Comparator<Content>()
213                     {
214                         public int compare(Content c1, Content c2)
215                         {
216                             if (c1._lastAccessed<c2._lastAccessed)
217                                 return -1;
218                             
219                             if (c1._lastAccessed>c2._lastAccessed)
220                                 return 1;
221 
222                             if (c1._length<c2._length)
223                                 return -1;
224                             
225                             return c1._key.compareTo(c2._key);
226                         }
227                     });
228             for (Content content : _cache.values())
229                 sorted.add(content);
230             
231             // Invalidate least recently used first
232             for (Content content : sorted)
233             {
234                 if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
235                     break;
236                 if (content==_cache.remove(content.getKey()))
237                     content.invalidate();
238             }
239         }
240     }
241 
242     /* ------------------------------------------------------------ */
243     /** Remember a Resource Miss!
244      * @param pathInContext Path the cache resource at
245      * @param resource The resource to cache.
246      */
247     public void miss(String pathInContext, Resource resource)
248     {
249         if (_maxCachedFiles>0 && _cachedFiles.get()>=_maxCachedFiles)
250             return;
251 
252         // check that somebody else did not fill this spot.
253         Miss miss = new Miss(pathInContext,resource);
254         if (_cache.putIfAbsent(pathInContext,miss)!=null)
255             miss.invalidate();
256     }
257     
258     /* ------------------------------------------------------------ */
259     protected Buffer getIndirectBuffer(Resource resource)
260     {
261         try
262         {
263             int len=(int)resource.length();
264             Buffer buffer = new IndirectNIOBuffer(len);
265             InputStream is = resource.getInputStream();
266             buffer.readFrom(is,len);
267             is.close();
268             return buffer;
269         }
270         catch(IOException e)
271         {
272             Log.warn(e);
273             return null;
274         }
275     }
276 
277     /* ------------------------------------------------------------ */
278     protected Buffer getDirectBuffer(Resource resource)
279     {
280         try
281         {
282             if (_useFileMappedBuffer && resource.getFile()!=null) 
283                 return new DirectNIOBuffer(resource.getFile());
284 
285             int len=(int)resource.length();
286             Buffer buffer = new DirectNIOBuffer(len);
287             InputStream is = resource.getInputStream();
288             buffer.readFrom(is,len);
289             is.close();
290             return buffer;
291         }
292         catch(IOException e)
293         {
294             Log.warn(e);
295             return null;
296         }
297     }
298     
299     /* ------------------------------------------------------------ */
300     /* ------------------------------------------------------------ */
301     /** MetaData associated with a context Resource.
302      */
303     public class Content implements HttpContent
304     {
305         final Resource _resource;
306         final int _length;
307         final String _key;
308         final long _lastModified;
309         final Buffer _lastModifiedBytes;
310         final Buffer _contentType;
311         
312         volatile long _lastAccessed;
313         AtomicReference<Buffer> _indirectBuffer=new AtomicReference<Buffer>();
314         AtomicReference<Buffer> _directBuffer=new AtomicReference<Buffer>();
315 
316         /* ------------------------------------------------------------ */
317         Content(String pathInContext,Resource resource)
318         {
319             _key=pathInContext;
320             _resource=resource;
321 
322             _contentType=_mimeTypes.getMimeByExtension(_resource.toString());
323             boolean exists=resource.exists();
324             _lastModified=exists?resource.lastModified():-1;
325             _lastModifiedBytes=_lastModified<0?null:new ByteArrayBuffer(HttpFields.formatDate(_lastModified));
326             
327             _length=exists?(int)resource.length():0;
328             _cachedSize.addAndGet(_length);
329             _cachedFiles.incrementAndGet();
330             _lastAccessed=System.currentTimeMillis();
331         }
332 
333 
334         /* ------------------------------------------------------------ */
335         public String getKey()
336         {
337             return _key;
338         }
339 
340         /* ------------------------------------------------------------ */
341         public boolean isCached()
342         {
343             return _key!=null;
344         }
345 
346         /* ------------------------------------------------------------ */
347         public Resource getResource()
348         {
349             return _resource;
350         }
351         
352         /* ------------------------------------------------------------ */
353         boolean isValid()
354         {
355             if (_lastModified==_resource.lastModified())
356             {
357                 _lastAccessed=System.currentTimeMillis();
358                 return true;
359             }
360 
361             if (this==_cache.remove(_key))
362                 invalidate();
363             return false;
364         }
365 
366         /* ------------------------------------------------------------ */
367         protected void invalidate()
368         {
369             // Invalidate it
370             _cachedSize.addAndGet(-_length);
371             _cachedFiles.decrementAndGet();
372             _resource.release(); 
373         }
374 
375         /* ------------------------------------------------------------ */
376         public Buffer getLastModified()
377         {
378             return _lastModifiedBytes;
379         }
380 
381         /* ------------------------------------------------------------ */
382         public Buffer getContentType()
383         {
384             return _contentType;
385         }
386 
387         /* ------------------------------------------------------------ */
388         public void release()
389         {
390             // don't release while cached. Release when invalidated.
391         }
392 
393         /* ------------------------------------------------------------ */
394         public Buffer getIndirectBuffer()
395         {
396             Buffer buffer = _indirectBuffer.get();
397             if (buffer==null)
398             {
399                 synchronized (_indirectBuffer)
400                 {
401                     if (_indirectBuffer.get()==null)
402                     {
403                         buffer=ResourceCache.this.getIndirectBuffer(_resource);
404                         _indirectBuffer.set(buffer);
405                     }
406                 }
407             }
408             if (buffer==null)
409                 return null;
410             return new View(buffer);
411         }
412         
413 
414         /* ------------------------------------------------------------ */
415         public Buffer getDirectBuffer()
416         {
417             Buffer buffer = _directBuffer.get();
418             if (buffer==null)
419             {
420                 synchronized (_directBuffer)
421                 {
422                     if (_directBuffer.get()==null)
423                     {
424                         buffer=ResourceCache.this.getDirectBuffer(_resource);
425                         _directBuffer.set(buffer);
426                     }
427                 }
428             }
429             if (buffer==null)
430                 return null;
431                         
432             return new View(buffer);
433         }
434         
435         /* ------------------------------------------------------------ */
436         public long getContentLength()
437         {
438             return _length;
439         }
440 
441         /* ------------------------------------------------------------ */
442         public InputStream getInputStream() throws IOException
443         {
444             Buffer indirect = getIndirectBuffer();
445             if (indirect!=null && indirect.array()!=null)
446                 return new ByteArrayInputStream(indirect.array(),indirect.getIndex(),indirect.length());
447            
448             return _resource.getInputStream();
449         }   
450 
451         /* ------------------------------------------------------------ */
452         @Override
453         public String toString()
454         {
455             return "{"+_resource+","+_contentType+","+_lastModifiedBytes+"}";
456         }
457         
458         
459     }
460     
461 
462     /* ------------------------------------------------------------ */
463     /* ------------------------------------------------------------ */
464     /** MetaData associated with a context Resource.
465      */
466     public class Miss extends Content
467     {
468         Miss(String pathInContext,Resource resource)
469         {
470             super(pathInContext,resource);
471         }
472 
473         /* ------------------------------------------------------------ */
474         @Override
475         boolean isValid()
476         {
477             if (_resource.exists())
478             {
479                 if (this==_cache.remove(_key))
480                     invalidate();
481                 return false;
482             }
483             return true;
484         }
485     }
486 }