/* * 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.io.IOException; import java.net.MalformedURLException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.logger.AbstractLogEnabled; import org.apache.avalon.framework.logger.Logger; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.Processor; import org.apache.cocoon.components.ChainedConfiguration; import org.apache.cocoon.components.CocoonComponentManager; import org.apache.cocoon.components.pipeline.ProcessingPipeline; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.ForwardRedirector; import org.apache.cocoon.environment.PermanentRedirector; import org.apache.cocoon.environment.Redirector; import org.apache.cocoon.environment.wrapper.EnvironmentWrapper; import org.apache.cocoon.environment.wrapper.MutableEnvironmentFacade; /** * The concrete implementation of {@link Processor}, containing the evaluation tree and associated * data such as component manager. * * @version $Id$ */ public class ConcreteTreeProcessor extends AbstractLogEnabled implements Processor, Disposable { /** The processor that wraps us */ private TreeProcessor wrappingProcessor; /** Component manager defined by the <map:components> of this sitemap */ ComponentManager sitemapComponentManager; /** Processing nodes that need to be disposed with this processor */ private List disposableNodes; /** Root node of the processing tree */ private ProcessingNode rootNode; private Map sitemapComponentConfigurations; private Configuration componentConfigurations; /** Number of simultaneous uses of this processor (either by concurrent request or by internal requests) */ private int requestCount; /** Builds a concrete processig, given the wrapping processor */ public ConcreteTreeProcessor(TreeProcessor wrappingProcessor) { this.wrappingProcessor = wrappingProcessor; } /** Set the processor data, result of the treebuilder job */ public void setProcessorData(ComponentManager manager, ProcessingNode rootNode, List disposableNodes) { if (this.sitemapComponentManager != null) { throw new IllegalStateException("setProcessorData() can only be called once"); } this.sitemapComponentManager = manager; this.rootNode = rootNode; this.disposableNodes = disposableNodes; } /** Set the sitemap component configurations (called as part of the tree building process) */ public void setComponentConfigurations(Configuration componentConfigurations) { this.componentConfigurations = componentConfigurations; this.sitemapComponentConfigurations = null; } /** * Get the sitemap component configurations * @since 2.1 */ public Map getComponentConfigurations() { // do we have the sitemap configurations prepared for this processor? if ( null == this.sitemapComponentConfigurations ) { synchronized (this) { if ( this.sitemapComponentConfigurations == null ) { // do we have configurations? final Configuration[] childs = (this.componentConfigurations == null ? null : this.componentConfigurations.getChildren()); if ( null != childs ) { if ( null == this.wrappingProcessor.parent ) { this.sitemapComponentConfigurations = new HashMap(12); } else { // copy all configurations from parent this.sitemapComponentConfigurations = new HashMap( this.wrappingProcessor.parent.getComponentConfigurations()); } // and now check for new configurations for(int m = 0; m < childs.length; m++) { final String r = this.wrappingProcessor.roleManager.getRoleForName(childs[m].getName()); this.sitemapComponentConfigurations.put(r, new ChainedConfiguration(childs[m], (ChainedConfiguration)this.sitemapComponentConfigurations.get(r))); } } else { // we don't have configurations if ( null == this.wrappingProcessor.parent ) { this.sitemapComponentConfigurations = Collections.EMPTY_MAP; } else { // use configuration from parent this.sitemapComponentConfigurations = this.wrappingProcessor.parent.getComponentConfigurations(); } } } } } return this.sitemapComponentConfigurations; } /** * Mark this processor as needing to be disposed. Actual call to {@link #dispose()} will occur when * all request processings on this processor will be terminated. */ public void markForDisposal() { // Decrement the request count (negative number means dispose) synchronized(this) { this.requestCount--; } if (this.requestCount < 0) { // No more users : dispose right now dispose(); } } public TreeProcessor getWrappingProcessor() { return this.wrappingProcessor; } public Processor getRootProcessor() { return this.wrappingProcessor.getRootProcessor(); } /** * 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 { InvokeContext context = new InvokeContext(); context.enableLogging(getLogger()); try { return process(environment, context); } finally { context.dispose(); } } /** * Process the given <code>Environment</code> to assemble * a <code>ProcessingPipeline</code>. * @since 2.1 */ public ProcessingPipeline buildPipeline(Environment environment) throws Exception { InvokeContext context = new InvokeContext(true); context.enableLogging(getLogger()); try { if (process(environment, context)) { return context.getProcessingPipeline(); } else { return null; } } finally { context.dispose(); } } /** * Do the actual processing, be it producing the response or just building the pipeline * @param environment * @param context * @return true if the pipeline was successfully built, false otherwise. * @throws Exception */ protected boolean process(Environment environment, InvokeContext context) throws Exception { // Increment the concurrent requests count synchronized(this) { requestCount++; } try { // and now process CocoonComponentManager.enterEnvironment(environment, this.sitemapComponentManager, this); Map objectModel = environment.getObjectModel(); Object oldResolver = objectModel.get(ProcessingNode.OBJECT_SOURCE_RESOLVER); final Redirector oldRedirector = context.getRedirector(); // Build a redirector TreeProcessorRedirector redirector = new TreeProcessorRedirector(environment, context); setupLogger(redirector); context.setRedirector(redirector); objectModel.put(ProcessingNode.OBJECT_SOURCE_RESOLVER, environment); boolean success = false; try { success = this.rootNode.invoke(environment, context); return success; } finally { CocoonComponentManager.leaveEnvironment(success); // Restore old redirector and resolver context.setRedirector(oldRedirector); objectModel.put(ProcessingNode.OBJECT_SOURCE_RESOLVER, oldResolver); } } finally { // Decrement the concurrent request count synchronized(this) { requestCount--; } if(requestCount < 0) { // Marked for disposal and no more concurrent requests. dispose(); } } } private boolean handleCocoonRedirect(String uri, Environment environment, InvokeContext context) throws Exception { // Build an environment wrapper // If the current env is a facade, change the delegate and continue processing the facade, since // we may have other redirects that will in turn also change the facade delegate MutableEnvironmentFacade facade = environment instanceof MutableEnvironmentFacade ? ((MutableEnvironmentFacade)environment) : null; if (facade != null) { // Consider the facade delegate (the real environment) environment = facade.getDelegate(); } // test if this is a call from flow boolean isRedirect = (environment.getObjectModel().remove("cocoon:forward") == null); Environment newEnv = new ForwardEnvironmentWrapper(environment, this.sitemapComponentManager, uri, getLogger()); if ( isRedirect ) { ((ForwardEnvironmentWrapper)newEnv).setInternalRedirect(true); } if (facade != null) { // Change the facade delegate facade.setDelegate((EnvironmentWrapper)newEnv); newEnv = facade; } // Get the processor that should process this request // (see https://issues.apache.org/jira/browse/COCOON-1990). ConcreteTreeProcessor processor = this; if (uri.startsWith("cocoon://")) { processor = ((TreeProcessor)getRootProcessor()).concreteProcessor; newEnv.changeContext("", ((TreeProcessor)getRootProcessor()).source.getURI()); } // Process the redirect // No more reset since with TreeProcessorRedirector, we need to pop values from the redirect location // context.reset(); // The following is a fix for bug #26854 and #26571 final boolean result = processor.process(newEnv, context); if ( facade != null ) { newEnv = facade.getDelegate(); } ForwardEnvironmentWrapper forwardEnv = (ForwardEnvironmentWrapper) newEnv; if (forwardEnv.hasRedirected()) { if (forwardEnv.isPermanentRedirection() && environment instanceof PermanentRedirector) { ((PermanentRedirector )environment).permanentRedirect(false, forwardEnv.getRedirectURL()); } else { environment.redirect( false, forwardEnv.getRedirectURL() ); } } return result; } /* (non-Javadoc) * @see org.apache.avalon.framework.activity.Disposable#dispose() */ public void dispose() { if (this.disposableNodes != null) { // we must dispose the nodes in reverse order // otherwise selector nodes are freed before the components node for(int i=this.disposableNodes.size()-1; i>-1; i--) { ((Disposable)disposableNodes.get(i)).dispose(); } this.disposableNodes = null; } // Ensure it won't be used anymore this.rootNode = null; } public String toString() { return "ConcreteTreeProcessor - " + wrappingProcessor.source.getURI(); } private class TreeProcessorRedirector extends ForwardRedirector { private InvokeContext context; public TreeProcessorRedirector(Environment env, InvokeContext context) { super(env); this.context = context; } protected void cocoonRedirect(String uri) throws IOException, ProcessingException { try { ConcreteTreeProcessor.this.handleCocoonRedirect(uri, this.env, this.context); } catch(IOException ioe) { throw ioe; } catch(ProcessingException pe) { throw pe; } catch(RuntimeException re) { throw re; } catch(Exception ex) { throw new ProcessingException(ex); } } } /** * Local extension of EnvironmentWrapper to propagate otherwise blocked * methods to the actual environment. */ private static final class ForwardEnvironmentWrapper extends EnvironmentWrapper { public ForwardEnvironmentWrapper(Environment env, ComponentManager manager, String uri, Logger logger) throws MalformedURLException { super(env, manager, uri, logger, false); } public void setStatus(int statusCode) { environment.setStatus(statusCode); } public void setContentLength(int length) { environment.setContentLength(length); } public void setContentType(String contentType) { environment.setContentType(contentType); } public String getContentType() { return environment.getContentType(); } public boolean isResponseModified(long lastModified) { return environment.isResponseModified(lastModified); } public void setResponseIsNotModified() { environment.setResponseIsNotModified(); } } }