blob: 15ff0cc07e2d70335b9db72d16a0968e3ad28d9b [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.components.treeprocessor;
import java.util.Map;
import org.apache.avalon.excalibur.component.RoleManageable;
import org.apache.avalon.excalibur.component.RoleManager;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.component.Recomposable;
import org.apache.avalon.framework.configuration.Configurable;
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.container.ContainerUtil;
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.AbstractLogEnabled;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.cocoon.Processor;
import org.apache.cocoon.util.Settings;
import org.apache.cocoon.util.SettingsHelper;
import org.apache.cocoon.components.CocoonComponentManager;
import org.apache.cocoon.components.ExtendedComponentSelector;
import org.apache.cocoon.components.LifecycleHelper;
import org.apache.cocoon.components.PropertyAwareSAXConfigurationHandler;
import org.apache.cocoon.components.pipeline.ProcessingPipeline;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.components.source.impl.DelayedRefreshSourceWrapper;
import org.apache.cocoon.environment.Environment;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceResolver;
/**
* Interpreted tree-traversal implementation of a pipeline assembly language.
*
* @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
* @version CVS $Id$
*/
public class TreeProcessor
extends AbstractLogEnabled
implements ThreadSafe,
Processor,
Composable,
Configurable,
RoleManageable,
Contextualizable,
Disposable {
public static final String COCOON_REDIRECT_ATTR = "sitemap:cocoon-redirect";
private static final String XCONF_URL =
"resource://org/apache/cocoon/components/treeprocessor/treeprocessor-builtins.xml";
/** The parent TreeProcessor, if any */
protected TreeProcessor parent;
/** The context */
protected Context context;
/** The component manager */
protected ComponentManager manager;
/** The role manager */
protected RoleManager roleManager;
/** Selector of TreeBuilders, the hint is the language name */
protected ExtendedComponentSelector builderSelector;
/** Last modification time */
protected long lastModified = 0;
/** The source of the tree definition */
protected DelayedRefreshSourceWrapper source;
/** Delay for <code>sourceLastModified</code>. */
protected long lastModifiedDelay;
/** The current language configuration */
protected Configuration currentLanguage;
/** Check for reload? */
protected boolean checkReload;
/** The source resolver */
protected SourceResolver resolver;
/** The actual processor (package-private as needs to be accessed by ConcreteTreeProcessor) */
ConcreteTreeProcessor concreteProcessor;
/**
* Create a TreeProcessor.
*/
public TreeProcessor() {
this.checkReload = true;
this.lastModifiedDelay = 1000;
}
/**
* Create a child processor for a given language
*/
protected TreeProcessor(TreeProcessor parent, ComponentManager manager) {
this.parent = parent;
// Copy all that can be copied from the parent
this.enableLogging(parent.getLogger());
this.context = parent.context;
this.roleManager = parent.roleManager;
this.builderSelector = parent.builderSelector;
this.checkReload = parent.checkReload;
this.lastModifiedDelay = parent.lastModifiedDelay;
// We have our own CM
this.manager = manager;
}
/**
* Create a new child of this processor (used for mounting submaps).
*
* @param manager the component manager to be used by the child processor.
* @return a new child processor.
*/
public TreeProcessor createChildProcessor(ComponentManager manager,
String actualSource,
boolean checkReload)
throws Exception {
// Note: lifecycle methods aren't called, since this constructors copies all
// that can be copied from the parent (see above)
TreeProcessor child = new TreeProcessor(this, manager);
child.checkReload = checkReload;
child.resolver = (SourceResolver)manager.lookup(SourceResolver.ROLE);
child.source = new DelayedRefreshSourceWrapper(child.resolver.resolveURI(actualSource), this.lastModifiedDelay);
return child;
}
/* (non-Javadoc)
* @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
*/
public void contextualize(Context context) throws ContextException {
this.context = context;
}
/* (non-Javadoc)
* @see org.apache.avalon.framework.component.Composable#compose(org.apache.avalon.framework.component.ComponentManager)
*/
public void compose(ComponentManager manager) throws ComponentException {
this.manager = manager;
this.resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
}
/* (non-Javadoc)
* @see org.apache.avalon.excalibur.component.RoleManageable#setRoleManager(org.apache.avalon.excalibur.component.RoleManager)
*/
public void setRoleManager(RoleManager rm) {
this.roleManager = rm;
}
/*
<processor>
<reload delay="10"/>
<language>...</language>
</processor>
*/
public void configure(Configuration config)
throws ConfigurationException {
this.checkReload = config.getAttributeAsBoolean("check-reload", true);
// Obtain the configuration file, or use the XCONF_URL if none
// is defined
String xconfURL = config.getAttribute("config", XCONF_URL);
// Reload check delay. Default is 1 second.
this.lastModifiedDelay = config.getChild("reload").getAttributeAsLong("delay", 1000L);
String fileName = config.getAttribute("file", "sitemap.xmap");
try {
this.source = new DelayedRefreshSourceWrapper(this.resolver.resolveURI(fileName), this.lastModifiedDelay);
} catch (Exception e) {
throw new ConfigurationException("Cannot resolve " + fileName, e);
}
// Read the builtin languages definition file
Configuration builtin;
try {
Source source = this.resolver.resolveURI(xconfURL);
try {
Settings settings = SettingsHelper.getSettings(this.context);
SAXConfigurationHandler handler = new PropertyAwareSAXConfigurationHandler(settings, this.getLogger());
SourceUtil.toSAX( this.manager, source, null, handler);
builtin = handler.getConfiguration();
} finally {
this.resolver.release(source);
}
} catch(Exception e) {
String msg = "Error while reading " + xconfURL + ": " + e.getMessage();
throw new ConfigurationException(msg, e);
}
// Create a selector for tree builders of all languages
this.builderSelector = new ExtendedComponentSelector(Thread.currentThread().getContextClassLoader());
try {
LifecycleHelper.setupComponent(this.builderSelector,
this.getLogger(),
this.context,
this.manager,
this.roleManager,
builtin);
} catch (ConfigurationException e) {
throw e;
} catch (Exception e) {
throw new ConfigurationException("Could not setup builder selector", e);
}
}
/**
* Process the given <code>Environment</code> producing the output.
* @return If the processing is successfull <code>true</code> is returned.
* If not match is found in the sitemap <code>false</code>
* is returned.
* @throws org.apache.cocoon.ResourceNotFoundException If a sitemap component tries
* to access a resource which can not
* be found, e.g. the generator
* ConnectionResetException If the connection was reset
*/
public boolean process(Environment environment) throws Exception {
this.setupConcreteProcessor(environment);
return this.concreteProcessor.process(environment);
}
/**
* Process the given <code>Environment</code> to assemble
* a <code>ProcessingPipeline</code>.
* @since 2.1
*/
public ProcessingPipeline buildPipeline(Environment environment)
throws Exception {
this.setupConcreteProcessor(environment);
return this.concreteProcessor.buildPipeline(environment);
}
/* (non-Javadoc)
* @see org.apache.cocoon.Processor#getRootProcessor()
*/
public Processor getRootProcessor() {
TreeProcessor result = this;
while(result.parent != null) {
result = result.parent;
}
return result;
}
/**
* Set the sitemap component configurations
*/
public void setComponentConfigurations(Configuration componentConfigurations) {
this.concreteProcessor.setComponentConfigurations(componentConfigurations);
}
/* (non-Javadoc)
* @see org.apache.cocoon.Processor#getComponentConfigurations()
*/
public Map getComponentConfigurations() {
return this.concreteProcessor.getComponentConfigurations();
}
private void setupConcreteProcessor(Environment env) throws Exception {
if (this.parent == null) {
// Ensure root sitemap uses the correct context, even if not located in the webapp context
env.changeContext("", this.source.getURI());
}
// check for sitemap changes
if (this.concreteProcessor == null ||
(this.checkReload && this.source.getLastModified() != this.lastModified)) {
this.buildConcreteProcessor(env);
}
}
private synchronized void buildConcreteProcessor(Environment env) throws Exception {
// Now that we entered the synchronized area, recheck what's already
// been checked in process().
if (this.concreteProcessor != null && this.source.getLastModified() == this.lastModified) {
// Nothing changed
return;
}
long startTime = System.currentTimeMillis();
// Dispose the old processor, if any
if (this.concreteProcessor != null) {
this.concreteProcessor.markForDisposal();
}
// Get a builder
TreeBuilder builder = (TreeBuilder)this.builderSelector.select("sitemap");
ConcreteTreeProcessor newProcessor = new ConcreteTreeProcessor(this);
long newLastModified;
this.setupLogger(newProcessor);
//FIXME (SW): why do we need to enterProcessor here?
CocoonComponentManager.enterEnvironment(env, this.manager, this);
try {
if (builder instanceof Recomposable) {
((Recomposable)builder).recompose(this.manager);
}
builder.setProcessor(newProcessor);
newLastModified = this.source.getLastModified();
ProcessingNode root = builder.build(this.source);
newProcessor.setProcessorData(builder.getSitemapComponentManager(), root, builder.getDisposableNodes());
} finally {
CocoonComponentManager.leaveEnvironment();
this.builderSelector.release(builder);
}
if (this.getLogger().isDebugEnabled()) {
double time = (this.lastModified - startTime) / 1000.0;
this.getLogger().debug("TreeProcessor built in " + time + " secs from " + this.source.getURI());
}
// Switch to the new processor (ensure it's never temporarily null)
this.concreteProcessor = newProcessor;
this.lastModified = newLastModified;
}
/* (non-Javadoc)
* @see org.apache.avalon.framework.activity.Disposable#dispose()
*/
public void dispose() {
// Dispose the concrete processor. No need to check for existing requests, as there
// are none when a TreeProcessor is disposed.
ContainerUtil.dispose(this.concreteProcessor);
this.concreteProcessor = null;
if (this.manager != null) {
if (this.source != null) {
this.resolver.release(this.source.getSource());
this.source = null;
}
if (this.parent == null) {
// root processor : dispose the builder selector
this.builderSelector.dispose();
this.builderSelector = null;
}
// Release resolver looked up in compose()
this.manager.release((Component)this.resolver);
this.resolver = null;
this.manager = null;
}
}
public String toString() {
return "TreeProcessor - " + (this.source == null ? "[unknown location]" : this.source.getURI());
}
}