net.lightbody.bmp.proxy.jetty.http.ResourceCache.java Source code

Java tutorial

Introduction

Here is the source code for net.lightbody.bmp.proxy.jetty.http.ResourceCache.java

Source

// ========================================================================
// $Id: ResourceCache.java,v 1.13 2006/04/04 22:28:02 gregwilkins Exp $
// Copyright 2000-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================

package net.lightbody.bmp.proxy.jetty.http;

import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import net.lightbody.bmp.proxy.jetty.util.*;
import org.apache.commons.logging.Log;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/* ------------------------------------------------------------ */
/** 
 * @version $Id: ResourceCache.java,v 1.13 2006/04/04 22:28:02 gregwilkins Exp $
 * @author Greg Wilkins
 */
public class ResourceCache implements LifeCycle, Serializable {
    private static Log log = LogFactory.getLog(ResourceCache.class);

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    private final static Map __dftMimeMap = new HashMap();
    private final static Map __encodings = new HashMap();
    static {
        ResourceBundle mime = ResourceBundle.getBundle("net/lightbody/bmp/proxy/jetty/http/mime");
        Enumeration i = mime.getKeys();
        while (i.hasMoreElements()) {
            String ext = (String) i.nextElement();
            __dftMimeMap.put(StringUtil.asciiToLowerCase(ext), mime.getString(ext));
        }
        ResourceBundle encoding = ResourceBundle.getBundle("net/lightbody/bmp/proxy/jetty/http/encoding");
        i = encoding.getKeys();
        while (i.hasMoreElements()) {
            String type = (String) i.nextElement();
            __encodings.put(type, encoding.getString(type));
        }
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    // TODO - handle this
    // These attributes are serialized by WebApplicationContext, which needs
    // to be updated if you add to these

    private int _maxCachedFileSize = 1 * 1024;
    private int _maxCacheSize = 1 * 1024;

    /* ------------------------------------------------------------ */
    private Resource _resourceBase;
    private Map _mimeMap;
    private Map _encodingMap;

    /* ------------------------------------------------------------ */
    private transient boolean _started;

    protected transient Map _cache;
    protected transient int _cacheSize;
    protected transient CachedMetaData _mostRecentlyUsed;
    protected transient CachedMetaData _leastRecentlyUsed;

    /* ------------------------------------------------------------ */
    /** Constructor.
     */
    public ResourceCache() {
        _cache = new HashMap();
    }

    /* ------------------------------------------------------------ */
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        _cache = new HashMap();
    }

    /* ------------------------------------------------------------ */
    /** Set the Resource Base.
     * The base resource is the Resource to use as a relative base
     * for all context resources. The ResourceBase attribute is a
     * string version of the baseResource.
     * If a relative file is passed, it is converted to a file
     * URL based on the current working directory.
     * @return The file or URL to use as the base for all resources
     * within the context.
     */
    public String getResourceBase() {
        if (_resourceBase == null)
            return null;
        return _resourceBase.toString();
    }

    /* ------------------------------------------------------------ */
    /** Set the Resource Base.
     * The base resource is the Resource to use as a relative base
     * for all context resources. The ResourceBase attribute is a
     * string version of the baseResource.
     * If a relative file is passed, it is converted to a file
     * URL based on the current working directory.
     * @param resourceBase A URL prefix or directory name.
     */
    public void setResourceBase(String resourceBase) {
        try {
            _resourceBase = Resource.newResource(resourceBase);
            if (log.isDebugEnabled())
                log.debug("resourceBase=" + _resourceBase + " for " + this);
        } catch (IOException e) {
            log.debug(LogSupport.EXCEPTION, e);
            throw new IllegalArgumentException(resourceBase + ":" + e.toString());
        }
    }

    /* ------------------------------------------------------------ */
    /** Get the base resource.
     * The base resource is the Resource to use as a relative base
     * for all context resources. The ResourceBase attribute is a
     * string version of the baseResource.
     * @return The resourceBase as a Resource instance
     */
    public Resource getBaseResource() {
        return _resourceBase;
    }

    /* ------------------------------------------------------------ */
    /** Set the base resource.
     * The base resource is the Resource to use as a relative base
     * for all context resources. The ResourceBase attribute is a
     * string version of the baseResource.
     * @param base The resourceBase as a Resource instance
     */
    public void setBaseResource(Resource base) {
        _resourceBase = base;
    }

    /* ------------------------------------------------------------ */
    public int getMaxCachedFileSize() {
        return _maxCachedFileSize;
    }

    /* ------------------------------------------------------------ */
    public void setMaxCachedFileSize(int maxCachedFileSize) {
        _maxCachedFileSize = maxCachedFileSize;
        _cache.clear();
    }

