org.scilla.core.CacheManager.java Source code

Java tutorial

Introduction

Here is the source code for org.scilla.core.CacheManager.java

Source

/*
 * scilla
 *
 * Copyright (C) 2001  R.W. van 't Veer
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 */

package org.scilla.core;

import java.io.*;
import java.net.URL;

import java.util.Iterator;
import java.util.Hashtable;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.scilla.*;
import org.scilla.util.*;

/**
 * The CacheManager serves cached or fresh objects.  If the requested
 * object is not available in cache, a new conversion will be started.
 *
 * @version $Revision: 1.35 $
 * @author R.W. van 't Veer
 */
public class CacheManager implements RunnerChangeListener {
    /** instance of logger */
    private static final Log log = LogFactory.getLog(CacheManager.class);
    /** instance of global configuration */
    private static final Config config = Config.getInstance();

    /** singleton instance of this class */
    private static CacheManager _instance = null;

    /** private constructor for this singleton */
    private CacheManager() {
        // launch cleanup daemon
    }

    /**
     * This is a singleton.
     * @return CacheManager for this scilla instance
     */
    public static synchronized CacheManager getInstance() {
        if (_instance == null) {
            _instance = new CacheManager();
        }
        return _instance;
    }

    /**
     * Get object from cache.  The CacheManager will lookup a
     * equivalent object in cache.  When no cached object is available
     * an new conversion will be started.
     * @param req request info
     * @return requested object
     * @throws ScillaException when object creation failed
     */
    public MediaObject getMediaObject(Request req) throws ScillaException {
        if (log.isDebugEnabled()) {
            log.debug("getMediaObject(" + req + ")");
        }

        File ifile = new File(req.getInputFile());

        // does source really exist
        if (!ifile.exists()) {
            throw new ScillaNoInputException();
        }
        // does request need any conversion?
        if (!req.needConverter()) {
            log.debug("get: original data");
            return new FileObject(req.getInputFile());
        }

        MediaObject obj = null;
        String ofn = getCachedObjectFilename(req);
        File ofile = new File(ofn);

        // conversion still running?
        obj = (RunnerObject) runners.get(ofn);
        if (obj != null) {
            log.debug("get: catchup with runner");
            return new CachedObject(ofn, (RunnerObject) obj);
        }
        // output in cache and source not newer than cache
        if (ofile.exists() && ifile.lastModified() < ofile.lastModified()) {
            log.debug("get: cached data");
            return new CachedObject(ofn);
        }
        // create new MediaObject
        obj = MediaFactory.createObject(req, ofn);
        if (obj instanceof RunnerObject) {
            RunnerObject ro = (RunnerObject) obj;

            // make sure output file can be writen
            ensureCacheDirectoryFor(ofn);

            // register change listener
            runners.put(ofn, ro);
            ro.addRunnerChangeListener(this);

            // add runner to list and start converter
            ro.start();

            if (log.isDebugEnabled()) {
                log.debug("get: started runner: " + ro);
            }
            return obj;
        }

        // not a runner probably original
        log.debug("get: passing object from media factory");
        return obj;
    }

