]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/server/ResourceCache.java
Update notes about password security
[gigi.git] / lib / jetty / org / eclipse / jetty / server / ResourceCache.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 //  ------------------------------------------------------------------------
5 //  All rights reserved. This program and the accompanying materials
6 //  are made available under the terms of the Eclipse Public License v1.0
7 //  and Apache License v2.0 which accompanies this distribution.
8 //
9 //      The Eclipse Public License is available at
10 //      http://www.eclipse.org/legal/epl-v10.html
11 //
12 //      The Apache License v2.0 is available at
13 //      http://www.opensource.org/licenses/apache2.0.php
14 //
15 //  You may elect to redistribute this code under either of these licenses.
16 //  ========================================================================
17 //
18
19 package org.eclipse.jetty.server;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.ReadableByteChannel;
26 import java.util.Comparator;
27 import java.util.SortedSet;
28 import java.util.TreeSet;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31 import java.util.concurrent.atomic.AtomicInteger;
32 import java.util.concurrent.atomic.AtomicReference;
33
34 import org.eclipse.jetty.http.DateGenerator;
35 import org.eclipse.jetty.http.HttpContent;
36 import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
37 import org.eclipse.jetty.http.MimeTypes;
38 import org.eclipse.jetty.util.BufferUtil;
39 import org.eclipse.jetty.util.log.Log;
40 import org.eclipse.jetty.util.log.Logger;
41 import org.eclipse.jetty.util.resource.Resource;
42 import org.eclipse.jetty.util.resource.ResourceFactory;
43
44
45 /* ------------------------------------------------------------ */
46 /** 
47  * 
48  */
49 public class ResourceCache
50 {
51     private static final Logger LOG = Log.getLogger(ResourceCache.class);
52
53     private final ConcurrentMap<String,Content> _cache;
54     private final AtomicInteger _cachedSize;
55     private final AtomicInteger _cachedFiles;
56     private final ResourceFactory _factory;
57     private final ResourceCache _parent;
58     private final MimeTypes _mimeTypes;
59     private final boolean _etagSupported;
60     private final boolean  _useFileMappedBuffer;
61     
62     private int _maxCachedFileSize =4*1024*1024;
63     private int _maxCachedFiles=2048;
64     private int _maxCacheSize =32*1024*1024;
65     
66     /* ------------------------------------------------------------ */
67     /** Constructor.
68      * @param mimeTypes Mimetype to use for meta data
69      */
70     public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
71     {
72         _factory = factory;
73         _cache=new ConcurrentHashMap<String,Content>();
74         _cachedSize=new AtomicInteger();
75         _cachedFiles=new AtomicInteger();
76         _mimeTypes=mimeTypes;
77         _parent=parent;
78         _useFileMappedBuffer=useFileMappedBuffer;
79         _etagSupported=etags;
80     }
81
82     /* ------------------------------------------------------------ */
83     public int getCachedSize()
84     {
85         return _cachedSize.get();
86     }
87     
88     /* ------------------------------------------------------------ */
89     public int getCachedFiles()
90     {
91         return _cachedFiles.get();
92     }
93     
94     /* ------------------------------------------------------------ */
95     public int getMaxCachedFileSize()
96     {
97         return _maxCachedFileSize;
98     }
99
100     /* ------------------------------------------------------------ */
101     public void setMaxCachedFileSize(int maxCachedFileSize)
102     {
103         _maxCachedFileSize = maxCachedFileSize;
104         shrinkCache();
105     }
106
107     /* ------------------------------------------------------------ */
108     public int getMaxCacheSize()
109     {
110         return _maxCacheSize;
111     }
112
113     /* ------------------------------------------------------------ */
114     public void setMaxCacheSize(int maxCacheSize)
115     {
116         _maxCacheSize = maxCacheSize;
117         shrinkCache();
118     }
119
120     /* ------------------------------------------------------------ */
121     /**
122      * @return Returns the maxCachedFiles.
123      */
124     public int getMaxCachedFiles()
125     {
126         return _maxCachedFiles;
127     }
128     
129     /* ------------------------------------------------------------ */
130     /**
131      * @param maxCachedFiles The maxCachedFiles to set.
132      */
133     public void setMaxCachedFiles(int maxCachedFiles)
134     {
135         _maxCachedFiles = maxCachedFiles;
136         shrinkCache();
137     }
138
139     /* ------------------------------------------------------------ */
140     public boolean isUseFileMappedBuffer()
141     {
142         return _useFileMappedBuffer;
143     }
144
145     /* ------------------------------------------------------------ */
146     public void flushCache()
147     {
148         if (_cache!=null)
149         {
150             while (_cache.size()>0)
151             {
152                 for (String path : _cache.keySet())
153                 {
154                     Content content = _cache.remove(path);
155                     if (content!=null)
156                         content.invalidate();
157                 }
158             }
159         }
160     }
161
162     /* ------------------------------------------------------------ */
163     /** Get a Entry from the cache.
164      * Get either a valid entry object or create a new one if possible.
165      *
166      * @param pathInContext The key into the cache
167      * @return The entry matching <code>pathInContext</code>, or a new entry 
168      * if no matching entry was found. If the content exists but is not cachable, 
169      * then a {@link ResourceAsHttpContent} instance is return. If 
170      * the resource does not exist, then null is returned.
171      * @throws IOException Problem loading the resource
172      */
173     public HttpContent lookup(String pathInContext)
174         throws IOException
175     {
176         // Is the content in this cache?
177         Content content =_cache.get(pathInContext);
178         if (content!=null && (content).isValid())
179             return content;
180        
181         // try loading the content from our factory.
182         Resource resource=_factory.getResource(pathInContext);
183         HttpContent loaded = load(pathInContext,resource);
184         if (loaded!=null)
185             return loaded;
186         
187         // Is the content in the parent cache?
188         if (_parent!=null)
189         {
190             HttpContent httpContent=_parent.lookup(pathInContext);
191             if (httpContent!=null)
192                 return httpContent;
193         }
194         
195         return null;
196     }
197     
198     /* ------------------------------------------------------------ */
199     /**
200      * @param resource
201      * @return True if the resource is cacheable. The default implementation tests the cache sizes.
202      */
203     protected boolean isCacheable(Resource resource)
204     {
205         long len = resource.length();
206
207         // Will it fit in the cache?
208         return  (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
209     }
210     
211     /* ------------------------------------------------------------ */
212     private HttpContent load(String pathInContext, Resource resource)
213         throws IOException
214     {
215         Content content=null;
216         
217         if (resource==null || !resource.exists())
218             return null;
219         
220         // Will it fit in the cache?
221         if (!resource.isDirectory() && isCacheable(resource))
222         {   
223             // Create the Content (to increment the cache sizes before adding the content 
224             content = new Content(pathInContext,resource);
225
226             // reduce the cache to an acceptable size.
227             shrinkCache();
228
229             // Add it to the cache.
230             Content added = _cache.putIfAbsent(pathInContext,content);
231             if (added!=null)
232             {
233                 content.invalidate();
234                 content=added;
235             }
236
237             return content;
238         }
239         
240         return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
241         
242     }
243     
244     /* ------------------------------------------------------------ */
245     private void shrinkCache()
246     {
247         // While we need to shrink
248         while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
249         {
250             // Scan the entire cache and generate an ordered list by last accessed time.
251             SortedSet<Content> sorted= new TreeSet<Content>(
252                     new Comparator<Content>()
253                     {
254                         public int compare(Content c1, Content c2)
255                         {
256                             if (c1._lastAccessed<c2._lastAccessed)
257                                 return -1;
258                             
259                             if (c1._lastAccessed>c2._lastAccessed)
260                                 return 1;
261
262                             if (c1._length<c2._length)
263                                 return -1;
264                             
265                             return c1._key.compareTo(c2._key);
266                         }
267                     });
268             for (Content content : _cache.values())
269                 sorted.add(content);
270             
271             // Invalidate least recently used first
272             for (Content content : sorted)
273             {
274                 if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
275                     break;
276                 if (content==_cache.remove(content.getKey()))
277                     content.invalidate();
278             }
279         }
280     }
281     
282     /* ------------------------------------------------------------ */
283     protected ByteBuffer getIndirectBuffer(Resource resource)
284     {
285         try
286         {
287             return BufferUtil.toBuffer(resource,true);
288         }
289         catch(IOException|IllegalArgumentException e)
290         {
291             LOG.warn(e);
292             return null;
293         }
294     }
295
296     /* ------------------------------------------------------------ */
297     protected ByteBuffer getDirectBuffer(Resource resource)
298     {
299         try
300         {
301             if (_useFileMappedBuffer && resource.getFile()!=null) 
302                 return BufferUtil.toMappedBuffer(resource.getFile());
303             
304             return BufferUtil.toBuffer(resource,true);
305         }
306         catch(IOException|IllegalArgumentException e)
307         {
308             LOG.warn(e);
309             return null;
310         }
311     }
312
313     /* ------------------------------------------------------------ */
314     @Override
315     public String toString()
316     {
317         return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
318     }
319     
320     /* ------------------------------------------------------------ */
321     /* ------------------------------------------------------------ */
322     /** MetaData associated with a context Resource.
323      */
324     public class Content implements HttpContent
325     {
326         final Resource _resource;
327         final int _length;
328         final String _key;
329         final long _lastModified;
330         final ByteBuffer _lastModifiedBytes;
331         final ByteBuffer _contentType;
332         final String _etag;
333         
334         volatile long _lastAccessed;
335         AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
336         AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
337
338         /* ------------------------------------------------------------ */
339         Content(String pathInContext,Resource resource)
340         {
341             _key=pathInContext;
342             _resource=resource;
343
344             String mimeType = _mimeTypes.getMimeByExtension(_resource.toString());
345             _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType));
346             boolean exists=resource.exists();
347             _lastModified=exists?resource.lastModified():-1;
348             _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified));
349             
350             _length=exists?(int)resource.length():0;
351             _cachedSize.addAndGet(_length);
352             _cachedFiles.incrementAndGet();
353             _lastAccessed=System.currentTimeMillis();
354             
355             _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null;
356         }
357
358
359         /* ------------------------------------------------------------ */
360         public String getKey()
361         {
362             return _key;
363         }
364
365         /* ------------------------------------------------------------ */
366         public boolean isCached()
367         {
368             return _key!=null;
369         }
370         
371         /* ------------------------------------------------------------ */
372         public boolean isMiss()
373         {
374             return false;
375         }
376
377         /* ------------------------------------------------------------ */
378         @Override
379         public Resource getResource()
380         {
381             return _resource;
382         }
383
384         /* ------------------------------------------------------------ */
385         @Override
386         public String getETag()
387         {
388             return _etag;
389         }
390         
391         /* ------------------------------------------------------------ */
392         boolean isValid()
393         {
394             if (_lastModified==_resource.lastModified() && _length==_resource.length())
395             {
396                 _lastAccessed=System.currentTimeMillis();
397                 return true;
398             }
399
400             if (this==_cache.remove(_key))
401                 invalidate();
402             return false;
403         }
404
405         /* ------------------------------------------------------------ */
406         protected void invalidate()
407         {
408             // Invalidate it
409             _cachedSize.addAndGet(-_length);
410             _cachedFiles.decrementAndGet();
411             _resource.close(); 
412         }
413
414         /* ------------------------------------------------------------ */
415         @Override
416         public String getLastModified()
417         {
418             return BufferUtil.toString(_lastModifiedBytes);
419         }
420
421         /* ------------------------------------------------------------ */
422         @Override
423         public String getContentType()
424         {
425             return BufferUtil.toString(_contentType);
426         }
427
428         /* ------------------------------------------------------------ */
429         @Override
430         public void release()
431         {
432             // don't release while cached. Release when invalidated.
433         }
434
435         /* ------------------------------------------------------------ */
436         @Override
437         public ByteBuffer getIndirectBuffer()
438         {
439             ByteBuffer buffer = _indirectBuffer.get();
440             if (buffer==null)
441             {
442                 ByteBuffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
443                 
444                 if (buffer2==null)
445                     LOG.warn("Could not load "+this);
446                 else if (_indirectBuffer.compareAndSet(null,buffer2))
447                     buffer=buffer2;
448                 else
449                     buffer=_indirectBuffer.get();
450             }
451             if (buffer==null)
452                 return null;
453             return buffer.slice();
454         }
455         
456
457         /* ------------------------------------------------------------ */
458         @Override
459         public ByteBuffer getDirectBuffer()
460         {
461             ByteBuffer buffer = _directBuffer.get();
462             if (buffer==null)
463             {
464                 ByteBuffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
465
466                 if (buffer2==null)
467                     LOG.warn("Could not load "+this);
468                 else if (_directBuffer.compareAndSet(null,buffer2))
469                     buffer=buffer2;
470                 else
471                     buffer=_directBuffer.get();
472             }
473             if (buffer==null)
474                 return null;
475             return buffer.asReadOnlyBuffer();
476         }
477         
478         /* ------------------------------------------------------------ */
479         @Override
480         public long getContentLength()
481         {
482             return _length;
483         }
484
485         /* ------------------------------------------------------------ */
486         @Override
487         public InputStream getInputStream() throws IOException
488         {
489             ByteBuffer indirect = getIndirectBuffer();
490             if (indirect!=null && indirect.hasArray())
491                 return new ByteArrayInputStream(indirect.array(),indirect.arrayOffset()+indirect.position(),indirect.remaining());
492            
493             return _resource.getInputStream();
494         }   
495         
496         /* ------------------------------------------------------------ */
497         @Override
498         public ReadableByteChannel getReadableByteChannel() throws IOException
499         {
500             return _resource.getReadableByteChannel();
501         }
502
503
504         /* ------------------------------------------------------------ */
505         @Override
506         public String toString()
507         {
508             return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
509         }   
510     }
511 }