org.apache.cocoon.bean.CocoonWrapper.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.cocoon.bean.CocoonWrapper.java

Source

/*
 * Copyright 1999-2005 The Apache Software Foundation.
 *
 * 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 org.apache.cocoon.bean;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.avalon.excalibur.component.ExcaliburComponentManager;
import org.apache.avalon.excalibur.logger.LogKitLoggerManager;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.avalon.framework.container.ContainerUtil;
import org.apache.avalon.framework.context.DefaultContext;
import org.apache.avalon.framework.logger.LogKitLogger;
import org.apache.avalon.framework.logger.Logger;

import org.apache.cocoon.Cocoon;
import org.apache.cocoon.CocoonAccess;
import org.apache.cocoon.Constants;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.components.CocoonComponentManager;
import org.apache.cocoon.components.pipeline.ProcessingPipeline;
import org.apache.cocoon.environment.Environment;
import org.apache.cocoon.environment.commandline.CommandLineContext;
import org.apache.cocoon.environment.commandline.FileSavingEnvironment;
import org.apache.cocoon.environment.commandline.LinkSamplingEnvironment;
import org.apache.cocoon.util.ClassUtils;
import org.apache.cocoon.util.IOUtils;
import org.apache.cocoon.util.NetUtils;
import org.apache.cocoon.xml.ContentHandlerWrapper;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.commons.lang.SystemUtils;

import org.apache.log.Hierarchy;
import org.apache.log.Priority;
import org.xml.sax.ContentHandler;

/**
 * The Cocoon Wrapper simplifies usage of the Cocoon object. Allows to create,
 * configure Cocoon instance and process single requests.
 *
 * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
 * @author <a href="mailto:nicolaken@apache.org">Nicola Ken Barozzi</a>
 * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
 * @author <a href="mailto:uv@upaya.co.uk">Upayavira</a>
 * @version $Id: CocoonWrapper.java 371534 2006-01-23 12:19:55Z jbq $
 */
public class CocoonWrapper {

    protected static final String DEFAULT_USER_AGENT = Constants.COMPLETE_NAME;
    protected static final String DEFAULT_ACCEPT = "text/html, */*";

    // User Supplied Parameters
    private String contextDir = Constants.DEFAULT_CONTEXT_DIR;
    private String configFile = null;

    private String workDir = Constants.DEFAULT_WORK_DIR;
    private String logKit = null;
    protected String logger = null;
    protected String logLevel = "ERROR";
    private String userAgent = DEFAULT_USER_AGENT;
    private String accept = DEFAULT_ACCEPT;
    private List classList = new ArrayList();

    // Objects used alongside User Supplied Parameters
    protected File context;
    private File work;
    private File conf;

    // Internal Objects
    protected CommandLineContext cliContext;
    private LogKitLoggerManager logManager;
    protected Cocoon cocoon;
    protected Logger log;
    private HashMap empty = new HashMap();

    private boolean initialized = false;
    private boolean useExistingCocoon = false;

    //
    // INITIALISATION METHOD
    //
    public void initialize() throws Exception {
        // @todo@ these should log then throw exceptions back to the caller, not use system.exit()

        // Create a new hierarchy. This is needed when CocoonBean is called from
        // within a CocoonServlet call, in order not to mix logs
        final Hierarchy hierarchy = new Hierarchy();

        final Priority priority = Priority.getPriorityForName(logLevel);
        hierarchy.setDefaultPriority(priority);

        // Install a temporary logger so that getDir() can log if needed
        this.log = new LogKitLogger(hierarchy.getLoggerFor(""));

        try {
            // First of all, initialize the logging system

            // Setup the application context with context-dir and work-dir that
            // can be used in logkit.xconf
            this.context = getDir(this.contextDir, "context");
            this.work = getDir(workDir, "working");
            DefaultContext appContext = new DefaultContext();
            appContext.put(Constants.CONTEXT_WORK_DIR, work);

            this.logManager = new LogKitLoggerManager(hierarchy);
            this.logManager.enableLogging(log);

            if (this.logKit != null) {
                final FileInputStream fis = new FileInputStream(logKit);
                final DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
                final Configuration logKitConf = builder.build(fis);
                final DefaultContext subcontext = new DefaultContext(appContext);
                subcontext.put("context-root", this.contextDir);
                subcontext.put("context-work", this.workDir);
                this.logManager.contextualize(subcontext);
                this.logManager.configure(logKitConf);
                if (logger != null) {
                    log = this.logManager.getLoggerForCategory(logger);
                } else {
                    log = this.logManager.getLoggerForCategory("cocoon");
                }
            }

            this.conf = getConfigurationFile(this.context, this.configFile);

            cliContext = new CommandLineContext(contextDir);
            cliContext.enableLogging(log);

            appContext.put(Constants.CONTEXT_ENVIRONMENT_CONTEXT, cliContext);
            appContext.put(Constants.CONTEXT_CLASS_LOADER, CocoonWrapper.class.getClassLoader());
            appContext.put(Constants.CONTEXT_CLASSPATH, getClassPath(contextDir));
            appContext.put(Constants.CONTEXT_UPLOAD_DIR, contextDir + "upload-dir");
            File cacheDir = getDir(workDir + File.separator + "cache-dir", "cache");
            appContext.put(Constants.CONTEXT_CACHE_DIR, cacheDir);
            appContext.put(Constants.CONTEXT_CONFIG_URL, conf.toURL());
            appContext.put(Constants.CONTEXT_DEFAULT_ENCODING, "ISO-8859-1");

            loadClasses(classList);

            if (this.useExistingCocoon) {
                cocoon = getCocoon();
            }
            if (cocoon == null) {
                cocoon = new Cocoon();
                ContainerUtil.enableLogging(cocoon, log);
                ContainerUtil.contextualize(cocoon, appContext);
                cocoon.setLoggerManager(logManager);
                ContainerUtil.initialize(cocoon);
            }
        } catch (Exception e) {
            log.fatalError("Exception caught", e);
            throw e;
        }
        initialized = true;
    }