    public String getRemoteObjectFile(URL url) throws ScillaException {
        String suffix = MimeType.getExtensionFromFilename(url.toString());
        String fn = getCachedRemoteObjectFilename(url, suffix);

        // download object if not availble in cache
        if (!(new File(fn)).exists()) {
            if (log.isDebugEnabled()) {
                log.debug("fetching: " + url);
            }

            InputStream in = null;
            OutputStream out = null;
            try {
                ensureCacheDirectoryFor(fn);
                out = new FileOutputStream(fn);
                in = url.openStream();
                byte[] b = new byte[BUFFER_SIZE];
                int n = 0;
                while ((n = in.read(b)) != -1) {
                    out.write(b, 0, n);
                }
            } catch (Throwable ex) {
                log.warn("unable to fetch remote object", ex);
                throw new ScillaException("unable to fetch remote object", ex);
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException ex) {
                        // ignore
                    }
                }
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException ex) {
                        // ignore
                    }
                }
            }
        }
        return fn;
    }

    /**
     * Eventhandler for runner change events.
     * @param ro runner object where event came from
     * @param code event code
     * @see RunnerChangeListener#RUNNER_FINISHED
     */
    public void runnerChange(RunnerObject ro, int code) {
        if (code == RUNNER_FINISHED) {
            if (log.isDebugEnabled()) {
                log.debug("runnerChange: runner finished: " + ro);
            }
            // remove runner for list
            runners.remove(ro.getOutputFile());
        } else {
            log.warn("runnerChange: unhandled runner change: " + code + ": " + ro);
        }
    }

    /** the runner list */
    private Map runners = new Hashtable();

    /** property for configuring the maximum filename length when creating cache filenames */
    public final static String MAX_FN_LEN_KEY = "cache.filenamelen.max";
    /** default maximum filename length */
    public static int maxFilenameLen = 32;

    // try to set max filename len from config
    static {
        if (config.containsKey(MAX_FN_LEN_KEY)) {
            try {
                maxFilenameLen = config.getInt(MAX_FN_LEN_KEY);
            } catch (NumberFormatException e) {
                log.error(e);
            }
        }
    }

    /** name of directory (in cache directory) to contain cached media objects */
    public final static String CACHE_DIRNAME = "cache";
    /** name of directory (in cache directory) to contain cached remote objects */
    public final static String REMOTE_DIRNAME = "remote";

    /** buffer size when fetching remote objects */
    private static final int BUFFER_SIZE = 4096;

    /**
     * Create a uniq cache filename for given request.
     * @param req request to make filename for
     * @return absolute filename for object
     */
    private String getCachedObjectFilename(Request req) {
        StringBuffer result = new StringBuffer(req.getInputFile());
        result.append("?");

        // append parameters to result
        for (Iterator it = req.getParameters().iterator(); it.hasNext();) {
            RequestParameter rp = (RequestParameter) it.next();
            result.append(rp.key);
            result.append("=");
            result.append(rp.val);
            if (it.hasNext()) {
                result.append("&");
            }
        }
        String fn = result.toString();

        // make sure it's a legal filename
        fn = fnEncodeCharacters(fn);

        // append suffix to fool simple OS converters
        fn += "." + MimeType.getExtensionForType(req.getOutputType());

        // make sure max filename length is not violated
        fn = fnChopIntoDirectories(fn);

        // prepend cache path
        fn = config.getString(Config.CACHE_DIR_KEY) + File.separator + CACHE_DIRNAME + File.separator + fn;

        if (log.isDebugEnabled()) {
            log.debug("getCachedObjectFilename=" + fn);
        }
        return fn;
    }

    /**
     * Create a uniq cache filename for given remote object.
     * @param url location of object
     * @return absolute filename for object
     */
    private String getCachedRemoteObjectFilename(URL url, String suffix) {
        // make legal filename from url and suffix
        String fn = fnChopIntoDirectories(fnEncodeCharacters(url.toString()) + "." + suffix);

        // prepend cache path
        fn = config.getString(Config.CACHE_DIR_KEY) + File.separator + REMOTE_DIRNAME + File.separator + fn;

        if (log.isDebugEnabled()) {
            log.debug("getCachedRemoteObjectFilename=" + fn);
        }
        return fn;
    }

    private static String fnEncodeCharacters(String in) {
        // encode this to a legal filename
        StringBuffer out = new StringBuffer();
        char[] data = in.toCharArray();
        for (int i = 0; i < data.length; i++) {
            if (Character.isLetterOrDigit(data[i])) {
                out.append(data[i]);
            } else {
                out.append("_" + (int) data[i]);
            }
        }
        return out.toString();
    }

    private static String fnChopIntoDirectories(String in) {
        // hold suffix
        int suffixIdx = in.lastIndexOf('.');
        String suffix = in.substring(suffixIdx);
        int suffixLen = suffix.length();
        String t = in.substring(0, suffixIdx);

        // chopup, making directories using maxFilenameLen
        StringBuffer out = new StringBuffer();
        char[] data = t.toCharArray();
        int i = 0;
        for (; i < data.length; i++) {
            if (i % maxFilenameLen == 0) {
                out.append(File.separator);
            }
            out.append(data[i]);
        }

        // avoid file/ directory clash
        for (; (i % maxFilenameLen) + suffixLen > maxFilenameLen; i++) {
            out.append('x');
        }
        if (i % maxFilenameLen == 0) {
            out.append(File.separator);
            out.append('x');
        }

        // reintroduce suffix
        out.append(suffix);

        return out.toString();
    }

    /**
     * Make sure given filename has directory to live in.
     * @param fn name of file to make directory for
     */
    private static void ensureCacheDirectoryFor(String fn) {
        if (log.isDebugEnabled()) {
            log.debug("ensureCacheDirectoryFor(" + fn + ")");
        }

        String path = fn.substring(0, fn.lastIndexOf(File.separator));
        (new File(path)).mkdirs();
    }

    /**
     * Debugging..
     */
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++) {
            String fn = fnChopIntoDirectories(fnEncodeCharacters(args[i]) + ".bla");
            System.out.println("" + fn);
        }
    }
}