blob: 1afbfe980c25dfa29f7c24134e1bb7413b0039d8 [file] [log] [blame]
/*
* 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.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
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.framework.context.DefaultContext;
import org.apache.avalon.framework.logger.ConsoleLogger;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.cocoon.Cocoon;
import org.apache.cocoon.Constants;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.Processor;
import org.apache.cocoon.core.BootstrapEnvironment;
import org.apache.cocoon.core.CoreUtil;
import org.apache.cocoon.core.MutableSettings;
import org.apache.cocoon.environment.Context;
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.environment.internal.EnvironmentHelper;
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.xml.sax.ContentHandler;
/**
* The Cocoon Wrapper simplifies usage of the Cocoon object. Allows to create,
* configure Cocoon instance and process single requests.
*
* @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";
private String userAgent = DEFAULT_USER_AGENT;
private String accept = DEFAULT_ACCEPT;
private List classList = new ArrayList();
// Objects used alongside User Supplied Parameters
private File context;
private File work;
private File conf;
// Internal Objects
private CommandLineContext cliContext;
private Cocoon cocoon;
protected Logger log;
private HashMap empty = new HashMap();
private boolean initialized = false;
private CoreUtil coreUtil;
/**
* INITIALISATION METHOD.
*/
public void initialize() throws Exception {
// Install a temporary logger so that getDir() can log if needed
final BootstrapEnvironment.LogLevel level = BootstrapEnvironment.LogLevel.getLogLevelForName(this.logLevel);
final Logger envLogger = new ConsoleLogger(level.getLevel());
this.log = envLogger;
this.context = getDir(this.contextDir, "context");
this.work = getDir(workDir, "working");
this.conf = getConfigurationFile(this.context, this.configFile);
cliContext = new CommandLineContext(contextDir);
cliContext.enableLogging(envLogger);
// setup Cocoon core
File cacheDir = getDir(workDir + File.separator + "cache-dir", "cache");
WrapperBootstrapper env = this.getBootstrapEnvironment();
env.setContextDirectory(contextDir);
env.setEnvironmentLogger(envLogger);
env.setEnvironmentContext(cliContext);
env.setWorkingDirectory(this.work);
env.setCachingDirectory(cacheDir);
env.setBootstrapLogLevel(this.logLevel);
env.setLoggingConfiguration(this.logKit);
env.setConfigFile(this.conf);
env.setLoadClassList(this.classList);
this.coreUtil = new CoreUtil(env);
this.cocoon = this.coreUtil.createCocoon();
this.log = env.logger;
this.initialized = true;
}
protected ServiceManager getServiceManager() {
return cocoon.getServiceManager();
}
/**
* 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.canRead()) {
conf = new File(dir, configFile);
if (!conf.canRead()) {
conf = null;
}
}
}
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;
}
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();
}
//
// 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);
}
/**
* 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;
this.coreUtil.destroy();
this.cocoon = null;
this.coreUtil = null;
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);
Processor.InternalPipelineDescription pipeline = cocoon.buildPipeline(env);
EnvironmentHelper.enterProcessor(pipeline.lastProcessor, pipeline.pipelineManager, env);
try {
pipeline.processingPipeline.prepareInternal(env);
pipeline.processingPipeline.process(env, consumer);
} finally {
EnvironmentHelper.leaveProcessor();
}
// 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 {
// ignore
}
public void write(byte b[]) throws IOException {
// ignore
}
public void write(byte b[], int off, int len) throws IOException {
// ignore
}
}
/**
* 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();
}
protected WrapperBootstrapper getBootstrapEnvironment() {
return new WrapperBootstrapper();
}
/**
* This class provides wrapper specific environment information
*
*/
public static class WrapperBootstrapper implements BootstrapEnvironment {
public Logger logger;
protected Logger environmentLogger;
protected Context environmentContext;
protected String workingDirectory;
protected String bootstrapLogLevel;
protected String loggingConfiguration;
protected String cachingDirectory;
protected String contextDirectory;
protected String configFile;
protected List loadClassList;
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#getBootstrapLogger(org.apache.cocoon.core.BootstrapEnvironment.LogLevel)
*/
public Logger getBootstrapLogger(LogLevel logLevel) {
return new ConsoleLogger(logLevel.getLevel());
}
public void setEnvironmentLogger(Logger log) {
this.environmentLogger = log;
}
public void setEnvironmentContext(Context context) {
this.environmentContext = context;
}
public void setWorkingDirectory(File dir) {
this.workingDirectory = dir.getAbsolutePath();
}
public void setBootstrapLogLevel(String bootstrapLogLevel) {
this.bootstrapLogLevel = bootstrapLogLevel;
}
public void setLoggingConfiguration(String config) {
this.loggingConfiguration = config;
}
public void setCachingDirectory(File dir) {
this.cachingDirectory = dir.getAbsolutePath();
}
public void setContextDirectory(String dir) {
this.contextDirectory = dir;
}
public void setConfigFile(File file) {
this.configFile = file.getAbsolutePath();
}
public void setLoadClassList(List l) {
this.loadClassList = l;
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#configure(org.apache.avalon.framework.context.DefaultContext)
*/
public void configure(DefaultContext context) {
// nothing to add
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#configure(org.apache.cocoon.core.MutableSettings)
*/
public void configure(MutableSettings settings) {
settings.setWorkDirectory(this.workingDirectory);
settings.setCacheDirectory(this.cachingDirectory);
settings.setUploadDirectory(this.contextDirectory + "upload-dir");
settings.setBootstrapLogLevel(this.bootstrapLogLevel);
settings.setLoggingConfiguration(this.loggingConfiguration);
settings.setFormEncoding("ISO-8859-1");
settings.setConfiguration(this.configFile);
if ( this.loadClassList != null ) {
final Iterator i = this.loadClassList.iterator();
while ( i.hasNext() ) {
settings.addToLoadClasses(i.next().toString());
}
}
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#configureLoggingContext(org.apache.avalon.framework.context.DefaultContext)
*/
public void configureLoggingContext(DefaultContext context) {
// nothing to add
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#getConfigFile(java.lang.String)
*/
public URL getConfigFile(String configFileName) throws Exception {
return new File(configFileName).toURL();
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#getContextForWriting()
*/
public File getContextForWriting() {
return new File(this.contextDirectory);
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#getContextURL()
*/
public String getContextURL() {
try {
return new File(this.contextDirectory).toURL().toExternalForm();
} catch (MalformedURLException mue) {
return "file://" + this.contextDirectory;
}
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#getEnvironmentContext()
*/
public Context getEnvironmentContext() {
return this.environmentContext;
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#log(java.lang.String, java.lang.Throwable)
*/
public void log(String message, Throwable error) {
this.environmentLogger.error(message, error);
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#log(java.lang.String)
*/
public void log(String message) {
this.environmentLogger.debug(message);
}
/**
* @see org.apache.cocoon.core.BootstrapEnvironment#setLogger(org.apache.avalon.framework.logger.Logger)
*/
public void setLogger(Logger rootLogger) {
this.logger = rootLogger;
}
}
}