    private Cocoon getCocoon() {
        return new CocoonInstance().instance();
    }

    private static class CocoonInstance extends CocoonAccess {
        final Cocoon instance() {
            return super.getCocoon();
        }
    }

    protected ExcaliburComponentManager getComponentManager() {
        return cocoon.getComponentManager();
    }

    /**
     * Look around for the configuration file.
     *
     * @param dir a <code>File</code> where to look for configuration files
     * @return a <code>File</code> representing the configuration
     * @exception IOException if an error occurs
     */
    private File getConfigurationFile(File dir, String configFile) throws IOException {
        File conf;
        if (configFile == null) {
            conf = tryConfigurationFile(dir + File.separator + Constants.DEFAULT_CONF_FILE);
            if (conf == null) {
                conf = tryConfigurationFile(
                        dir + File.separator + "WEB-INF" + File.separator + Constants.DEFAULT_CONF_FILE);
            }
            if (conf == null) {
                conf = tryConfigurationFile(SystemUtils.USER_DIR + File.separator + Constants.DEFAULT_CONF_FILE);
            }
            if (conf == null) {
                conf = tryConfigurationFile("/usr/local/etc/" + Constants.DEFAULT_CONF_FILE);
            }
        } else {
            conf = new File(configFile);
            if (!conf.exists()) {
                conf = new File(dir, configFile);
            }
        }
        if (conf == null) {
            log.error("Could not find the configuration file.");
            throw new FileNotFoundException("The configuration file could not be found.");
        }
        return conf;
    }

    /**
     * Try loading the configuration file from a single location
     */
    private File tryConfigurationFile(String filename) {
        if (log.isDebugEnabled()) {
            log.debug("Trying configuration file at: " + filename);
        }
        File conf = new File(filename);
        if (conf.canRead()) {
            return conf;
        } else {
            return null;
        }
    }

