blob: e85b0f2dbd7581139e2a0e077c98fc5348de2dd1 [file] [log] [blame]
/*
* Copyright 1999-2004 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.pipeline;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.Constants;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.Processor;
import org.apache.cocoon.environment.Environment;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.environment.internal.EnvironmentHelper;
import org.apache.cocoon.generation.Generator;
import org.apache.cocoon.serialization.Serializer;
import org.apache.cocoon.sitemap.SitemapErrorHandler;
import org.apache.cocoon.sitemap.SitemapModelComponent;
import org.apache.cocoon.transformation.Transformer;
import org.apache.cocoon.util.location.Locatable;
import org.apache.cocoon.util.location.Location;
import org.apache.cocoon.xml.SaxBuffer;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.cocoon.xml.XMLProducer;
import org.apache.excalibur.source.SourceValidity;
import org.xml.sax.SAXException;
/**
* Pipeline used by virtual pipeline components
*
* @version $Id$
*/
public class VirtualProcessingPipeline extends AbstractLogEnabled
implements ProcessingPipeline, Recyclable, Serviceable {
// Resolver of the defining sitemap
private SourceResolver resolver;
// Generator stuff
protected Generator generator;
protected Parameters generatorParam;
protected String generatorSource;
// Transformer stuff
protected ArrayList transformers = new ArrayList();
protected ArrayList transformerParams = new ArrayList();
protected ArrayList transformerSources = new ArrayList();
// Serializer stuff
protected Serializer serializer;
protected Parameters serializerParam;
protected String serializerSource;
protected String serializerMimeType;
// Error handler stuff
private SitemapErrorHandler errorHandler;
private Processor.InternalPipelineDescription errorPipeline;
/**
* True when pipeline has been prepared.
*/
private boolean prepared;
/**
* This is the first consumer component in the pipeline, either
* the first transformer or the serializer.
*/
protected XMLConsumer firstConsumer;
/**
* This is the last component in the pipeline, either the serializer
* or a custom xmlconsumer for the cocoon: protocol etc.
*/
protected XMLConsumer lastConsumer;
/** The component manager set with compose() */
protected ServiceManager manager;
/** The component manager set with compose() and recompose() */
protected ServiceManager newManager;
/** The current Processor */
protected Processor processor;
public VirtualProcessingPipeline(Context context) throws Exception {
this.resolver = (EnvironmentHelper) context.get(Constants.CONTEXT_ENV_HELPER);
}
public void service(ServiceManager manager)
throws ServiceException {
this.manager = manager;
this.newManager = manager;
}
/**
* Set the processor's service manager
*/
public void setProcessorManager(ServiceManager manager) {
this.newManager = manager;
}
/**
* Setup this component
*/
public void setup(Parameters params) {
}
/**
* Get the generator - used for content aggregation
*/
public Generator getGenerator() {
return this.generator;
}
/**
* Informs pipeline we have come across a branch point
* Default Behaviour is do nothing
*/
public void informBranchPoint() {
// this can be overwritten in subclasses
}
/**
* Set the generator that will be used as the initial step in the pipeline.
* The generator role is given : the actual <code>Generator</code> is fetched
* from the latest <code>ServiceManager</code>.
*
* @param role the generator role in the component manager.
* @param source the source where to produce XML from, or <code>null</code> if no
* source is given.
* @param param the parameters for the generator.
* @throws org.apache.cocoon.ProcessingException if the generator couldn't be obtained.
*/
public void setGenerator(String role, String source, Parameters param, Parameters hintParam)
throws ProcessingException {
if (this.generator != null) {
throw new ProcessingException ("Generator already set. Cannot set generator '" + role + "'",
getLocation(param));
}
try {
this.generator = (Generator) this.newManager.lookup(Generator.ROLE + '/' + role);
} catch (ServiceException ce) {
throw ProcessingException.throwLocated("Lookup of generator '" + role + "' failed", ce, getLocation(param));
}
this.generatorSource = source;
this.generatorParam = param;
}
/**
* Add a transformer at the end of the pipeline.
* The transformer role is given : the actual <code>Transformer</code> is fetched
* from the latest <code>ServiceManager</code>.
*
* @param role the transformer role in the component manager.
* @param source the source used to setup the transformer (e.g. XSL file), or
* <code>null</code> if no source is given.
* @param param the parameters for the transfomer.
*/
public void addTransformer(String role, String source, Parameters param, Parameters hintParam)
throws ProcessingException {
try {
this.transformers.add(this.newManager.lookup(Transformer.ROLE + '/' + role));
} catch (ServiceException ce) {
throw ProcessingException.throwLocated("Lookup of transformer '"+role+"' failed", ce, getLocation(param));
}
this.transformerSources.add(source);
this.transformerParams.add(param);
}
/**
* Set the serializer for this pipeline
* @param mimeType Can be null
*/
public void setSerializer(String role, String source, Parameters param, Parameters hintParam, String mimeType)
throws ProcessingException {
if (this.serializer != null) {
// Should normally not happen as adding a serializer starts pipeline processing
throw new ProcessingException ("Serializer already set. Cannot set serializer '" + role + "'",
getLocation(param));
}
try {
this.serializer = (Serializer)this.newManager.lookup(Serializer.ROLE + '/' + role);
} catch (ServiceException ce) {
throw ProcessingException.throwLocated("Lookup of serializer '" + role + "' failed", ce, getLocation(param));
}
this.serializerSource = source;
this.serializerParam = param;
this.serializerMimeType = mimeType;
this.lastConsumer = this.serializer;
}
/**
* Sets error handler for this pipeline.
* Used for handling errors in the internal pipelines.
* @param errorHandler error handler
*/
public void setErrorHandler(SitemapErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
/**
* Sanity check
* @return true if the pipeline is 'sane', for VPCs all pipelines are sane
*/
protected boolean checkPipeline() {
return true;
}
/**
* Setup pipeline components.
*/
protected void setupPipeline(Environment environment)
throws ProcessingException {
try {
// SourceResolver resolver = this.processor.getSourceResolver();
// setup the generator
if (this.generator != null) {
this.generator.setup(resolver,
environment.getObjectModel(),
generatorSource,
generatorParam);
}
Iterator transformerItt = this.transformers.iterator();
Iterator transformerSourceItt = this.transformerSources.iterator();
Iterator transformerParamItt = this.transformerParams.iterator();
while (transformerItt.hasNext()) {
Transformer trans = (Transformer)transformerItt.next();
trans.setup(resolver,
environment.getObjectModel(),
(String)transformerSourceItt.next(),
(Parameters)transformerParamItt.next()
);
}
if (this.serializer != null && this.serializer instanceof SitemapModelComponent) {
((SitemapModelComponent)this.serializer).setup(
resolver,
environment.getObjectModel(),
this.serializerSource,
this.serializerParam
);
}
} catch (SAXException e) {
throw new ProcessingException("Could not setup pipeline.", e);
} catch (IOException e) {
throw new ProcessingException("Could not setup pipeline.", e);
}
}
/**
* Connect the next component
*/
protected void connect(Environment environment,
XMLProducer producer,
XMLConsumer consumer)
throws ProcessingException {
// Connect next component.
producer.setConsumer(consumer);
}
/**
* Connect the XML pipeline.
*/
protected void connectPipeline(Environment environment)
throws ProcessingException {
XMLProducer prev = this.generator;
Iterator itt = this.transformers.iterator();
// No generator for VPC transformer and serializer
if (this.generator == null && itt.hasNext()) {
this.firstConsumer = (XMLConsumer)(prev = (XMLProducer)itt.next());
}
while (itt.hasNext()) {
Transformer next = (Transformer) itt.next();
connect(environment, prev, next);
prev = next;
}
// insert the serializer
if (prev != null) {
connect(environment, prev, this.lastConsumer);
} else {
this.firstConsumer = this.lastConsumer;
}
}
/**
* Prepare the pipeline
*/
protected void preparePipeline(Environment environment)
throws ProcessingException {
// TODO (CZ) Get the processor set via IoC
this.processor = EnvironmentHelper.getCurrentProcessor();
if (!checkPipeline()) {
throw new ProcessingException("Attempted to process incomplete pipeline.");
}
if (this.prepared) {
throw new ProcessingException("Duplicate preparePipeline call caught.");
}
setupPipeline(environment);
this.prepared = true;
}
/**
* Prepare an internal processing
* @param environment The current environment.
* @throws org.apache.cocoon.ProcessingException
*/
public void prepareInternal(Environment environment)
throws ProcessingException {
this.lastConsumer = null;
try {
preparePipeline(environment);
} catch (ProcessingException e) {
prepareInternalErrorHandler(environment, e);
}
}
/**
* If prepareInternal fails, prepare internal error handler.
*/
protected void prepareInternalErrorHandler(Environment environment, ProcessingException ex)
throws ProcessingException {
if (this.errorHandler != null) {
try {
this.errorPipeline = this.errorHandler.prepareErrorPipeline(ex);
if (this.errorPipeline != null) {
this.errorPipeline.processingPipeline.prepareInternal(environment);
return;
}
} catch (ProcessingException e) {
throw e;
} catch (Exception e) {
throw new ProcessingException("Failed to handle exception <" + ex + ">", e);
}
}
}
/**
* @return true if error happened during internal pipeline prepare call.
*/
protected boolean isInternalError() {
return this.errorPipeline != null;
}
public void setReader(String role, String source, Parameters param, String mimeType) throws ProcessingException {
throw new UnsupportedOperationException();
}
public boolean process(Environment environment) throws ProcessingException {
if (!this.prepared) {
preparePipeline(environment);
}
// If this is an internal request, lastConsumer was reset!
if (this.lastConsumer == null) {
this.lastConsumer = this.serializer;
}
connectPipeline(environment);
return processXMLPipeline(environment);
}
/**
* Process the given <code>Environment</code>, but do not use the
* serializer. Instead the sax events are streamed to the XMLConsumer.
*/
public boolean process(Environment environment, XMLConsumer consumer)
throws ProcessingException {
// Exception happened during setup and was handled
if (this.errorPipeline != null) {
return this.errorPipeline.processingPipeline.process(environment, consumer);
}
// Have to buffer events if error handler is specified.
SaxBuffer buffer = null;
this.lastConsumer = this.errorHandler == null? consumer: (buffer = new SaxBuffer());
try {
connectPipeline(environment);
return processXMLPipeline(environment);
} catch (ProcessingException e) {
buffer = null;
return processErrorHandler(environment, e, consumer);
} finally {
if (buffer != null) {
try {
buffer.toSAX(consumer);
} catch (SAXException e) {
throw new ProcessingException("Failed to execute pipeline.", e);
}
}
}
}
/**
* Get the first consumer - used for VPC transformers
*/
public XMLConsumer getXMLConsumer(Environment environment, XMLConsumer consumer)
throws ProcessingException {
if (!this.prepared) {
preparePipeline(environment);
}
this.lastConsumer = consumer;
connectPipeline(environment);
if (this.firstConsumer == null)
throw new ProcessingException("A VPC transformer pipeline should not contain a generator.");
return this.firstConsumer;
}
/**
* Get the first consumer - used for VPC serializers
*/
public XMLConsumer getXMLConsumer(Environment environment) throws ProcessingException {
if (!this.prepared) {
preparePipeline(environment);
}
// If this is an internal request, lastConsumer was reset!
if (this.lastConsumer == null) {
this.lastConsumer = this.serializer;
}
connectPipeline(environment);
if (this.serializer == null) {
throw new ProcessingException("A VPC serializer pipeline must contain a serializer.");
}
try {
this.serializer.setOutputStream(environment.getOutputStream(0));
} catch (Exception e) {
throw new ProcessingException("Couldn't set output stream ", e);
}
if (this.firstConsumer == null)
throw new ProcessingException("A VPC serializer pipeline should not contain a generator.");
return this.firstConsumer;
}
/**
* Get the mime-type for the serializer
*/
public String getMimeType() {
if (this.lastConsumer == null) {
// internal processing: text/xml
return "text/xml";
} else {
// Get the mime-type
if (serializerMimeType != null) {
// there was a serializer defined in the sitemap
return serializerMimeType;
} else {
// ask to the component itself
return this.serializer.getMimeType();
}
}
}
/**
* Test if the serializer wants to set the content length
*/
public boolean shouldSetContentLength() {
return this.serializer.shouldSetContentLength();
}
/**
* Process the SAX event pipeline
*/
protected boolean processXMLPipeline(Environment environment)
throws ProcessingException {
try {
if (this.lastConsumer == null || this.serializer == null) {
// internal processing
this.generator.generate();
} else {
if (this.serializer.shouldSetContentLength()) {
// set the output stream
ByteArrayOutputStream os = new ByteArrayOutputStream();
this.serializer.setOutputStream(os);
// execute the pipeline:
this.generator.generate();
environment.setContentLength(os.size());
os.writeTo(environment.getOutputStream(0));
} else {
// set the output stream
this.serializer.setOutputStream(environment.getOutputStream(0));
// execute the pipeline:
this.generator.generate();
}
}
} catch (ProcessingException e) {
throw e;
} catch (Exception e) {
// TODO: Unwrap SAXException ?
throw new ProcessingException("Failed to execute pipeline.", e);
}
return true;
}
public void recycle() {
this.prepared = false;
if (this.generator != null) {
// Release generator.
this.newManager.release(this.generator);
this.generator = null;
this.generatorParam = null;
}
// Release transformers
int size = this.transformers.size();
for (int i = 0; i < size; i++) {
this.newManager.release(this.transformers.get(i));
}
this.transformers.clear();
this.transformerParams.clear();
this.transformerSources.clear();
// Release serializer
if (this.serializer != null) {
this.newManager.release(this.serializer);
this.serializerParam = null;
}
this.serializer = null;
this.processor = null;
this.lastConsumer = null;
this.firstConsumer = null;
// Release error handler
this.errorHandler = null;
if (this.errorPipeline != null) {
this.errorPipeline.release();
this.errorPipeline = null;
}
}
protected boolean processErrorHandler(Environment environment, ProcessingException e, XMLConsumer consumer)
throws ProcessingException {
if (this.errorHandler != null) {
try {
this.errorPipeline = this.errorHandler.prepareErrorPipeline(e);
if (this.errorPipeline != null) {
this.errorPipeline.processingPipeline.prepareInternal(environment);
return this.errorPipeline.processingPipeline.process(environment, consumer);
}
} catch (Exception ignored) {
getLogger().debug("Exception in error handler", ignored);
}
}
throw e;
}
/**
* Return valid validity objects for the event pipeline
* If the "event pipeline" (= the complete pipeline without the
* serializer) is cacheable and valid, return all validity objects.
* Otherwise return <code>null</code>
*/
public SourceValidity getValidityForEventPipeline() {
return null;
}
public String getKeyForEventPipeline() {
return null;
}
protected Location getLocation(Parameters param) {
Location value = null;
if (param instanceof Locatable) {
value = ((Locatable) param).getLocation();
}
if (value == null) {
value = Location.UNKNOWN;
}
return value;
}
}