blob: 3c39c7753c90e7f30a8b1408dfc0ca10cb153a72 [file] [log] [blame]
/*
* 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();
}
}