| /* |
| * 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()); |
| } |
| } |