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.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.server;
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;
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;
44 /* ------------------------------------------------------------ */
48 public class ResourceCache
50 private static final Logger LOG = Log.getLogger(ResourceCache.class);
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;
61 private int _maxCachedFileSize =128*1024*1024;
62 private int _maxCachedFiles=2048;
63 private int _maxCacheSize =256*1024*1024;
65 /* ------------------------------------------------------------ */
67 * @param mimeTypes Mimetype to use for meta data
69 public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
72 _cache=new ConcurrentHashMap<String,Content>();
73 _cachedSize=new AtomicInteger();
74 _cachedFiles=new AtomicInteger();
77 _useFileMappedBuffer=useFileMappedBuffer;
81 /* ------------------------------------------------------------ */
82 public int getCachedSize()
84 return _cachedSize.get();
87 /* ------------------------------------------------------------ */
88 public int getCachedFiles()
90 return _cachedFiles.get();
93 /* ------------------------------------------------------------ */
94 public int getMaxCachedFileSize()
96 return _maxCachedFileSize;
99 /* ------------------------------------------------------------ */
100 public void setMaxCachedFileSize(int maxCachedFileSize)
102 _maxCachedFileSize = maxCachedFileSize;
106 /* ------------------------------------------------------------ */
107 public int getMaxCacheSize()
109 return _maxCacheSize;
112 /* ------------------------------------------------------------ */
113 public void setMaxCacheSize(int maxCacheSize)
115 _maxCacheSize = maxCacheSize;
119 /* ------------------------------------------------------------ */
121 * @return Returns the maxCachedFiles.
123 public int getMaxCachedFiles()
125 return _maxCachedFiles;
128 /* ------------------------------------------------------------ */
130 * @param maxCachedFiles The maxCachedFiles to set.
132 public void setMaxCachedFiles(int maxCachedFiles)
134 _maxCachedFiles = maxCachedFiles;
138 /* ------------------------------------------------------------ */
139 public boolean isUseFileMappedBuffer()
141 return _useFileMappedBuffer;
144 /* ------------------------------------------------------------ */
145 public void flushCache()
149 while (_cache.size()>0)
151 for (String path : _cache.keySet())
153 Content content = _cache.remove(path);
155 content.invalidate();
161 /* ------------------------------------------------------------ */
162 /** Get a Entry from the cache.
163 * Get either a valid entry object or create a new one if possible.
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
172 public HttpContent lookup(String pathInContext)
175 // Is the content in this cache?
176 Content content =_cache.get(pathInContext);
177 if (content!=null && (content).isValid())
180 // try loading the content from our factory.
181 Resource resource=_factory.getResource(pathInContext);
182 HttpContent loaded = load(pathInContext,resource);
186 // Is the content in the parent cache?
189 HttpContent httpContent=_parent.lookup(pathInContext);
190 if (httpContent!=null)
197 /* ------------------------------------------------------------ */
200 * @return True if the resource is cacheable. The default implementation tests the cache sizes.
202 protected boolean isCacheable(Resource resource)
204 long len = resource.length();
206 // Will it fit in the cache?
207 return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
210 /* ------------------------------------------------------------ */
211 private HttpContent load(String pathInContext, Resource resource)
214 Content content=null;
216 if (resource==null || !resource.exists())
219 // Will it fit in the cache?
220 if (!resource.isDirectory() && isCacheable(resource))
222 // Create the Content (to increment the cache sizes before adding the content
223 content = new Content(pathInContext,resource);
225 // reduce the cache to an acceptable size.
228 // Add it to the cache.
229 Content added = _cache.putIfAbsent(pathInContext,content);
232 content.invalidate();
239 return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
243 /* ------------------------------------------------------------ */
244 private void shrinkCache()
246 // While we need to shrink
247 while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
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>()
253 public int compare(Content c1, Content c2)
255 if (c1._lastAccessed<c2._lastAccessed)
258 if (c1._lastAccessed>c2._lastAccessed)
261 if (c1._length<c2._length)
264 return c1._key.compareTo(c2._key);
267 for (Content content : _cache.values())
270 // Invalidate least recently used first
271 for (Content content : sorted)
273 if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
275 if (content==_cache.remove(content.getKey()))
276 content.invalidate();
281 /* ------------------------------------------------------------ */
282 protected ByteBuffer getIndirectBuffer(Resource resource)
286 return BufferUtil.toBuffer(resource,true);
288 catch(IOException|IllegalArgumentException e)
295 /* ------------------------------------------------------------ */
296 protected ByteBuffer getDirectBuffer(Resource resource)
300 if (_useFileMappedBuffer && resource.getFile()!=null && resource.length()<Integer.MAX_VALUE)
301 return BufferUtil.toMappedBuffer(resource.getFile());
303 return BufferUtil.toBuffer(resource,true);
305 catch(IOException|IllegalArgumentException e)
312 /* ------------------------------------------------------------ */
314 public String toString()
316 return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
319 /* ------------------------------------------------------------ */
320 /* ------------------------------------------------------------ */
321 /** MetaData associated with a context Resource.
323 public class Content implements HttpContent
325 final Resource _resource;
328 final long _lastModified;
329 final ByteBuffer _lastModifiedBytes;
330 final ByteBuffer _contentType;
333 volatile long _lastAccessed;
334 AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
335 AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
337 /* ------------------------------------------------------------ */
338 Content(String pathInContext,Resource resource)
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));
349 _length=exists?(int)resource.length():0;
350 _cachedSize.addAndGet(_length);
351 _cachedFiles.incrementAndGet();
352 _lastAccessed=System.currentTimeMillis();
354 _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null;
358 /* ------------------------------------------------------------ */
359 public String getKey()
364 /* ------------------------------------------------------------ */
365 public boolean isCached()
370 /* ------------------------------------------------------------ */
371 public boolean isMiss()
376 /* ------------------------------------------------------------ */
378 public Resource getResource()
383 /* ------------------------------------------------------------ */
385 public String getETag()
390 /* ------------------------------------------------------------ */
393 if (_lastModified==_resource.lastModified() && _length==_resource.length())
395 _lastAccessed=System.currentTimeMillis();
399 if (this==_cache.remove(_key))
404 /* ------------------------------------------------------------ */
405 protected void invalidate()
408 _cachedSize.addAndGet(-_length);
409 _cachedFiles.decrementAndGet();
413 /* ------------------------------------------------------------ */
415 public String getLastModified()
417 return BufferUtil.toString(_lastModifiedBytes);
420 /* ------------------------------------------------------------ */
422 public String getContentType()
424 return BufferUtil.toString(_contentType);
427 /* ------------------------------------------------------------ */
429 public void release()
431 // don't release while cached. Release when invalidated.
434 /* ------------------------------------------------------------ */
436 public ByteBuffer getIndirectBuffer()
438 ByteBuffer buffer = _indirectBuffer.get();
441 ByteBuffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
444 LOG.warn("Could not load "+this);
445 else if (_indirectBuffer.compareAndSet(null,buffer2))
448 buffer=_indirectBuffer.get();
452 return buffer.slice();
456 /* ------------------------------------------------------------ */
458 public ByteBuffer getDirectBuffer()
460 ByteBuffer buffer = _directBuffer.get();
463 ByteBuffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
466 LOG.warn("Could not load "+this);
467 else if (_directBuffer.compareAndSet(null,buffer2))
470 buffer=_directBuffer.get();
474 return buffer.asReadOnlyBuffer();
477 /* ------------------------------------------------------------ */
479 public long getContentLength()
484 /* ------------------------------------------------------------ */
486 public InputStream getInputStream() throws IOException
488 ByteBuffer indirect = getIndirectBuffer();
489 if (indirect!=null && indirect.hasArray())
490 return new ByteArrayInputStream(indirect.array(),indirect.arrayOffset()+indirect.position(),indirect.remaining());
492 return _resource.getInputStream();
495 /* ------------------------------------------------------------ */
497 public ReadableByteChannel getReadableByteChannel() throws IOException
499 return _resource.getReadableByteChannel();
503 /* ------------------------------------------------------------ */
505 public String toString()
507 return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s}",hashCode(),_resource,_resource.exists(),BufferUtil.toString(_lastModifiedBytes),_contentType);