/*
============================================================================
The Apache Software License, Version 1.1
============================================================================
Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved.
Redistribution and use in source and binary forms, with or without modifica-
tion, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The end-user documentation included with the redistribution, if any, must
include the following acknowledgment: "This product includes software
developed by the Apache Software Foundation (http://www.apache.org/)."
Alternately, this acknowledgment may appear in the software itself, if
and wherever such third-party acknowledgments normally appear.
4. The names "Apache Cocoon" and "Apache Software Foundation" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
apache@apache.org.
5. Products derived from this software may not be called "Apache", nor may
"Apache" appear in their name, without prior written permission of the
Apache Software Foundation.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This software consists of voluntary contributions made by many individuals
on behalf of the Apache Software Foundation and was originally created by
Stefano Mazzocchi <stefano@apache.org>. For more information on the Apache
Software Foundation, please see <http://www.apache.org/>.
*/
package org.apache.cocoon;
import org.apache.avalon.excalibur.xml.Parser;
import org.apache.avalon.excalibur.component.DefaultRoleManager;
import org.apache.avalon.excalibur.component.ExcaliburComponentManager;
import org.apache.avalon.excalibur.logger.LogKitManageable;
import org.apache.avalon.excalibur.logger.LogKitManager;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.SAXConfigurationHandler;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.logger.AbstractLoggable;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.cocoon.components.CocoonComponentManager;
import org.apache.cocoon.components.language.generator.CompiledComponent;
import org.apache.cocoon.components.language.generator.ProgramGenerator;
import org.apache.cocoon.components.pipeline.EventPipeline;
import org.apache.cocoon.components.pipeline.StreamPipeline;
import org.apache.cocoon.components.source.DelayedRefreshSourceWrapper;
import org.apache.cocoon.components.source.SourceHandler;
import org.apache.cocoon.components.source.URLSource;
import org.apache.cocoon.environment.Environment;
import org.apache.cocoon.environment.ModifiableSource;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.Session;
import org.apache.cocoon.sitemap.SitemapManager;
import org.apache.cocoon.util.ClassUtils;
import org.xml.sax.InputSource;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
/**
* The Cocoon Object is the main Kernel for the entire Cocoon system.
*
* @author <a href="mailto:fumagalli@exoffice.com">Pierpaolo Fumagalli</a> (Apache Software Foundation, Exoffice Technologies)
* @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
* @author <a href="mailto:leo.sutic@inspireinfrastructure.com">Leo Sutic</a>
* @version CVS $Id: Cocoon.java,v 1.22.2.2 2002/06/19 13:48:33 vgritsenko Exp $
*/
public class Cocoon
extends AbstractLoggable
implements ThreadSafe,
Component,
Initializable,
Disposable,
Modifiable,
Processor,
Contextualizable,
Composable,
LogKitManageable {
/** The application context */
private Context context;
/** The configuration file */
private ModifiableSource configurationFile;
/** The configuration tree */
private Configuration configuration;
/** The logkit manager */
private LogKitManager logKitManager;
/** The classpath (null if not available) */
private String classpath;
/** The working directory (null if not available) */
private File workDir;
/** The component manager. */
private ExcaliburComponentManager componentManager;
/** The parent component manager. */
private ComponentManager parentComponentManager;
/** flag for disposed or not */
private boolean disposed = false;
/** active request count */
private volatile int activeRequestCount = 0;
/** the Processor if it is ThreadSafe */
private Processor threadSafeProcessor = null;
/**
* Creates a new <code>Cocoon</code> instance.
*
* @exception ConfigurationException if an error occurs
*/
public Cocoon() throws ConfigurationException {
// Set the system properties needed by Xalan2.
setSystemProperties();
}
/**
* Get the parent component manager. For purposes of
* avoiding extra method calls, the manager parameter may be null.
*
* @param manager the parent component manager. May be <code>null</code>
*/
public void compose(ComponentManager manager) {
this.parentComponentManager = manager;
}
/**
* Describe <code>contextualize</code> method here.
*
* @param context a <code>Context</code> value
* @exception ContextException if an error occurs
*/
public void contextualize(Context context) throws ContextException {
if (this.context == null) {
this.context = context;
this.classpath = (String)context.get(Constants.CONTEXT_CLASSPATH);
this.workDir = (File)context.get(Constants.CONTEXT_WORK_DIR);
try {
// FIXME : add a configuration option for the refresh delay.
// for now, hard-coded to 1 second.
this.configurationFile = new DelayedRefreshSourceWrapper(
new URLSource((URL)context.get(Constants.CONTEXT_CONFIG_URL), this.componentManager),
1000L
);
} catch (IOException ioe) {
getLogger().error("Could not open configuration file.", ioe);
throw new ContextException("Could not open configuration file.", ioe);
} catch (Exception e) {
getLogger().error("contextualize(..) Exception", e);
throw new ContextException("contextualize(..) Exception", e);
}
}
}
/**
* The <code>setLogKitManager</code> method will get a <code>LogKitManager</code>
* for further use.
*
* @param logKitManager a <code>LogKitManager</code> value
*/
public void setLogKitManager(LogKitManager logKitManager) {
this.logKitManager = logKitManager;
}
/**
* The <code>initialize</code> method
*
* @exception Exception if an error occurs
*/
public void initialize() throws Exception {
if (parentComponentManager != null) {
this.componentManager = new CocoonComponentManager(parentComponentManager,(ClassLoader)this.context.get(Constants.CONTEXT_CLASS_LOADER));
} else {
this.componentManager = new CocoonComponentManager((ClassLoader)this.context.get(Constants.CONTEXT_CLASS_LOADER));
}
this.componentManager.setLogger(getLogger().getChildLogger("manager"));
this.componentManager.contextualize(this.context);
if (getLogger().isDebugEnabled()) {
getLogger().debug("New Cocoon object.");
}
// Log the System Properties.
dumpSystemProperties();
// Setup the default parser, for parsing configuration.
// If one need to use a different parser, set the given system property
// first check for deprecated property to be compatible:
String parser = System.getProperty(Constants.DEPRECATED_PARSER_PROPERTY, Constants.DEFAULT_PARSER);
if ( !Constants.DEFAULT_PARSER.equals( parser ) ) {
this.getLogger().warn("Deprecated property " +Constants.DEPRECATED_PARSER_PROPERTY+ " is used. Please use "+Constants.PARSER_PROPERTY+" instead.");
if ( "org.apache.cocoon.components.parser.XercesParser".equals(parser) ) {
parser = "org.apache.avalon.excalibur.xml.XercesParser";
} else {
this.getLogger().warn("Unknown value for deprecated property: " +
Constants.DEPRECATED_PARSER_PROPERTY + ", value: " + parser +
". If you experience problems during startup, check the parser configuration section of the documentation.");
}
} else {
parser = System.getProperty(Constants.PARSER_PROPERTY, Constants.DEFAULT_PARSER);
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Using parser: " + parser);
}
ExcaliburComponentManager startupManager = new ExcaliburComponentManager((ClassLoader)this.context.get(Constants.CONTEXT_CLASS_LOADER));
startupManager.setLogger(getLogger().getChildLogger("startup"));
startupManager.contextualize(this.context);
startupManager.setLogKitManager(this.logKitManager);
try {
startupManager.addComponent(Parser.ROLE, ClassUtils.loadClass(parser), new org.apache.avalon.framework.configuration.DefaultConfiguration("", "empty"));
} catch (Exception e) {
getLogger().error("Could not load parser, Cocoon object not created.", e);
throw new ConfigurationException("Could not load parser " + parser, e);
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Classpath = " + classpath);
getLogger().debug("Work directory = " + workDir.getCanonicalPath());
}
startupManager.initialize();
this.configure(startupManager);
startupManager.dispose();
startupManager = null;
this.componentManager.initialize();
// Get the Processor and keep it if it's ThreadSafe
Processor processor = (Processor)this.componentManager.lookup(Processor.ROLE);
if (processor instanceof ThreadSafe) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Processor of class " + processor.getClass().getName() + " is ThreadSafe");
}
this.threadSafeProcessor = processor;
} else {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Processor of class " + processor.getClass().getName() +
" is NOT ThreadSafe -- will be looked up at each request");
}
this.componentManager.release(processor);
}
}
/** Dump System Properties */
private void dumpSystemProperties() {
try {
Enumeration e = System.getProperties().propertyNames();
if (getLogger().isDebugEnabled()) {
getLogger().debug("===== System Properties Start =====");
}
for (;e.hasMoreElements();) {
String key = (String) e.nextElement();
if (getLogger().isDebugEnabled()) {
getLogger().debug(key + "=" + System.getProperty(key));
}
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("===== System Properties End =====");
}
} catch ( SecurityException se ) {
// Ignore Exceptions.
}
}
/**
* Configure this <code>Cocoon</code> instance.
*
* @param startupManager an <code>ExcaliburComponentManager</code> value
* @return a <code>Configuration</code> value
* @exception ConfigurationException if an error occurs
* @exception ContextException if an error occurs
*/
public Configuration configure(ExcaliburComponentManager startupManager) throws ConfigurationException, ContextException {
Parser p = null;
Configuration roleConfig = null;
try {
this.configurationFile.refresh();
p = (Parser)startupManager.lookup(Parser.ROLE);
SAXConfigurationHandler b = new SAXConfigurationHandler();
InputStream inputStream = ClassUtils.getResource("org/apache/cocoon/cocoon.roles").openStream();
InputSource is = new InputSource(inputStream);
is.setSystemId(this.configurationFile.getSystemId());
p.parse(is, b);
roleConfig = b.getConfiguration();
} catch (Exception e) {
getLogger().error("Could not configure Cocoon environment", e);
throw new ConfigurationException("Error trying to load configurations", e);
} finally {
if (p != null) startupManager.release(p);
}
DefaultRoleManager drm = new DefaultRoleManager();
drm.setLogger(getLogger().getChildLogger("roles"));
drm.configure(roleConfig);
roleConfig = null;
try {
p = (Parser)startupManager.lookup(Parser.ROLE);
SAXConfigurationHandler b = new SAXConfigurationHandler();
InputSource is = this.configurationFile.getInputSource();
p.parse(is, b);
this.configuration = b.getConfiguration();
} catch (Exception e) {
getLogger().error("Could not configure Cocoon environment", e);
throw new ConfigurationException("Error trying to load configurations",e);
} finally {
if (p != null) startupManager.release(p);
}
Configuration conf = this.configuration;
if (getLogger().isDebugEnabled()) {
getLogger().debug("Root configuration: " + conf.getName());
}
if (! "cocoon".equals(conf.getName())) {
throw new ConfigurationException("Invalid configuration file\n" + conf.toString());
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Configuration version: " + conf.getAttribute("version"));
}
if (!Constants.CONF_VERSION.equals(conf.getAttribute("version"))) {
throw new ConfigurationException("Invalid configuration schema version. Must be '" + Constants.CONF_VERSION + "'.");
}
String userRoles = conf.getAttribute("user-roles", "");
if (!"".equals(userRoles)) {
try {
p = (Parser)startupManager.lookup(Parser.ROLE);
SAXConfigurationHandler b = new SAXConfigurationHandler();
org.apache.cocoon.environment.Context context =
(org.apache.cocoon.environment.Context) this.context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
URL url = context.getResource(userRoles);
InputSource is = new InputSource(new BufferedInputStream(url.openStream()));
is.setSystemId(this.configurationFile.getSystemId());
p.parse(is, b);
roleConfig = b.getConfiguration();
} catch (Exception e) {
getLogger().error("Could not configure Cocoon environment with user roles file", e);
throw new ConfigurationException("Error trying to load user-roles configuration", e);
} finally {
startupManager.release(p);
}
DefaultRoleManager urm = new DefaultRoleManager(drm);
urm.setLogger(getLogger().getChildLogger("roles").getChildLogger("user"));
urm.configure(roleConfig);
roleConfig = null;
drm = urm;
}
this.componentManager.setRoleManager(drm);
this.componentManager.setLogKitManager(this.logKitManager);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Setting up components...");
}
this.componentManager.configure(conf);
return conf;
}
/**
* Queries the class to estimate its ergodic period termination.
*
* @param date a <code>long</code> value
* @return a <code>boolean</code> value
*/
public boolean modifiedSince(long date) {
return date < this.configurationFile.getLastModified();
}
/**
* Sets required system properties.
*/
protected void setSystemProperties() {
java.util.Properties props = new java.util.Properties();
// FIXME We shouldn't have to specify the SAXParser...
// This is needed by Xalan2, it is used by org.xml.sax.helpers.XMLReaderFactory
// to locate the SAX2 driver.
props.put("org.xml.sax.driver", "org.apache.xerces.parsers.SAXParser");
java.util.Properties systemProps = System.getProperties();
Enumeration propEnum = props.propertyNames();
while (propEnum.hasMoreElements()) {
String prop = (String)propEnum.nextElement();
if (!systemProps.containsKey(prop)) {
systemProps.put(prop, props.getProperty(prop));
}
}
// FIXME We shouldn't have to specify these. Needed to override jaxp implementation of weblogic.
if (systemProps.containsKey("javax.xml.parsers.DocumentBuilderFactory") &&
systemProps.getProperty("javax.xml.parsers.DocumentBuilderFactory").startsWith("weblogic")) {
systemProps.put("javax.xml.parsers.DocumentBuilderFactory", "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
systemProps.put("javax.xml.parsers.SAXParserFactory","org.apache.xerces.jaxp.SAXParserFactoryImpl");
}
System.setProperties(systemProps);
}
/**
* Dispose this instance
*/
public void dispose() {
this.componentManager.release(this.threadSafeProcessor);
this.componentManager.dispose();
if (this.configurationFile != null) {
this.configurationFile.recycle();
}
this.disposed = true;
}
/**
* Log debug information about the current environment.
*
* @param environment an <code>Environment</code> value
* @param pipeline a <code>StreamPipeline</code> value
* @param eventPipeline an <code>EventPipeline</code> value
*/
protected void debug(Environment environment,
StreamPipeline pipeline,
EventPipeline eventPipeline) {
String lineSeparator = System.getProperty("line.separator");
Map objectModel = environment.getObjectModel();
Request request = ObjectModelHelper.getRequest(objectModel);
Session session = request.getSession(false);
StringBuffer msg = new StringBuffer();
msg.append("DEBUGGING INFORMATION:").append(lineSeparator);
if (pipeline != null && eventPipeline != null) {
msg.append("INTERNAL ");
}
msg.append("REQUEST: ").append(request.getRequestURI()).append(lineSeparator).append(lineSeparator);
msg.append("CONTEXT PATH: ").append(request.getContextPath()).append(lineSeparator);
msg.append("SERVLET PATH: ").append(request.getServletPath()).append(lineSeparator);
msg.append("PATH INFO: ").append(request.getPathInfo()).append(lineSeparator).append(lineSeparator);
msg.append("REMOTE HOST: ").append(request.getRemoteHost()).append(lineSeparator);
msg.append("REMOTE ADDRESS: ").append(request.getRemoteAddr()).append(lineSeparator);
msg.append("REMOTE USER: ").append(request.getRemoteUser()).append(lineSeparator);
msg.append("REQUEST SESSION ID: ").append(request.getRequestedSessionId()).append(lineSeparator);
msg.append("REQUEST PREFERRED LOCALE: ").append(request.getLocale().toString()).append(lineSeparator);
msg.append("SERVER HOST: ").append(request.getServerName()).append(lineSeparator);
msg.append("SERVER PORT: ").append(request.getServerPort()).append(lineSeparator).append(lineSeparator);
msg.append("METHOD: ").append(request.getMethod()).append(lineSeparator);
msg.append("CONTENT LENGTH: ").append(request.getContentLength()).append(lineSeparator);
msg.append("PROTOCOL: ").append(request.getProtocol()).append(lineSeparator);
msg.append("SCHEME: ").append(request.getScheme()).append(lineSeparator);
msg.append("AUTH TYPE: ").append(request.getAuthType()).append(lineSeparator).append(lineSeparator);
msg.append("CURRENT ACTIVE REQUESTS: ").append(activeRequestCount).append(lineSeparator);
// log all of the request parameters
Enumeration e = request.getParameterNames();
msg.append("REQUEST PARAMETERS:").append(lineSeparator).append(lineSeparator);
while (e.hasMoreElements()) {
String p = (String) e.nextElement();
msg.append("PARAM: '").append(p).append("' ")
.append("VALUES: '");
String[] params = request.getParameterValues(p);
for (int i = 0; i < params.length; i++) {
msg.append("[" + params[i] + "]");
if (i != (params.length - 1)) {
msg.append(", ");
}
}
msg.append("'").append(lineSeparator);
}
// log all of the header parameters
Enumeration e2 = request.getHeaderNames();
msg.append("HEADER PARAMETERS:").append(lineSeparator).append(lineSeparator);
while (e2.hasMoreElements()) {
String p = (String) e2.nextElement();
msg.append("PARAM: '").append(p).append("' ")
.append("VALUES: '");
Enumeration e3 = request.getHeaders(p);
while (e3.hasMoreElements()) {
msg.append("[" + e3.nextElement() + "]");
if (e3.hasMoreElements()) {
msg.append(", ");
}
}
msg.append("'").append(lineSeparator);
}
msg.append(lineSeparator).append("SESSION ATTRIBUTES:").append(lineSeparator).append(lineSeparator);
// log all of the session attributes
if (session != null) {
e = session.getAttributeNames();
while (e.hasMoreElements()) {
String p = (String) e.nextElement();
msg.append("PARAM: '").append(p).append("' ")
.append("VALUE: '").append(session.getAttribute(p)).append("'").append(lineSeparator);
}
}
if (getLogger().isDebugEnabled()) {
getLogger().debug(msg.toString());
}
}
/**
* Process the given <code>Environment</code> to produce the output.
*
* @param environment an <code>Environment</code> value
* @return a <code>boolean</code> value
* @exception Exception if an error occurs
*/
public boolean process(Environment environment)
throws Exception {
if (disposed) {
throw new IllegalStateException("You cannot process a Disposed Cocoon engine.");
}
try {
if (this.getLogger().isDebugEnabled()) {
++activeRequestCount;
this.debug(environment, null, null);
}
if (this.threadSafeProcessor != null) {
return this.threadSafeProcessor.process(environment);
} else {
Processor processor = (Processor)this.componentManager.lookup(Processor.ROLE);
try {
return processor.process(environment);
}
finally {
this.componentManager.release(processor);
}
}
} finally {
if (this.getLogger().isDebugEnabled()) {
--activeRequestCount;
}
}
}
/**
* Process the given <code>Environment</code> to assemble
* a <code>StreamPipeline</code> and an <code>EventPipeline</code>.
*
* @param environment an <code>Environment</code> value
* @param pipeline a <code>StreamPipeline</code> value
* @param eventPipeline an <code>EventPipeline</code> value
* @return a <code>boolean</code> value
* @exception Exception if an error occurs
*/
public boolean process(Environment environment, StreamPipeline pipeline, EventPipeline eventPipeline)
throws Exception {
if (disposed) {
throw new IllegalStateException("You cannot process a Disposed Cocoon engine.");
}
try {
if (this.getLogger().isDebugEnabled()) {
++activeRequestCount;
this.debug(environment, pipeline, eventPipeline);
}
if (this.threadSafeProcessor != null) {
return this.threadSafeProcessor.process(environment, pipeline, eventPipeline);
} else {
Processor processor = (Processor)this.componentManager.lookup(Processor.ROLE);
try {
return processor.process(environment);
}
finally {
this.componentManager.release(processor);
}
}
} finally {
if (this.getLogger().isDebugEnabled()) {
--activeRequestCount;
}
}
}
/**
* Process the given <code>Environment</code> to generate the sitemap.
* Delegated to the Processor component if it's a <code>SitemapManager</code>.
*
* @param environment an <code>Environment</code> value
* @exception Exception if an error occurs
*/
public void generateSitemap(Environment environment)
throws Exception {
Component processor = this.componentManager.lookup(Processor.ROLE);
try {
if (processor instanceof SitemapManager) {
((SitemapManager)processor).generateSitemap(environment);
}
} finally {
this.componentManager.release(processor);
}
}
/**
* Process the given <code>Environment</code> to generate Java code for specified XSP files.
*
* @param fileName a <code>String</code> value
* @param environment an <code>Environment</code> value
* @exception Exception if an error occurs
*/
public void precompile(String fileName, Environment environment, String markupLanguage, String programmingLanguage)
throws Exception {
ProgramGenerator programGenerator = null;
SourceHandler oldSourceHandler = environment.getSourceHandler();
SourceHandler sourceHandler = null;
try {
if (getLogger().isDebugEnabled()) {
getLogger().debug("XSP generation begin:" + fileName);
}
programGenerator = (ProgramGenerator) this.componentManager.lookup(ProgramGenerator.ROLE);
sourceHandler = (SourceHandler) this.componentManager.lookup(SourceHandler.ROLE);
environment.setSourceHandler(sourceHandler);
CompiledComponent xsp = programGenerator.load(this.componentManager, fileName, markupLanguage, programmingLanguage, environment);
if (getLogger().isDebugEnabled()) {
getLogger().debug("XSP generation complete:" + xsp);
}
this.componentManager.release(programGenerator);
} catch (Exception e) {
getLogger().error("Main: Error compiling XSP", e);
throw e;
} finally {
environment.setSourceHandler(oldSourceHandler);
this.componentManager.release(programGenerator);
this.componentManager.release(sourceHandler);
}
}
/**
* Accessor for active request count
*/
public int getActiveRequestCount() {
return activeRequestCount;
}
}