org.eclipse.jetty.server.ResourceCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
//
// ========================================================================
// Copyright (c) 1995-2014 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;
_useFileMappedBuffer=useFileMappedBuffer;
}
/* ------------------------------------------------------------ */
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);
}
}
}