    /* ------------------------------------------------------------ */
    public int getMaxCacheSize() {
        return _maxCacheSize;
    }

    /* ------------------------------------------------------------ */
    public void setMaxCacheSize(int maxCacheSize) {
        _maxCacheSize = maxCacheSize;
        _cache.clear();
    }

    /* ------------------------------------------------------------ */
    public void flushCache() {
        _cache.clear();
        System.gc();
    }

    /* ------------------------------------------------------------ */
    /** Get a resource from the context.
     * Cached Resources are returned if the resource fits within the LRU
     * cache.  Directories may have CachedResources returned, but the
     * caller must use the CachedResource.setCachedData method to set the
     * formatted directory content.
     *
     * @param pathInContext
     * @return Resource
     * @exception IOException
     */
    public Resource getResource(String pathInContext) throws IOException {
        if (log.isTraceEnabled())
            log.trace("getResource " + pathInContext);
        if (_resourceBase == null)
            return null;

        Resource resource = null;

        // Cache operations
        synchronized (_cache) {
            // Look for it in the cache
            CachedResource cached = (CachedResource) _cache.get(pathInContext);
            if (cached != null) {
                if (log.isTraceEnabled())
                    log.trace("CACHE HIT: " + cached);
                CachedMetaData cmd = (CachedMetaData) cached.getAssociate();
                if (cmd != null && cmd.isValid())
                    return cached;
            }

            // Make the resource
            resource = _resourceBase.addPath(_resourceBase.encode(pathInContext));
            if (log.isTraceEnabled())
                log.trace("CACHE MISS: " + resource);
            if (resource == null)
                return null;

            // Check for file aliasing
            if (resource.getAlias() != null) {
                log.warn("Alias request of '" + resource.getAlias() + "' for '" + resource + "'");
                return null;
            }

            // Is it an existing file?
            long len = resource.length();
            if (resource.exists()) {
                // Is it badly named?
                if (!resource.isDirectory() && pathInContext.endsWith("/"))
                    return null;

                // Guess directory length.
                if (resource.isDirectory()) {
                    if (resource.list() != null)
                        len = resource.list().length * 100;
                    else
                        len = 0;
                }

                // Is it cacheable?
                if (len > 0 && len < _maxCachedFileSize && len < _maxCacheSize) {
                    int needed = _maxCacheSize - (int) len;
                    while (_cacheSize > needed)
                        _leastRecentlyUsed.invalidate();

                    cached = resource.cache();
                    if (log.isTraceEnabled())
                        log.trace("CACHED: " + resource);
                    new CachedMetaData(cached, pathInContext);
                    return cached;
                }
            }
        }

        // Non cached response
        new ResourceMetaData(resource);
        return resource;
    }

    /* ------------------------------------------------------------ */
    public synchronized Map getMimeMap() {
        return _mimeMap;
    }

    /* ------------------------------------------------------------ */
    /**
     * Also sets the org.mortbay.http.mimeMap context attribute
     * @param mimeMap
     */
    public void setMimeMap(Map mimeMap) {
        _mimeMap = mimeMap;
    }

    /* ------------------------------------------------------------ */
    /** Get the MIME type by filename extension.
     * @param filename A file name
     * @return MIME type matching the longest dot extension of the
     * file name.
     */
    public String getMimeByExtension(String filename) {
        String type = null;

        if (filename != null) {
            int i = -1;
            while (type == null) {
                i = filename.indexOf(".", i + 1);

                if (i < 0 || i >= filename.length())
                    break;

                String ext = StringUtil.asciiToLowerCase(filename.substring(i + 1));
                if (_mimeMap != null)
                    type = (String) _mimeMap.get(ext);
                if (type == null)
                    type = (String) __dftMimeMap.get(ext);
            }
        }

        if (type == null) {
            if (_mimeMap != null)
                type = (String) _mimeMap.get("*");
            if (type == null)
                type = (String) __dftMimeMap.get("*");
        }

        return type;
    }

    /* ------------------------------------------------------------ */
    /** Set a mime mapping
     * @param extension
     * @param type
     */
    public void setMimeMapping(String extension, String type) {
        if (_mimeMap == null)
            _mimeMap = new HashMap();
        _mimeMap.put(StringUtil.asciiToLowerCase(extension), type);
    }

    /* ------------------------------------------------------------ */
    /** Get the map of mime type to char encoding.
     * @return Map of mime type to character encodings.
     */
    public synchronized Map getEncodingMap() {
        if (_encodingMap == null)
            _encodingMap = Collections.unmodifiableMap(__encodings);
        return _encodingMap;
    }