    /**
     * Get a <code>File</code> representing a directory.
     *
     * @param dir a <code>String</code> with a directory name
     * @param type a <code>String</code> describing the type of directory
     * @return a <code>File</code> value
     * @exception IOException if an error occurs
     */
    private File getDir(String dir, String type) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("Getting handle to " + type + " directory '" + dir + "'");
        }
        File d = new File(dir);

        if (!d.exists()) {
            if (!d.mkdirs()) {
                throw new IOException("Error creating " + type + " directory '" + d + "'");
            }
        }

        if (!d.isDirectory()) {
            throw new IOException("'" + d + "' is not a directory.");
        }

        if (!d.canRead()) {
            throw new IOException("Directory '" + d + "' is not readable");
        }

        if ("working".equals(type) && !d.canWrite()) {
            throw new IOException("Directory '" + d + "' is not writable");
        }

        return d;
    }

    protected void finalize() throws Throwable {
        dispose();
        super.finalize();
    }

    protected void loadClasses(List classList) {
        if (classList != null) {
            for (Iterator i = classList.iterator(); i.hasNext();) {
                String className = (String) i.next();
                try {
                    if (log.isDebugEnabled()) {
                        log.debug("Trying to load class: " + className);
                    }
                    ClassUtils.loadClass(className).newInstance();
                } catch (Exception e) {
                    if (log.isWarnEnabled()) {
                        log.warn("Could not force-load class: " + className, e);
                    }
                    // Do not throw an exception, because it is not a fatal error.
                }
            }
        }
    }

    //
    // GETTERS AND SETTERS FOR CONFIGURATION PROPERTIES
    //

    /**
     * Set LogKit configuration file name
     * @param logKit LogKit configuration file
     */
    public void setLogKit(String logKit) {
        this.logKit = logKit;
    }

    /**
     * Set log level. Default is DEBUG.
     * @param logLevel log level
     */
    public void setLogLevel(String logLevel) {
        this.logLevel = logLevel;
    }

    /**
     * Set logger category as default logger for the Cocoon engine
     * @param logger logger category
     */
    public void setLogger(String logger) {
        this.logger = logger;
    }

    public String getLoggerName() {
        return logger;
    }

    /**
     * Set context directory
     * @param contextDir context directory
     */
    public void setContextDir(String contextDir) {
        this.contextDir = contextDir;
    }

    /**
     * Set working directory
     * @param workDir working directory
     */
    public void setWorkDir(String workDir) {
        this.workDir = workDir;
    }

    public void setConfigFile(String configFile) {
        this.configFile = configFile;
    }

    public void setAgentOptions(String userAgent) {
        this.userAgent = userAgent;
    }

    public void setAcceptOptions(String accept) {
        this.accept = accept;
    }

    public void addLoadedClass(String className) {
        this.classList.add(className);
    }

    public void addLoadedClasses(List classList) {
        this.classList.addAll(classList);
    }

    public void setUseExistingCocoon(boolean useExistingCocoon) {
        this.useExistingCocoon = useExistingCocoon;
    }

    /**
     * Process single URI into given output stream.
     *
     * @param uri to process
     * @param outputStream to write generated contents into
     */
    public void processURI(String uri, OutputStream outputStream) throws Exception {

        if (!initialized) {
            initialize();
        }
        log.info("Processing URI: " + uri);

        // Get parameters, deparameterized URI and path from URI
        final TreeMap parameters = new TreeMap();
        final String deparameterizedURI = NetUtils.deparameterize(uri, parameters);
        parameters.put("user-agent", userAgent);
        parameters.put("accept", accept);

        int status = getPage(deparameterizedURI, 0L, parameters, null, null, outputStream);

        if (status >= 400) {
            throw new ProcessingException("Resource not found: " + status);
        }
    }

    /**
     * Process single URI into given content handler, skipping final
     * serializer
     *
     * @param uri to process
     * @param handler to write generated contents into
     */
    public void processURI(String uri, ContentHandler handler) throws Exception {

        if (!initialized) {
            initialize();
        }
        log.info("Processing URI: " + uri);

        // Get parameters, deparameterized URI and path from URI
        final TreeMap parameters = new TreeMap();
        final String deparameterizedURI = NetUtils.deparameterize(uri, parameters);
        parameters.put("user-agent", userAgent);
        parameters.put("accept", accept);

        int status = getPage(deparameterizedURI, 0L, parameters, null, null, handler);

        if (status >= 400) {
            throw new ProcessingException("Resource not found: " + status);
        }
    }

    public void dispose() {
        if (this.initialized) {
            this.initialized = false;
            ContainerUtil.dispose(this.cocoon);
            this.cocoon = null;
            this.logManager.dispose();
            if (log.isDebugEnabled()) {
                log.debug("Disposed");
            }
        }
    }

    /**
     * Samples an URI for its links.
     *
     * @param deparameterizedURI a <code>String</code> value of an URI to start sampling from
     * @param parameters a <code>Map</code> value containing request parameters
     * @return a <code>Collection</code> of links
     * @exception Exception if an error occurs
     */
    protected Collection getLinks(String deparameterizedURI, Map parameters) throws Exception {

        parameters.put("user-agent", userAgent);
        parameters.put("accept", accept);

        LinkSamplingEnvironment env = new LinkSamplingEnvironment(deparameterizedURI, context, null, parameters,
                cliContext, log);
        processLenient(env);
        return env.getLinks();
    }

    /**
     * Processes an URI for its content.
     *
     * @param deparameterizedURI a <code>String</code> value of an URI to start sampling from
     * @param parameters a <code>Map</code> value containing request parameters
     * @param links a <code>Map</code> value
     * @param stream an <code>OutputStream</code> to write the content to
     * @return a <code>String</code> value for the content
     * @exception Exception if an error occurs
     */
    protected int getPage(String deparameterizedURI, long lastModified, Map parameters, Map links,
            List gatheredLinks, OutputStream stream) throws Exception {

        parameters.put("user-agent", userAgent);
        parameters.put("accept", accept);

        FileSavingEnvironment env = new FileSavingEnvironment(deparameterizedURI, lastModified, context, null,
                parameters, links, gatheredLinks, cliContext, stream, log);

        // Here Cocoon can throw an exception if there are errors in processing the page
        cocoon.process(env);

        // if we get here, the page was created :-)
        int status = env.getStatus();
        if (!env.isModified()) {
            status = -1;
        }
        return status;
    }

    /**
     * Processes an URI for its content.
     *
     * @param deparameterizedURI a <code>String</code> value of an URI to start sampling from
     * @param parameters a <code>Map</code> value containing request parameters
     * @param links a <code>Map</code> value
     * @param handler an <code>ContentHandler</code> to send the content to
     * @return a <code>String</code> value for the content
     * @exception Exception if an error occurs
     */
    protected int getPage(String deparameterizedURI, long lastModified, Map parameters, Map links,
            List gatheredLinks, ContentHandler handler) throws Exception {

        parameters.put("user-agent", userAgent);
        parameters.put("accept", accept);

        FileSavingEnvironment env = new FileSavingEnvironment(deparameterizedURI, lastModified, context, null,
                parameters, links, gatheredLinks, cliContext, null, log);

        XMLConsumer consumer = new ContentHandlerWrapper(handler);
        ProcessingPipeline pipeline = cocoon.buildPipeline(env);
        CocoonComponentManager.enterEnvironment(env, cocoon.getComponentManager(), cocoon);
        try {
            pipeline.prepareInternal(env);
            pipeline.process(env, consumer);
        } finally {
            CocoonComponentManager.leaveEnvironment();
        }

        // if we get here, the page was created :-)
        int status = env.getStatus();
        if (!env.isModified()) {
            status = -1;
        }
        return status;
    }

    /** Class <code>NullOutputStream</code> here. */
    static class NullOutputStream extends OutputStream {
        public void write(int b) throws IOException {
        }

        public void write(byte b[]) throws IOException {
        }

        public void write(byte b[], int off, int len) throws IOException {
        }
    }

    /**
     * Analyze the type of content for an URI.
     *
     * @param deparameterizedURI a <code>String</code> value to analyze
     * @param parameters a <code>Map</code> value for the request
     * @return a <code>String</code> value denoting the type of content
     * @exception Exception if an error occurs
     */
    protected String getType(String deparameterizedURI, Map parameters) throws Exception {

        parameters.put("user-agent", userAgent);
        parameters.put("accept", accept);

        FileSavingEnvironment env = new FileSavingEnvironment(deparameterizedURI, context, null, parameters, empty,
                null, cliContext, new NullOutputStream(), log);
        processLenient(env);
        return env.getContentType();
    }

    /**
     * Try to process something but don't throw a ProcessingException.
     *
     * @param env the <code>Environment</code> to process
     * @return boolean true if no error were cast, false otherwise
     * @exception Exception if an error occurs, except RNFE
     */
    private boolean processLenient(Environment env) throws Exception {
        try {
            this.cocoon.process(env);
        } catch (ProcessingException pe) {
            return false;
        }
        return true;
    }

    /**
     * This builds the important ClassPath used by this class.  It
     * does so in a neutral way.
     * It iterates in alphabetical order through every file in the
     * lib directory and adds it to the classpath.
     *
     * Also, we add the files to the ClassLoader for the Cocoon system.
     * In order to protect ourselves from skitzofrantic classloaders,
     * we need to work with a known one.
     *
     * @param context  The context path
     * @return a <code>String</code> value
     */
    protected String getClassPath(final String context) {
        StringBuffer buildClassPath = new StringBuffer();

        String classDir = context + "/WEB-INF/classes";
        buildClassPath.append(classDir);

        File root = new File(context + "/WEB-INF/lib");
        if (root.isDirectory()) {
            File[] libraries = root.listFiles();
            Arrays.sort(libraries);
            for (int i = 0; i < libraries.length; i++) {
                if (libraries[i].getAbsolutePath().endsWith(".jar")) {
                    buildClassPath.append(File.pathSeparatorChar).append(IOUtils.getFullFilename(libraries[i]));
                }
            }
        }

        buildClassPath.append(File.pathSeparatorChar).append(SystemUtils.JAVA_CLASS_PATH);

        // Extra class path is necessary for non-classloader-aware java compilers to compile XSPs
        //        buildClassPath.append(File.pathSeparatorChar)
        //                      .append(getExtraClassPath(context));

        if (log.isDebugEnabled()) {
            log.debug("Context classpath: " + buildClassPath);
        }
        return buildClassPath.toString();
    }
}