/* * 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; import java.io.IOException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.avalon.excalibur.component.ExcaliburComponentManager; 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.ComponentSelector; import org.apache.avalon.framework.component.Recomposable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.logger.Logger; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.Processor; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.xml.XMLConsumer; import org.apache.excalibur.instrument.InstrumentManager; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.SourceResolver; /** * Cocoon Component Manager. * This manager extends the {@link ExcaliburComponentManager} * by a special lifecycle handling for a {@link RequestLifecycleComponent} * and by handling the lookup of the {@link SourceResolver}. * WARNING: This is a "private" Cocoon core class - do NOT use this class * directly - and do not assume that a {@link ComponentManager} you get * via the compose() method is an instance of CocoonComponentManager. * * @author <a href="mailto:bluetkemeier@s-und-n.de">Björn Lütkemeier</a> * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a> * @version CVS $Id$ */ public final class CocoonComponentManager extends ExcaliburComponentManager implements SourceResolver, Component { /** The key used to store the current process environment */ private static final String PROCESS_KEY = CocoonComponentManager.class.getName(); /** The environment attribute used to keep track of the actual environment in which the pipeline was built. */ private static final String PROCESSOR_ATTR = "CocoonComponentManager.processor"; /** The environment information */ protected static final ThreadLocal environmentStack = new ThreadLocal(); /** The configured {@link SourceResolver} */ private SourceResolver sourceResolver; /** The {@link SitemapConfigurationHolder}s */ private Map sitemapConfigurationHolders = new HashMap(15); /** The parent component manager for implementing parent aware components */ private ComponentManager parentManager; /** Temporary list of parent-aware components. Will be null for most of * our lifecycle. */ private ArrayList parentAwareComponents = new ArrayList(); /** has this been disposed? */ private boolean wasDisposed; /** The instrument manager (if any). */ private InstrumentManager instrumentManager; /** Create the ComponentManager */ public CocoonComponentManager() { super(null, Thread.currentThread().getContextClassLoader()); } /** Create the ComponentManager with a Classloader */ public CocoonComponentManager(final ClassLoader loader) { super(null, loader); } /** Create the ComponentManager with a Classloader and parent ComponentManager */ public CocoonComponentManager(final ComponentManager manager, final ClassLoader loader) { super(manager, loader); this.setParentManager(manager); } /** Create the ComponentManager with a parent ComponentManager */ public CocoonComponentManager(final ComponentManager manager) { super(manager); this.setParentManager(manager); } protected void setParentManager(final ComponentManager manager) { this.parentManager = manager; if ( manager instanceof CocoonComponentManager ) { this.setInstrumentManager(((CocoonComponentManager)manager).instrumentManager); } } /** * @see org.apache.avalon.excalibur.component.ExcaliburComponentManager#setInstrumentManager(org.apache.excalibur.instrument.InstrumentManager) */ public void setInstrumentManager(InstrumentManager iManager) { this.instrumentManager = iManager; super.setInstrumentManager(iManager); } /** * This hook must be called by the sitemap each time a sitemap is entered * This method should never raise an exception, except when the * parameters are not set! */ public static void enterEnvironment(Environment env, ComponentManager manager, Processor processor) { if (null == env || null == manager || null == processor) { throw new RuntimeException("CocoonComponentManager.enterEnvironment: " + "All parameters must be set: " + env + " - " + manager + " - " + processor); } EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if (stack == null) { stack = new EnvironmentStack(); environmentStack.set(stack); } stack.push(new EnvironmentStack.Item(env, processor, manager, stack.getOffset())); stack.setOffset(stack.size()-1); env.setAttribute(PROCESSOR_ATTR, processor); } /** * This hook must be called by the sitemap each time a sitemap is left. * It's the counterpart to {@link #enterEnvironment(Environment, ComponentManager, Processor)}. */ public static void leaveEnvironment() { // Calling with true will avoid any change on the active processor leaveEnvironment(true); } /** * This hook must be called by the sitemap each time a sitemap is left. * It's the counterpart to {@link #enterEnvironment(Environment, ComponentManager, Processor)}. * * @param success indicates if the request was successfully handled by the environment that's being left */ public static void leaveEnvironment(boolean success) { final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); final EnvironmentStack.Item objs = (EnvironmentStack.Item)stack.pop(); stack.setOffset(objs.offset); if (stack.isEmpty()) { final Environment env = objs.env; final Map globalComponents = (Map)env.getAttribute(GlobalRequestLifecycleComponent.class.getName()); if (globalComponents != null) { final Iterator iter = globalComponents.values().iterator(); while (iter.hasNext()) { final Object[] o = (Object[])iter.next(); final Component c = (Component)o[0]; ((CocoonComponentManager)o[1]).releaseRLComponent( c ); } } env.removeAttribute(GlobalRequestLifecycleComponent.class.getName()); // Setting this ThreadLocal to null allows it to be garbage collected CocoonComponentManager.environmentStack.set(null); } else { if (!success) { // Restore the current processor as being the active one getCurrentEnvironment().setAttribute(PROCESSOR_ATTR, getCurrentProcessor()); } } } /** * INTERNAL METHOD. Do not use, can be removed without warning or deprecation cycle. */ public static int markEnvironment() { // TODO (CZ): This is only for testing - remove it later on. See also Cocoon.java. final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if (stack != null) { return stack.size(); } return 0; } /** * INTERNAL METHOD. Do not use, can be removed without warning or deprecation cycle. */ public static void checkEnvironment(int depth, Logger logger) throws Exception { // TODO (CZ): This is only for testing - remove it later on. See also Cocoon.java. final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); int currentDepth = stack != null? stack.size() : 0; if (currentDepth != depth) { logger.error("ENVIRONMENT STACK HAS NOT BEEN CLEANED PROPERLY!"); throw new ProcessingException("Environment stack has not been cleaned up properly. " + "Please report this (and if possible, together with a test case) " + "to the Cocoon developers."); } } /** * Create an environment aware xml consumer for the cocoon * protocol */ public static XMLConsumer createEnvironmentAwareConsumer(XMLConsumer consumer) { final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); final EnvironmentStack.Item objs = stack.getCurrent(); return stack.getEnvironmentAwareConsumerWrapper(consumer, objs.offset); } /** * This hook has to be called before a request is processed. * The hook is called by the Cocoon component and by the * cocoon protocol implementation. * This method should never raise an exception, except when * the environment is not set. * * @return A unique key within this thread. */ public static Object startProcessing(Environment env) { if (null == env) { throw new RuntimeException("CocoonComponentManager.startProcessing: environment must be set."); } final EnvironmentDescription desc = new EnvironmentDescription(env); env.getObjectModel().put(PROCESS_KEY, desc); env.startingProcessing(); return desc; } /** * This hook has to be called before a request is processed. * The hook is called by the Cocoon component and by the * cocoon protocol implementation. * @param key A unique key within this thread return by * {@link #startProcessing(Environment)}. */ public static void endProcessing(Environment env, Object key) { env.finishingProcessing(); final EnvironmentDescription desc = (EnvironmentDescription)key; desc.release(); env.getObjectModel().remove(PROCESS_KEY); } /** * Return the current environment (for the cocoon: protocol) */ public static Environment getCurrentEnvironment() { final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if (null != stack && !stack.isEmpty()) { return stack.getCurrent().env; } return null; } /** * Return the current processor (for the cocoon: protocol) */ public static Processor getCurrentProcessor() { final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if (null != stack && !stack.isEmpty()) { return stack.getCurrent().processor; } return null; } /** * Return the processor that has actually processed the request */ public static Processor getActiveProcessor(Environment env) { return (Processor) env.getAttribute(PROCESSOR_ATTR); } /** * Get the current sitemap component manager. * This method return the current sitemap component manager. This * is the manager that holds all the components of the currently * processed (sub)sitemap. */ static public ComponentManager getSitemapComponentManager() { final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if (null != stack && !stack.isEmpty()) { EnvironmentStack.Item o = (EnvironmentStack.Item) stack.peek(); return o.manager; } // If we don't have an environment yet, just return null return null; } /** * Return an instance of a component based on a Role. The Role is usually the Interface's * Fully Qualified Name(FQN)--unless there are multiple Components for the same Role. In that * case, the Role's FQN is appended with "Selector", and we return a ComponentSelector. */ public Component lookup(final String role) throws ComponentException { if (null == role) { final String message = "ComponentLocator Attempted to retrieve component with null role."; throw new ComponentException(role, message); } if (role.equals(SourceResolver.ROLE)) { if (null == this.sourceResolver) { if(this.wasDisposed) { // (BD) working on bug 27249: I think we could throw an Exception here, as // the following call fails anyway, but I'm not sure enough ;-) this.getLogger().warn("Trying to lookup SourceResolver on disposed CocoonComponentManager"); } this.sourceResolver = (SourceResolver) super.lookup( role ); } return this; } final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if ( null != stack && !stack.isEmpty()) { final EnvironmentStack.Item objects = stack.getCurrent(); final Map objectModel = objects.env.getObjectModel(); EnvironmentDescription desc = (EnvironmentDescription)objectModel.get(PROCESS_KEY); if ( null != desc ) { Component component = desc.getRequestLifecycleComponent(role); if (null != component) { return component; } component = desc.getGlobalRequestLifecycleComponent(role); if (null != component) { return component; } } } final Component component = super.lookup(role); if (component != null && component instanceof RequestLifecycleComponent) { if (stack == null || stack.isEmpty()) { throw new ComponentException(role, "ComponentManager has no Environment Stack."); } final EnvironmentStack.Item objects = stack.getCurrent(); final Map objectModel = objects.env.getObjectModel(); EnvironmentDescription desc = (EnvironmentDescription) objectModel.get(PROCESS_KEY); if (null != desc) { // first test if the parent CM has already initialized this component if (!desc.containsRequestLifecycleComponent(role)) { try { if (component instanceof Recomposable) { ((Recomposable) component).recompose(this); } ((RequestLifecycleComponent) component).setup(objects.env, objectModel); } catch (Exception local) { throw new ComponentException(role, "Exception during setup of RequestLifecycleComponent.", local); } desc.addRequestLifecycleComponent(role, component, this); } } } if (component != null && component instanceof GlobalRequestLifecycleComponent) { if (stack == null || stack.isEmpty()) { throw new ComponentException(role, "ComponentManager has no Environment Stack."); } final EnvironmentStack.Item objects = stack.getCurrent(); final Map objectModel = objects.env.getObjectModel(); EnvironmentDescription desc = (EnvironmentDescription) objectModel.get(PROCESS_KEY); if (null != desc) { // first test if the parent CM has already initialized this component if ( !desc.containsGlobalRequestLifecycleComponent( role ) ) { try { if (component instanceof Recomposable) { ((Recomposable) component).recompose(this); } ((GlobalRequestLifecycleComponent) component).setup(objects.env, objectModel); } catch (Exception local) { throw new ComponentException(role, "Exception during setup of RequestLifecycleComponent.", local); } desc.addGlobalRequestLifecycleComponent(role, component, this); } } } if (component != null && component instanceof SitemapConfigurable) { // FIXME: how can we prevent that this is called over and over again? SitemapConfigurationHolder holder; holder = (SitemapConfigurationHolder) this.sitemapConfigurationHolders.get(role); if (null == holder) { // create new holder holder = new DefaultSitemapConfigurationHolder(role); this.sitemapConfigurationHolders.put(role, holder); } try { ((SitemapConfigurable)component).configure(holder); } catch (ConfigurationException ce) { throw new ComponentException(role, "Exception during setup of SitemapConfigurable.", ce); } } return component; } /** * Release a Component. This implementation makes sure it has a handle on the propper * ComponentHandler, and let's the ComponentHandler take care of the actual work. */ public void release(final Component component) { if (null == component) { return; } if (component instanceof RequestLifecycleComponent || component instanceof GlobalRequestLifecycleComponent) { return; } if (component == this) { return; } super.release(component); } /** * Release a RequestLifecycleComponent */ protected void releaseRLComponent(final Component component) { super.release(component); } /** * Add an automatically released component */ public static void addComponentForAutomaticRelease(final ComponentSelector selector, final Component component, final ComponentManager manager) throws ProcessingException { final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if ( null != stack && !stack.isEmpty()) { final EnvironmentStack.Item objects = (EnvironmentStack.Item)stack.get(0); final Map objectModel = objects.env.getObjectModel(); EnvironmentDescription desc = (EnvironmentDescription)objectModel.get(PROCESS_KEY); if ( null != desc ) { desc.addToAutoRelease(selector, component, manager); } } else { throw new ProcessingException("Unable to add component for automatic release: no environment available."); } } /** * Add an automatically released component */ public static void addComponentForAutomaticRelease(final ComponentManager manager, final Component component) throws ProcessingException { final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if ( null != stack && !stack.isEmpty()) { final EnvironmentStack.Item objects = (EnvironmentStack.Item)stack.get(0); final Map objectModel = objects.env.getObjectModel(); EnvironmentDescription desc = (EnvironmentDescription)objectModel.get(PROCESS_KEY); if ( null != desc ) { desc.addToAutoRelease(manager, component); } } else { throw new ProcessingException("Unable to add component for automatic release: no environment available."); } } /** * Remove from automatically released components */ public static void removeFromAutomaticRelease(final Component component) throws ProcessingException { final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if ( null != stack && !stack.isEmpty()) { final EnvironmentStack.Item objects = (EnvironmentStack.Item)stack.get(0); final Map objectModel = objects.env.getObjectModel(); EnvironmentDescription desc = (EnvironmentDescription)objectModel.get(PROCESS_KEY); if ( null != desc ) { desc.removeFromAutoRelease(component); } } else { throw new ProcessingException("Unable to remove component from automatic release: no environment available."); } } /** * Dispose */ public void dispose() { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("CocoonComponentManager.dispose() called"); } if (null != this.sourceResolver) { super.release((Component)this.sourceResolver); // We cannot null out sourceResolver here yet as some other not // disposed yet components might still have unreleased sources, // and they will call {@link #release(Source)} during their // dispose(). } super.dispose(); // All components now are released so sourceResolver should be not // needed anymore. this.sourceResolver = null; // This is used to track bug 27249 this.wasDisposed = true; } /** * Get a <code>Source</code> object. */ public Source resolveURI(final String location) throws MalformedURLException, IOException, SourceException { return this.resolveURI(location, null, null); } /** * Get a <code>Source</code> object. */ public Source resolveURI(final String location, String baseURI, final Map parameters) throws MalformedURLException, IOException, SourceException { if (baseURI == null) { final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); if ( null != stack && !stack.isEmpty()) { final EnvironmentStack.Item objects = stack.getCurrent(); baseURI = objects.env.getContext(); } } return this.sourceResolver.resolveURI(location, baseURI, parameters); } /** * Releases a resolved resource */ public void release(final Source source) { this.sourceResolver.release(source); } /* (non-Javadoc) * @see org.apache.avalon.excalibur.component.ExcaliburComponentManager#addComponent(java.lang.String, java.lang.Class, org.apache.avalon.framework.configuration.Configuration) */ public void addComponent(String role, Class clazz, Configuration conf) throws ComponentException { super.addComponent(role, clazz, conf); // Note that at this point, we're not initialized and cannot do // lookups, so defer parental introductions to initialize(). if (ParentAware.class.isAssignableFrom(clazz)) { this.parentAwareComponents.add(role); } } public void initialize() throws Exception { super.initialize(); if (this.parentAwareComponents == null) { throw new ComponentException(null, "CocoonComponentManager already initialized"); } // Set parents for parentAware components Iterator iter = this.parentAwareComponents.iterator(); while (iter.hasNext()) { String role = (String)iter.next(); this.getLogger().debug(".. "+role); if ( this.parentManager != null && this.parentManager.hasComponent( role ) ) { // lookup new component Component component = null; try { component = this.lookup( role ); ((ParentAware)component).setParentLocator( new ComponentLocatorImpl(this.parentManager, role )); } catch (ComponentException ignore) { // we don't set the parent then } finally { this.release( component ); } } } this.parentAwareComponents = null; // null to save memory, and catch logic bugs. } /** * A runnable wrapper that inherits the environment stack of the thread it is * created in. * <p> * It's defined as an abstract class here to use some internals of EnvironmentHelper, and * should only be used through its public counterpart, {@link org.apache.cocoon.environment.CocoonRunnable} */ public static abstract class AbstractCocoonRunnable implements Runnable { private Object parentStack = null; public AbstractCocoonRunnable() { // Clone the environment stack of the calling thread. // We'll use it in run() below Object stack = CocoonComponentManager.environmentStack.get(); if (stack != null) { this.parentStack = ((EnvironmentStack)stack).clone(); } } /** * Calls {@link #doRun()} within the environment context of the creating thread. */ public final void run() { // Install the stack from the parent thread and run the Runnable Object oldStack = environmentStack.get(); CocoonComponentManager.environmentStack.set(this.parentStack); try { this.doRun(); } finally { // Restore the previous stack CocoonComponentManager.environmentStack.set(oldStack); } // FIXME: Check the lifetime of this run compared to the parent thread. // A CocoonThread is meant to start and die within the execution period of the parent request, // and it is an error if it lives longer as the parent environment is no more valid. } abstract protected void doRun(); } } final class EnvironmentDescription { Environment environment; Map objectModel; Map requestLifecycleComponents; List autoreleaseComponents = new ArrayList(4); /** * Constructor */ EnvironmentDescription(Environment env) { this.environment = env; this.objectModel = env.getObjectModel(); } Map getGlobalRequestLifcecycleComponents() { Map m = (Map)this.environment.getAttribute(GlobalRequestLifecycleComponent.class.getName()); if ( m == null ) { m = new HashMap(); this.environment.setAttribute(GlobalRequestLifecycleComponent.class.getName(), m); } return m; } /** * Release all components of this environment * All RequestLifecycleComponents and autoreleaseComponents are * released. */ synchronized void release() { if ( this.requestLifecycleComponents != null ) { final Iterator iter = this.requestLifecycleComponents.values().iterator(); while (iter.hasNext()) { final Object[] o = (Object[])iter.next(); final Component component = (Component)o[0]; ((CocoonComponentManager)o[1]).releaseRLComponent( component ); } this.requestLifecycleComponents.clear(); } for (int i = 0; i < this.autoreleaseComponents.size(); i++) { final Object[] o = (Object[])this.autoreleaseComponents.get(i); final Component component = (Component)o[0]; if (o[1] instanceof ComponentManager) { ((ComponentManager)o[1]).release( component ); } else { ((ComponentSelector) o[1]).release(component); if (o[2] != null) { ((ComponentManager) o[2]).release((Component) o[1]); } } } this.autoreleaseComponents.clear(); this.environment = null; this.objectModel = null; } /** * Add a RequestLifecycleComponent to the environment */ void addRequestLifecycleComponent(final String role, final Component co, final ComponentManager manager) { if ( this.requestLifecycleComponents == null ) { this.requestLifecycleComponents = new HashMap(); } this.requestLifecycleComponents.put(role, new Object[] {co, manager}); } /** * Add a GlobalRequestLifecycleComponent to the environment */ void addGlobalRequestLifecycleComponent(final String role, final Component co, final ComponentManager manager) { this.getGlobalRequestLifcecycleComponents().put(role, new Object[] {co, manager}); } /** * Do we already have a request lifecycle component */ boolean containsRequestLifecycleComponent(final String role) { if ( this.requestLifecycleComponents == null ) { return false; } return this.requestLifecycleComponents.containsKey( role ); } /** * Do we already have a global request lifecycle component */ boolean containsGlobalRequestLifecycleComponent(final String role) { return this.getGlobalRequestLifcecycleComponents().containsKey( role ); } /** * Search a RequestLifecycleComponent */ Component getRequestLifecycleComponent(final String role) { if ( this.requestLifecycleComponents == null ) { return null; } final Object[] o = (Object[])this.requestLifecycleComponents.get(role); if ( null != o ) { return (Component)o[0]; } return null; } /** * Search a GlobalRequestLifecycleComponent */ Component getGlobalRequestLifecycleComponent(final String role) { final Object[] o = (Object[])this.getGlobalRequestLifcecycleComponents().get(role); if ( null != o ) { return (Component)o[0]; } return null; } /** * Add an automatically released component */ synchronized void addToAutoRelease(final ComponentSelector selector, final Component component, final ComponentManager manager) { this.autoreleaseComponents.add(new Object[] {component, selector, manager}); } /** * Add an automatically released component */ synchronized void addToAutoRelease(final ComponentManager manager, final Component component) { this.autoreleaseComponents.add(new Object[] {component, manager}); } /** * Remove from automatically released components */ synchronized void removeFromAutoRelease(final Component component) throws ProcessingException { int i = 0; boolean found = false; while (i < this.autoreleaseComponents.size() && !found) { final Object[] o = (Object[])this.autoreleaseComponents.get(i); if (o[0] == component) { found = true; if (o[1] instanceof ComponentManager) { ((ComponentManager)o[1]).release( component ); } else { ((ComponentSelector)o[1]).release( component ); if (o[2] != null) { ((ComponentManager)o[2]).release( (Component)o[1] ); } } this.autoreleaseComponents.remove(i); } else { i++; } } if (!found) { throw new ProcessingException("Unable to remove component from automatic release: component not found."); } } }