    /* ------------------------------------------------------------ */
    /** Set the map of mime type to char encoding.
     * Also sets the org.mortbay.http.encodingMap context attribute
     * @param encodingMap Map of mime type to character encodings.
     */
    public void setEncodingMap(Map encodingMap) {
        _encodingMap = encodingMap;
    }

    /* ------------------------------------------------------------ */
    /** Get char encoding by mime type.
     * @param type A mime type.
     * @return The prefered character encoding for that type if known.
     */
    public String getEncodingByMimeType(String type) {
        String encoding = null;

        if (type != null)
            encoding = (String) _encodingMap.get(type);

        return encoding;
    }

    /* ------------------------------------------------------------ */
    /** Set the encoding that should be used for a mimeType.
     * @param mimeType
     * @param encoding
     */
    public void setTypeEncoding(String mimeType, String encoding) {
        getEncodingMap().put(mimeType, encoding);
    }

    /* ------------------------------------------------------------ */
    public synchronized void start() throws Exception {
        if (isStarted())
            return;
        getMimeMap();
        getEncodingMap();
        _started = true;
    }

    /* ------------------------------------------------------------ */
    public boolean isStarted() {
        return _started;
    }

    /* ------------------------------------------------------------ */
    /** Stop the context.
     */
    public void stop() throws InterruptedException {
        _started = false;
        _cache.clear();
    }

    /* ------------------------------------------------------------ */
    /** Destroy a context.
     * Destroy a context and remove it from the HttpServer. The
     * HttpContext must be stopped before it can be destroyed.
     */
    public void destroy() {
        if (isStarted())
            throw new IllegalStateException("Started");

        setMimeMap(null);
        _encodingMap = null;
    }

    /* ------------------------------------------------------------ */
    /** Get Resource MetaData.
     * @param resource 
     * @return Meta data for the resource.
     */
    public ResourceMetaData getResourceMetaData(Resource resource) {
        Object o = resource.getAssociate();
        if (o instanceof ResourceMetaData)
            return (ResourceMetaData) o;
        return new ResourceMetaData(resource);
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /** MetaData associated with a context Resource.
     */
    public class ResourceMetaData {
        protected String _name;
        protected Resource _resource;

        ResourceMetaData(Resource resource) {
            _resource = resource;
            _name = _resource.toString();
            _resource.setAssociate(this);
        }

        public String getLength() {
            return Long.toString(_resource.length());
        }

        public String getLastModified() {
            return HttpFields.formatDate(_resource.lastModified(), false);
        }

        public String getMimeType() {
            return getMimeByExtension(_name);
        }
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    private class CachedMetaData extends ResourceMetaData {
        String _lastModified;
        String _encoding;
        String _length;
        String _key;

        CachedResource _cached;
        CachedMetaData _prev;
        CachedMetaData _next;

        CachedMetaData(CachedResource resource, String pathInContext) {
            super(resource);
            _cached = resource;
            _length = super.getLength();
            _lastModified = super.getLastModified();
            _encoding = super.getMimeType();
            _key = pathInContext;

            _next = _mostRecentlyUsed;
            _mostRecentlyUsed = this;
            if (_next != null)
                _next._prev = this;
            _prev = null;
            if (_leastRecentlyUsed == null)
                _leastRecentlyUsed = this;

            _cache.put(_key, resource);

            _cacheSize += _cached.length();

        }

        public String getLength() {
            return _length;
        }

        public String getLastModified() {
            return _lastModified;
        }

        public String getMimeType() {
            return _encoding;
        }

        /* ------------------------------------------------------------ */
        boolean isValid() throws IOException {
            if (_cached.isUptoDate()) {
                if (_mostRecentlyUsed != this) {
                    CachedMetaData tp = _prev;
                    CachedMetaData tn = _next;

                    _next = _mostRecentlyUsed;
                    _mostRecentlyUsed = this;
                    if (_next != null)
                        _next._prev = this;
                    _prev = null;

                    if (tp != null)
                        tp._next = tn;
                    if (tn != null)
                        tn._prev = tp;

                    if (_leastRecentlyUsed == this && tp != null)
                        _leastRecentlyUsed = tp;
                }
                return true;
            }

            invalidate();
            return false;
        }

        public void invalidate() {
            // Invalidate it
            _cache.remove(_key);
            _cacheSize = _cacheSize - (int) _cached.length();

            if (_mostRecentlyUsed == this)
                _mostRecentlyUsed = _next;
            else
                _prev._next = _next;

            if (_leastRecentlyUsed == this)
                _leastRecentlyUsed = _prev;
            else
                _next._prev = _prev;

            _prev = null;
            _next = null;
        }
    }

}