blob: 139a1e9c6c76639d695401629dd4d25b1182e7b5 [file] [log] [blame]
/*
* Copyright 1999-2005 The Apache Software Foundation.
*
* Licensed 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.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.container.ContainerUtil;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.Processor;
import org.apache.cocoon.components.source.impl.SitemapSourceInfo;
import org.apache.cocoon.environment.Environment;
import org.apache.cocoon.environment.ForwardRedirector;
import org.apache.cocoon.environment.Redirector;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.environment.internal.EnvironmentHelper;
import org.apache.cocoon.environment.internal.ForwardEnvironmentWrapper;
import org.apache.cocoon.environment.wrapper.MutableEnvironmentFacade;
import org.apache.cocoon.sitemap.ComponentLocator;
import org.apache.cocoon.sitemap.EnterSitemapEvent;
import org.apache.cocoon.sitemap.EnterSitemapEventListener;
import org.apache.cocoon.sitemap.ExecutionContext;
import org.apache.cocoon.sitemap.LeaveSitemapEvent;
import org.apache.cocoon.sitemap.LeaveSitemapEventListener;
import org.apache.cocoon.sitemap.SitemapExecutor;
import org.apache.cocoon.sitemap.SitemapListener;
import org.apache.cocoon.util.location.Location;
import org.apache.cocoon.util.location.LocationImpl;
import org.apache.commons.jci.listeners.NotificationListener;
/**
* 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, ExecutionContext, NotificationListener {
/** Our ServiceManager */
private ServiceManager manager;
/** Our class loader */
private ClassLoader classloader;
/** The processor that wraps us */
private TreeProcessor wrappingProcessor;
/** Processing nodes that need to be disposed with this processor */
private List disposableNodes;
/** Root node of the processing tree */
private ProcessingNode rootNode;
private Configuration componentConfigurations;
/** Number of simultaneous uses of this processor (either by concurrent request or by internal requests) */
private int requestCount;
/** The sitemap executor */
private SitemapExecutor sitemapExecutor;
/** Optional application container */
private ComponentLocator applicationContainer;
/** Optional event listeners for the enter sitemap event */
private List enterSitemapEventListeners = new ArrayList();
/** Optional event listeners for the leave sitemap event */
private List leaveSitemapEventListeners = new ArrayList();
/** Needs a reload? */
protected volatile boolean needsReload = false;
/** Processor attributes */
protected Map processorAttributes = new HashMap();
/** no listeners by default */
private Map classpathListeners = Collections.EMPTY_MAP;
/**
* Builds a concrete processig, given the wrapping processor
*/
public ConcreteTreeProcessor(TreeProcessor wrappingProcessor,
SitemapExecutor sitemapExecutor) {
// Store our wrapping processor
this.wrappingProcessor = wrappingProcessor;
// Get the sitemap executor - we use the same executor for each sitemap
this.sitemapExecutor = sitemapExecutor;
}
public void handleNotification() {
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + " got notified that a reload is required");
}
needsReload = true;
}
public void setClasspathListeners(Map classpathListeners) {
this.classpathListeners = classpathListeners;
}
public Map getClasspathListeners() {
return this.classpathListeners;
}
/** Set the processor data, result of the treebuilder job */
public void setProcessorData(ServiceManager manager,
ClassLoader classloader,
ProcessingNode rootNode,
List disposableNodes,
ComponentLocator componentLocator,
List enterSitemapEventListeners,
List leaveSitemapEventListeners) {
if (this.rootNode != null) {
throw new IllegalStateException("setProcessorData() can only be called once");
}
this.manager = manager;
this.classloader = classloader;
this.rootNode = rootNode;
this.disposableNodes = disposableNodes;
this.enterSitemapEventListeners = enterSitemapEventListeners;
this.leaveSitemapEventListeners = leaveSitemapEventListeners;
this.applicationContainer = componentLocator;
}
/** Set the sitemap component configurations (called as part of the tree building process) */
public void setComponentConfigurations(Configuration componentConfigurations) {
this.componentConfigurations = componentConfigurations;
}
/**
* @see org.apache.cocoon.Processor#getComponentConfigurations()
*/
public Configuration[] getComponentConfigurations() {
if (this.componentConfigurations == null) {
if (this.wrappingProcessor.parent != null) {
return this.wrappingProcessor.parent.getComponentConfigurations();
}
return null;
}
if (this.wrappingProcessor.parent == null) {
return new Configuration[]{this.componentConfigurations};
}
final Configuration[] parentArray = this.wrappingProcessor.parent.getComponentConfigurations();
if ( parentArray != null ) {
final Configuration[] newArray = new Configuration[parentArray.length + 1];
System.arraycopy(parentArray, 0, newArray, 1, parentArray.length);
newArray[0] = this.componentConfigurations;
return newArray;
}
return new Configuration[] {this.componentConfigurations};
}
/**
* 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();
}
}
boolean isReloadNeeded() {
return needsReload;
}
public TreeProcessor getWrappingProcessor() {
return this.wrappingProcessor;
}
/**
* @see org.apache.cocoon.Processor#getRootProcessor()
*/
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 InternalPipelineDescription buildPipeline(Environment environment)
throws Exception {
InvokeContext context = new InvokeContext(true);
context.enableLogging(getLogger());
try {
if (process(environment, context)) {
return context.getInternalPipelineDescription(environment);
}
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++;
}
Thread currentThread = Thread.currentThread();
ClassLoader oldClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(this.classloader);
try {
// invoke listeners
// only invoke if pipeline is not internally
if ( !context.isBuildingPipelineOnly() && this.enterSitemapEventListeners.size() > 0 ) {
final EnterSitemapEvent enterEvent = new EnterSitemapEvent(this, environment);
final Iterator enterSEI = this.enterSitemapEventListeners.iterator();
while ( enterSEI.hasNext() ) {
final TreeBuilder.EventComponent current = (TreeBuilder.EventComponent)enterSEI.next();
((EnterSitemapEventListener)current.component).enteredSitemap(enterEvent);
}
}
this.sitemapExecutor.enterSitemap(this, environment.getObjectModel(), this.wrappingProcessor.source.getURI());
// and now process
EnvironmentHelper.enterProcessor(this, this.manager, environment);
final Redirector oldRedirector = context.getRedirector();
// Build a redirector
TreeProcessorRedirector redirector = new TreeProcessorRedirector(environment, context);
setupLogger(redirector);
context.setRedirector(redirector);
context.service(this.manager);
context.setLastProcessor(this);
try {
final boolean success = this.rootNode.invoke(environment, context);
return success;
} finally {
EnvironmentHelper.leaveProcessor();
// Restore old redirector
context.setRedirector(oldRedirector);
}
} finally {
this.sitemapExecutor.leaveSitemap(this, environment.getObjectModel());
// invoke listeners
// only invoke if pipeline is not internally
if ( !context.isBuildingPipelineOnly() && this.leaveSitemapEventListeners.size() > 0 ) {
final LeaveSitemapEvent leaveEvent = new LeaveSitemapEvent(this, environment);
final Iterator leaveSEI = this.leaveSitemapEventListeners.iterator();
while ( leaveSEI.hasNext() ) {
final TreeBuilder.EventComponent current = (TreeBuilder.EventComponent)leaveSEI.next();
((LeaveSitemapEventListener)current.component).leftSitemap(leaveEvent);
}
}
// Restore classloader
currentThread.setContextClassLoader(oldClassLoader);
// Decrement the concurrent request count
synchronized (this) {
requestCount--;
}
if (requestCount < 0) {
// Marked for disposal and no more concurrent requests.
dispose();
}
}
}
protected 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);
final SitemapSourceInfo info = SitemapSourceInfo.parseURI(environment, uri);
Environment newEnv = new ForwardEnvironmentWrapper(environment, info, getLogger());
if (isRedirect) {
((ForwardEnvironmentWrapper) newEnv).setInternalRedirect(true);
}
if (facade != null) {
// Change the facade delegate
facade.setDelegate((ForwardEnvironmentWrapper)newEnv);
newEnv = facade;
}
// Get the processor that should process this request
ConcreteTreeProcessor processor;
if (newEnv.getURIPrefix().equals("")) {
processor = ((TreeProcessor)getRootProcessor()).concreteProcessor;
} else {
processor = this;
}
// 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();
}
if (((ForwardEnvironmentWrapper) newEnv).getRedirectURL() != null) {
environment.redirect(((ForwardEnvironmentWrapper) newEnv).getRedirectURL(), false, false);
}
return result;
}
/**
* @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;
this.sitemapExecutor = null;
// dispose listeners
this.disposeListeners(this.enterSitemapEventListeners);
this.disposeListeners(this.leaveSitemapEventListeners);
// dispose component locator - if it is a SitemapListener it is already disposed!
if ( !(this.applicationContainer instanceof SitemapListener) ) {
ContainerUtil.dispose(this.applicationContainer);
}
this.applicationContainer = null;
}
protected void disposeListeners(List l) {
Iterator i = l.iterator();
while ( i.hasNext() ) {
final TreeBuilder.EventComponent current = (TreeBuilder.EventComponent)i.next();
if ( current.releaseUsingManager ) {
this.manager.release(current.component);
} else {
ContainerUtil.dispose(current.component);
}
}
l.clear();
}
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 e) {
throw e;
} catch (ProcessingException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new ProcessingException(e);
}
}
}
public SourceResolver getSourceResolver() {
return wrappingProcessor.getSourceResolver();
}
public String getContext() {
return wrappingProcessor.getContext();
}
/**
* Return the sitemap executor
*/
public SitemapExecutor getSitemapExecutor() {
return this.sitemapExecutor;
}
public ServiceManager getServiceManager() {
return this.manager;
}
/**
* @see org.apache.cocoon.Processor#getAttribute(java.lang.String)
*/
public Object getAttribute(String name) {
return this.processorAttributes.get(name);
}
/**
* @see org.apache.cocoon.Processor#removeAttribute(java.lang.String)
*/
public Object removeAttribute(String name) {
return this.processorAttributes.remove(name);
}
/**
* @see org.apache.cocoon.Processor#setAttribute(java.lang.String, java.lang.Object)
*/
public void setAttribute(String name, Object value) {
this.processorAttributes.put(name, value);
}
/**
* @see org.apache.cocoon.sitemap.ExecutionContext#getLocation()
*/
public Location getLocation() {
return new LocationImpl("[sitemap]", this.wrappingProcessor.source.getURI());
}
/**
* @see org.apache.cocoon.sitemap.ExecutionContext#getType()
*/
public String getType() {
return "sitemap";
}
}