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