| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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$ |
| */ |
| 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"; |
| protected String userAgent = DEFAULT_USER_AGENT; |
| protected 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, headers, deparameterized URI and path from URI |
| final TreeMap parameters = new TreeMap(); |
| final TreeMap headers = new TreeMap(); |
| final String deparameterizedURI = |
| NetUtils.deparameterize(uri, parameters); |
| headers.put("user-agent", userAgent); |
| headers.put("accept", accept); |
| |
| int status = |
| getPage(deparameterizedURI, 0L, parameters, headers, 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, headers, deparameterized URI and path from URI |
| final TreeMap parameters = new TreeMap(); |
| final TreeMap headers = new TreeMap(); |
| final String deparameterizedURI = |
| NetUtils.deparameterize(uri, parameters); |
| headers.put("user-agent", userAgent); |
| headers.put("accept", accept); |
| |
| int status = |
| getPage(deparameterizedURI, 0L, parameters, headers, 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 { |
| |
| final TreeMap headers = new TreeMap(); |
| headers.put("user-agent", userAgent); |
| headers.put("accept", accept); |
| |
| LinkSamplingEnvironment env = |
| new LinkSamplingEnvironment(deparameterizedURI, context, null, |
| parameters, headers, 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 headers, |
| Map links, |
| List gatheredLinks, |
| OutputStream stream) |
| throws Exception { |
| |
| headers.put("user-agent", userAgent); |
| headers.put("accept", accept); |
| |
| FileSavingEnvironment env = |
| new FileSavingEnvironment(deparameterizedURI, lastModified, context, |
| null, parameters, headers, 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 headers, |
| Map links, |
| List gatheredLinks, |
| ContentHandler handler) |
| throws Exception { |
| FileSavingEnvironment env = |
| new FileSavingEnvironment(deparameterizedURI, lastModified, context, |
| null, parameters, headers, 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 { |
| |
| final TreeMap headers = new TreeMap(); |
| headers.put("user-agent", userAgent); |
| headers.put("accept", accept); |
| |
| FileSavingEnvironment env = |
| new FileSavingEnvironment(deparameterizedURI, context, null, |
| parameters, headers, 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(); |
| } |
| } |