org.eclipse.jetty.server.ResourceCache Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.View;
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
/* ------------------------------------------------------------ */
/**
*
*/
public class ResourceCache
{
private static final Logger LOG = Log.getLogger(ResourceCache.class);
private final ConcurrentMap _cache;
private final AtomicInteger _cachedSize;
private final AtomicInteger _cachedFiles;
private final ResourceFactory _factory;
private final ResourceCache _parent;
private final MimeTypes _mimeTypes;
private final boolean _etags;
private boolean _useFileMappedBuffer=true;
private int _maxCachedFileSize =4*1024*1024;
private int _maxCachedFiles=2048;
private int _maxCacheSize =32*1024*1024;
/* ------------------------------------------------------------ */
/** Constructor.
* @param mimeTypes Mimetype to use for meta data
*/
public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
{
_factory = factory;
_cache=new ConcurrentHashMap();
_cachedSize=new AtomicInteger();
_cachedFiles=new AtomicInteger();
_mimeTypes=mimeTypes;
_parent=parent;
_etags=etags;
}
/* ------------------------------------------------------------ */
public int getCachedSize()
{
return _cachedSize.get();
}
/* ------------------------------------------------------------ */
public int getCachedFiles()
{
return _cachedFiles.get();
}
/* ------------------------------------------------------------ */
public int getMaxCachedFileSize()
{
return _maxCachedFileSize;
}
/* ------------------------------------------------------------ */
public void setMaxCachedFileSize(int maxCachedFileSize)
{
_maxCachedFileSize = maxCachedFileSize;
shrinkCache();
}
/* ------------------------------------------------------------ */
public int getMaxCacheSize()
{
return _maxCacheSize;
}
/* ------------------------------------------------------------ */
public void setMaxCacheSize(int maxCacheSize)
{
_maxCacheSize = maxCacheSize;
shrinkCache();
}
/* ------------------------------------------------------------ */
/**
* @return Returns the maxCachedFiles.
*/
public int getMaxCachedFiles()
{
return _maxCachedFiles;
}
/* ------------------------------------------------------------ */
/**
* @param maxCachedFiles The maxCachedFiles to set.
*/
public void setMaxCachedFiles(int maxCachedFiles)
{
_maxCachedFiles = maxCachedFiles;
shrinkCache();
}
/* ------------------------------------------------------------ */
public boolean isUseFileMappedBuffer()
{
return _useFileMappedBuffer;
}
/* ------------------------------------------------------------ */
public void setUseFileMappedBuffer(boolean useFileMappedBuffer)
{
_useFileMappedBuffer = useFileMappedBuffer;
}
/* ------------------------------------------------------------ */
public void flushCache()
{
if (_cache!=null)
{
while (_cache.size()>0)
{
for (String path : _cache.keySet())
{
Content content = _cache.remove(path);
if (content!=null)
content.invalidate();
}
}
}
}
/* ------------------------------------------------------------ */
/** Get a Entry from the cache.
* Get either a valid entry object or create a new one if possible.
*
* @param pathInContext The key into the cache
* @return The entry matching pathInContext
, or a new entry
* if no matching entry was found. If the content exists but is not cachable,
* then a {@link ResourceAsHttpContent} instance is return. If
* the resource does not exist, then null is returned.
* @throws IOException Problem loading the resource
*/
public HttpContent lookup(String pathInContext)
throws IOException
{
// Is the content in this cache?
Content content =_cache.get(pathInContext);
if (content!=null && (content).isValid())
return content;
// try loading the content from our factory.
Resource resource=_factory.getResource(pathInContext);
HttpContent loaded = load(pathInContext,resource);
if (loaded!=null)
return loaded;
// Is the content in the parent cache?
if (_parent!=null)
{
HttpContent httpContent=_parent.lookup(pathInContext);
if (httpContent!=null)
return httpContent;
}
return null;
}
/* ------------------------------------------------------------ */
/**
* @param resource
* @return True if the resource is cacheable. The default implementation tests the cache sizes.
*/
protected boolean isCacheable(Resource resource)
{
long len = resource.length();
// Will it fit in the cache?
return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
}
/* ------------------------------------------------------------ */
private HttpContent load(String pathInContext, Resource resource)
throws IOException
{
Content content=null;
if (resource==null || !resource.exists())
return null;
// Will it fit in the cache?
if (!resource.isDirectory() && isCacheable(resource))
{
// Create the Content (to increment the cache sizes before adding the content
content = new Content(pathInContext,resource);
// reduce the cache to an acceptable size.
shrinkCache();
// Add it to the cache.
Content added = _cache.putIfAbsent(pathInContext,content);
if (added!=null)
{
content.invalidate();
content=added;
}
return content;
}
return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etags);
}
/* ------------------------------------------------------------ */
private void shrinkCache()
{
// While we need to shrink
while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
{
// Scan the entire cache and generate an ordered list by last accessed time.
SortedSet sorted= new TreeSet(
new Comparator()
{
public int compare(Content c1, Content c2)
{
if (c1._lastAccessedc2._lastAccessed)
return 1;
if (c1._length _indirectBuffer=new AtomicReference();
AtomicReference _directBuffer=new AtomicReference();
/* ------------------------------------------------------------ */
Content(String pathInContext,Resource resource)
{
_key=pathInContext;
_resource=resource;
_contentType=_mimeTypes.getMimeByExtension(_resource.toString());
boolean exists=resource.exists();
_lastModified=exists?resource.lastModified():-1;
_lastModifiedBytes=_lastModified<0?null:new ByteArrayBuffer(HttpFields.formatDate(_lastModified));
_length=exists?(int)resource.length():0;
_cachedSize.addAndGet(_length);
_cachedFiles.incrementAndGet();
_lastAccessed=System.currentTimeMillis();
_etagBuffer=_etags?new ByteArrayBuffer(resource.getWeakETag()):null;
}
/* ------------------------------------------------------------ */
public String getKey()
{
return _key;
}
/* ------------------------------------------------------------ */
public boolean isCached()
{
return _key!=null;
}
/* ------------------------------------------------------------ */
public boolean isMiss()
{
return false;
}
/* ------------------------------------------------------------ */
public Resource getResource()
{
return _resource;
}
/* ------------------------------------------------------------ */
public Buffer getETag()
{
return _etagBuffer;
}
/* ------------------------------------------------------------ */
boolean isValid()
{
if (_lastModified==_resource.lastModified() && _length==_resource.length())
{
_lastAccessed=System.currentTimeMillis();
return true;
}
if (this==_cache.remove(_key))
invalidate();
return false;
}
/* ------------------------------------------------------------ */
protected void invalidate()
{
// Invalidate it
_cachedSize.addAndGet(-_length);
_cachedFiles.decrementAndGet();
_resource.release();
}
/* ------------------------------------------------------------ */
public Buffer getLastModified()
{
return _lastModifiedBytes;
}
/* ------------------------------------------------------------ */
public Buffer getContentType()
{
return _contentType;
}
/* ------------------------------------------------------------ */
public void release()
{
// don't release while cached. Release when invalidated.
}
/* ------------------------------------------------------------ */
public Buffer getIndirectBuffer()
{
Buffer buffer = _indirectBuffer.get();
if (buffer==null)
{
Buffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
if (buffer2==null)
LOG.warn("Could not load "+this);
else if (_indirectBuffer.compareAndSet(null,buffer2))
buffer=buffer2;
else
buffer=_indirectBuffer.get();
}
if (buffer==null)
return null;
return new View(buffer);
}
/* ------------------------------------------------------------ */
public Buffer getDirectBuffer()
{
Buffer buffer = _directBuffer.get();
if (buffer==null)
{
Buffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
if (buffer2==null)
LOG.warn("Could not load "+this);
else if (_directBuffer.compareAndSet(null,buffer2))
buffer=buffer2;
else
buffer=_directBuffer.get();
}
if (buffer==null)
return null;
return new View(buffer);
}
/* ------------------------------------------------------------ */
public long getContentLength()
{
return _length;
}
/* ------------------------------------------------------------ */
public InputStream getInputStream() throws IOException
{
Buffer indirect = getIndirectBuffer();
if (indirect!=null && indirect.array()!=null)
return new ByteArrayInputStream(indirect.array(),indirect.getIndex(),indirect.length());
return _resource.getInputStream();
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
}
}
}