0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.cocoon.components.pipeline;
0018:
0019: import java.io.ByteArrayOutputStream;
0020: import java.io.IOException;
0021: import java.net.SocketException;
0022: import java.util.ArrayList;
0023: import java.util.Iterator;
0024: import java.util.NoSuchElementException;
0025: import java.util.StringTokenizer;
0026:
0027: import org.apache.avalon.excalibur.pool.Recyclable;
0028: import org.apache.avalon.framework.component.Component;
0029: import org.apache.avalon.framework.component.ComponentException;
0030: import org.apache.avalon.framework.component.ComponentManager;
0031: import org.apache.avalon.framework.component.ComponentSelector;
0032: import org.apache.avalon.framework.logger.AbstractLogEnabled;
0033: import org.apache.avalon.framework.parameters.ParameterException;
0034: import org.apache.avalon.framework.parameters.Parameterizable;
0035: import org.apache.avalon.framework.parameters.Parameters;
0036: import org.apache.cocoon.ConnectionResetException;
0037: import org.apache.cocoon.ProcessingException;
0038: import org.apache.cocoon.components.CocoonComponentManager;
0039: import org.apache.cocoon.environment.Environment;
0040: import org.apache.cocoon.environment.ObjectModelHelper;
0041: import org.apache.cocoon.environment.Response;
0042: import org.apache.cocoon.generation.Generator;
0043: import org.apache.cocoon.reading.Reader;
0044: import org.apache.cocoon.serialization.Serializer;
0045: import org.apache.cocoon.sitemap.SitemapErrorHandler;
0046: import org.apache.cocoon.sitemap.SitemapModelComponent;
0047: import org.apache.cocoon.transformation.Transformer;
0048: import org.apache.cocoon.util.location.Locatable;
0049: import org.apache.cocoon.util.location.Location;
0050: import org.apache.cocoon.xml.SaxBuffer;
0051: import org.apache.cocoon.xml.XMLConsumer;
0052: import org.apache.cocoon.xml.XMLProducer;
0053: import org.apache.excalibur.source.SourceValidity;
0054: import org.xml.sax.SAXException;
0055:
0056: /**
0057: * This is the base for all implementations of a <code>ProcessingPipeline</code>.
0058: *
0059: * @since 2.1
0060: * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
0061: * @version CVS $Id: AbstractProcessingPipeline.java 451148 2006-09-29 07:50:00Z anathaniel $
0062: */
0063: public abstract class AbstractProcessingPipeline extends
0064: AbstractLogEnabled implements ProcessingPipeline,
0065: Parameterizable, Recyclable {
0066:
0067: // Generator stuff
0068: protected Generator generator;
0069: protected Parameters generatorParam;
0070: protected String generatorSource;
0071: protected ComponentSelector generatorSelector;
0072:
0073: // Transformer stuff
0074: protected ArrayList transformers = new ArrayList();
0075: protected ArrayList transformerParams = new ArrayList();
0076: protected ArrayList transformerSources = new ArrayList();
0077: protected ArrayList transformerSelectors = new ArrayList();
0078:
0079: // Serializer stuff
0080: protected Serializer serializer;
0081: protected Parameters serializerParam;
0082: protected String serializerSource;
0083: protected String serializerMimeType;
0084: protected String sitemapSerializerMimeType;
0085: protected OutputComponentSelector serializerSelector;
0086:
0087: // Reader stuff
0088: protected Reader reader;
0089: protected Parameters readerParam;
0090: protected String readerSource;
0091: protected String readerMimeType;
0092: protected String sitemapReaderMimeType;
0093: protected OutputComponentSelector readerSelector;
0094:
0095: // Error handler stuff
0096: private SitemapErrorHandler errorHandler;
0097: private ProcessingPipeline errorPipeline;
0098:
0099: /** True when pipeline has been prepared. */
0100: private boolean prepared;
0101:
0102: /**
0103: * This is the last component in the pipeline, either the serializer
0104: * or a custom XML consumer in case of internal processing.
0105: */
0106: protected XMLConsumer lastConsumer;
0107:
0108: /** The component manager set with compose() */
0109: protected ComponentManager manager;
0110:
0111: /** The component manager set with compose() and recompose() */
0112: protected ComponentManager newManager;
0113:
0114: /** The configuration */
0115: protected Parameters configuration;
0116:
0117: /** Configured Expires value */
0118: protected long configuredExpires;
0119:
0120: /** Configured Output Buffer Size */
0121: protected int configuredOutputBufferSize;
0122:
0123: /** The parameters */
0124: protected Parameters parameters;
0125:
0126: /** Expires value */
0127: protected long expires;
0128:
0129: /** Output Buffer Size */
0130: protected int outputBufferSize;
0131:
0132: /**
0133: * Composable Interface
0134: */
0135: public void compose(ComponentManager manager)
0136: throws ComponentException {
0137: this .manager = manager;
0138: this .newManager = manager;
0139: }
0140:
0141: /**
0142: * Recomposable Interface
0143: */
0144: public void recompose(ComponentManager manager)
0145: throws ComponentException {
0146: this .newManager = manager;
0147: }
0148:
0149: /**
0150: * Parameterizable Interface - Configuration
0151: */
0152: public void parameterize(Parameters params)
0153: throws ParameterException {
0154: this .configuration = params;
0155: final String expiresValue = params
0156: .getParameter("expires", null);
0157: if (expiresValue != null) {
0158: this .configuredExpires = parseExpires(expiresValue);
0159: }
0160: this .configuredOutputBufferSize = params.getParameterAsInteger(
0161: "outputBufferSize", -1);
0162: }
0163:
0164: /**
0165: * Setup this component
0166: */
0167: public void setup(Parameters params) {
0168: this .parameters = params;
0169: final String expiresValue = params
0170: .getParameter("expires", null);
0171: if (expiresValue != null) {
0172: this .expires = parseExpires(expiresValue);
0173: } else {
0174: this .expires = this .configuredExpires;
0175: }
0176: this .outputBufferSize = params.getParameterAsInteger(
0177: "outputBufferSize", this .configuredOutputBufferSize);
0178: }
0179:
0180: /**
0181: * Release this component.
0182: * If you get an instance not by a component manager but for example
0183: * by a processor, you have to release this component by calling
0184: * this method and NOT by using a component manager!
0185: */
0186: public void release() {
0187: try {
0188: CocoonComponentManager.removeFromAutomaticRelease(this );
0189: } catch (ProcessingException e) {
0190: // ignore this
0191: getLogger()
0192: .error(
0193: "Unable to release self from automatic release.",
0194: e);
0195: }
0196: }
0197:
0198: /**
0199: * Informs pipeline we have come across a branch point.
0200: * Default behaviour is do nothing.
0201: */
0202: public void informBranchPoint() {
0203: // this can be overwritten in subclasses
0204: }
0205:
0206: /**
0207: * Get the generator - used for content aggregation
0208: */
0209: public Generator getGenerator() {
0210: return this .generator;
0211: }
0212:
0213: /**
0214: * Set the generator that will be used as the initial step in the pipeline.
0215: * The generator role is given : the actual <code>Generator</code> is fetched
0216: * from the latest <code>ComponentManager</code> given by <code>compose()</code>
0217: * or <code>recompose()</code>.
0218: *
0219: * @param role the generator role in the component manager.
0220: * @param source the source where to produce XML from, or <code>null</code> if no
0221: * source is given.
0222: * @param param the parameters for the generator.
0223: * @throws ProcessingException if the generator couldn't be obtained.
0224: */
0225: public void setGenerator(String role, String source,
0226: Parameters param, Parameters hintParam)
0227: throws ProcessingException {
0228: if (this .generator != null) {
0229: throw new ProcessingException(
0230: "Generator already set. Cannot set generator '"
0231: + role + "'", getLocation(param));
0232: }
0233: if (this .reader != null) {
0234: throw new ProcessingException(
0235: "Reader already set. Cannot set generator '" + role
0236: + "'", getLocation(param));
0237: }
0238: try {
0239: this .generatorSelector = (ComponentSelector) this .newManager
0240: .lookup(Generator.ROLE + "Selector");
0241: } catch (ComponentException ce) {
0242: throw ProcessingException.throwLocated(
0243: "Lookup of generator selector failed", ce,
0244: getLocation(param));
0245: }
0246: try {
0247: this .generator = (Generator) this .generatorSelector
0248: .select(role);
0249: } catch (ComponentException ce) {
0250: throw ProcessingException.throwLocated(
0251: "Lookup of generator '" + role + "' failed", ce,
0252: getLocation(param));
0253: }
0254: this .generatorSource = source;
0255: this .generatorParam = param;
0256: }
0257:
0258: /**
0259: * Add a transformer at the end of the pipeline.
0260: * The transformer role is given : the actual <code>Transformer</code> is fetched
0261: * from the latest <code>ComponentManager</code> given by <code>compose()</code>
0262: * or <code>recompose()</code>.
0263: *
0264: * @param role the transformer role in the component manager.
0265: * @param source the source used to setup the transformer (e.g. XSL file), or
0266: * <code>null</code> if no source is given.
0267: * @param param the parameters for the transfomer.
0268: * @throws ProcessingException if the generator couldn't be obtained.
0269: */
0270: public void addTransformer(String role, String source,
0271: Parameters param, Parameters hintParam)
0272: throws ProcessingException {
0273: if (this .reader != null) {
0274: // Should normally never happen as setting a reader starts pipeline processing
0275: throw new ProcessingException(
0276: "Reader already set. Cannot add transformer '"
0277: + role + "'", getLocation(param));
0278: }
0279: if (this .generator == null) {
0280: throw new ProcessingException(
0281: "Must set a generator before adding transformer '"
0282: + role + "'", getLocation(param));
0283: }
0284: ComponentSelector selector = null;
0285: try {
0286: selector = (ComponentSelector) this .newManager
0287: .lookup(Transformer.ROLE + "Selector");
0288: } catch (ComponentException ce) {
0289: throw ProcessingException.throwLocated(
0290: "Lookup of transformer selector failed", ce,
0291: getLocation(param));
0292: }
0293: try {
0294: this .transformers.add(selector.select(role));
0295: this .transformerSelectors.add(selector);
0296: } catch (ComponentException ce) {
0297: throw ProcessingException.throwLocated(
0298: "Lookup of transformer '" + role + "' failed", ce,
0299: getLocation(param));
0300: }
0301: this .transformerSources.add(source);
0302: this .transformerParams.add(param);
0303: }
0304:
0305: /**
0306: * Set the serializer for this pipeline
0307: * @param mimeType Can be null
0308: */
0309: public void setSerializer(String role, String source,
0310: Parameters param, Parameters hintParam, String mimeType)
0311: throws ProcessingException {
0312: if (this .serializer != null) {
0313: // Should normally not happen as adding a serializer starts pipeline processing
0314: throw new ProcessingException(
0315: "Serializer already set. Cannot set serializer '"
0316: + role + "'", getLocation(param));
0317: }
0318: if (this .reader != null) {
0319: // Should normally never happen as setting a reader starts pipeline processing
0320: throw new ProcessingException(
0321: "Reader already set. Cannot set serializer '"
0322: + role + "'", getLocation(param));
0323: }
0324: if (this .generator == null) {
0325: throw new ProcessingException(
0326: "Must set a generator before setting serializer '"
0327: + role + "'", getLocation(param));
0328: }
0329:
0330: try {
0331: this .serializerSelector = (OutputComponentSelector) this .newManager
0332: .lookup(Serializer.ROLE + "Selector");
0333: } catch (ComponentException ce) {
0334: throw ProcessingException.throwLocated(
0335: "Lookup of serializer selector failed", ce,
0336: getLocation(param));
0337: }
0338: try {
0339: this .serializer = (Serializer) serializerSelector
0340: .select(role);
0341: } catch (ComponentException ce) {
0342: throw ProcessingException.throwLocated(
0343: "Lookup of serializer '" + role + "' failed", ce,
0344: getLocation(param));
0345: }
0346: this .serializerSource = source;
0347: this .serializerParam = param;
0348: this .serializerMimeType = mimeType;
0349: this .sitemapSerializerMimeType = serializerSelector
0350: .getMimeTypeForHint(role);
0351: this .lastConsumer = this .serializer;
0352: }
0353:
0354: /**
0355: * Set the reader for this pipeline
0356: * @param mimeType Can be null
0357: */
0358: public void setReader(String role, String source, Parameters param,
0359: String mimeType) throws ProcessingException {
0360: if (this .reader != null) {
0361: // Should normally never happen as setting a reader starts pipeline processing
0362: throw new ProcessingException(
0363: "Reader already set. Cannot set reader '" + role
0364: + "'", getLocation(param));
0365: }
0366: if (this .generator != null) {
0367: // Should normally never happen as setting a reader starts pipeline processing
0368: throw new ProcessingException(
0369: "Generator already set. Cannot use reader '" + role
0370: + "'", getLocation(param));
0371: }
0372:
0373: try {
0374: this .readerSelector = (OutputComponentSelector) this .newManager
0375: .lookup(Reader.ROLE + "Selector");
0376: } catch (ComponentException ce) {
0377: throw ProcessingException.throwLocated(
0378: "Lookup of reader selector failed", ce,
0379: getLocation(param));
0380: }
0381: try {
0382: this .reader = (Reader) readerSelector.select(role);
0383: } catch (ComponentException ce) {
0384: throw ProcessingException.throwLocated("Lookup of reader '"
0385: + role + "' failed", ce, getLocation(param));
0386: }
0387: this .readerSource = source;
0388: this .readerParam = param;
0389: this .readerMimeType = mimeType;
0390: this .sitemapReaderMimeType = readerSelector
0391: .getMimeTypeForHint(role);
0392: }
0393:
0394: /**
0395: * Sets error handler for this pipeline.
0396: * Used for handling errors in the internal pipelines.
0397: * @param errorHandler error handler
0398: */
0399: public void setErrorHandler(SitemapErrorHandler errorHandler) {
0400: this .errorHandler = errorHandler;
0401: }
0402:
0403: /**
0404: * Sanity check
0405: * @return true if the pipeline is 'sane', false otherwise.
0406: */
0407: protected boolean checkPipeline() {
0408: if (this .generator == null && this .reader == null) {
0409: return false;
0410: }
0411:
0412: if (this .generator != null && this .serializer == null) {
0413: return false;
0414: }
0415:
0416: return true;
0417: }
0418:
0419: /**
0420: * Setup pipeline components.
0421: */
0422: protected void setupPipeline(Environment environment)
0423: throws ProcessingException {
0424: try {
0425: // setup the generator
0426: this .generator.setup(environment, environment
0427: .getObjectModel(), generatorSource, generatorParam);
0428:
0429: Iterator transformerItt = this .transformers.iterator();
0430: Iterator transformerSourceItt = this .transformerSources
0431: .iterator();
0432: Iterator transformerParamItt = this .transformerParams
0433: .iterator();
0434:
0435: while (transformerItt.hasNext()) {
0436: Transformer trans = (Transformer) transformerItt.next();
0437: trans.setup(environment, environment.getObjectModel(),
0438: (String) transformerSourceItt.next(),
0439: (Parameters) transformerParamItt.next());
0440: }
0441:
0442: if (this .serializer instanceof SitemapModelComponent) {
0443: ((SitemapModelComponent) this .serializer).setup(
0444: environment, environment.getObjectModel(),
0445: this .serializerSource, this .serializerParam);
0446: }
0447: } catch (Exception e) {
0448: handleException(e);
0449: }
0450: }
0451:
0452: /**
0453: * Connect the next component
0454: */
0455: protected void connect(Environment environment,
0456: XMLProducer producer, XMLConsumer consumer)
0457: throws ProcessingException {
0458: // Connect next component.
0459: producer.setConsumer(consumer);
0460: }
0461:
0462: /**
0463: * Connect the XML pipeline.
0464: */
0465: protected void connectPipeline(Environment environment)
0466: throws ProcessingException {
0467: XMLProducer prev = this .generator;
0468:
0469: Iterator itt = this .transformers.iterator();
0470: while (itt.hasNext()) {
0471: Transformer next = (Transformer) itt.next();
0472: connect(environment, prev, next);
0473: prev = next;
0474: }
0475:
0476: // insert the serializer
0477: connect(environment, prev, this .lastConsumer);
0478: }
0479:
0480: /**
0481: * Process the given <code>Environment</code>, producing the output.
0482: */
0483: public boolean process(Environment environment)
0484: throws ProcessingException {
0485: if (!this .prepared) {
0486: preparePipeline(environment);
0487: }
0488:
0489: // See if we need to set an "Expires:" header
0490: if (this .expires != 0) {
0491: Response res = ObjectModelHelper.getResponse(environment
0492: .getObjectModel());
0493: res.setDateHeader("Expires", System.currentTimeMillis()
0494: + expires);
0495: res.setHeader("Cache-Control", "max-age=" + expires / 1000
0496: + ", public");
0497: if (getLogger().isDebugEnabled()) {
0498: getLogger()
0499: .debug(
0500: "Setting a new Expires object for this resource");
0501: }
0502: environment.getObjectModel().put(
0503: ObjectModelHelper.EXPIRES_OBJECT,
0504: new Long(expires + System.currentTimeMillis()));
0505: }
0506:
0507: if (this .reader != null) {
0508: if (checkIfModified(environment, this .reader
0509: .getLastModified())) {
0510: return true;
0511: }
0512:
0513: return processReader(environment);
0514: } else {
0515: // If this is an internal request, lastConsumer was reset!
0516: if (this .lastConsumer == null) {
0517: this .lastConsumer = this .serializer;
0518: }
0519:
0520: connectPipeline(environment);
0521: return processXMLPipeline(environment);
0522: }
0523: }
0524:
0525: /**
0526: * Prepare the pipeline
0527: */
0528: protected void preparePipeline(Environment environment)
0529: throws ProcessingException {
0530: if (!checkPipeline()) {
0531: throw new ProcessingException(
0532: "Attempted to process incomplete pipeline.");
0533: }
0534:
0535: if (this .prepared) {
0536: throw new ProcessingException(
0537: "Duplicate preparePipeline call caught.");
0538: }
0539:
0540: if (this .reader != null) {
0541: setupReader(environment);
0542: } else {
0543: setupPipeline(environment);
0544: }
0545: this .prepared = true;
0546: }
0547:
0548: /**
0549: * Prepare an internal processing.
0550: * @param environment The current environment.
0551: * @throws ProcessingException
0552: */
0553: public void prepareInternal(Environment environment)
0554: throws ProcessingException {
0555: this .lastConsumer = null;
0556: try {
0557: preparePipeline(environment);
0558: } catch (ProcessingException e) {
0559: prepareInternalErrorHandler(environment, e);
0560: }
0561: }
0562:
0563: /**
0564: * If prepareInternal fails, prepare internal error handler.
0565: */
0566: protected void prepareInternalErrorHandler(Environment environment,
0567: ProcessingException ex) throws ProcessingException {
0568: if (this .errorHandler != null) {
0569: try {
0570: this .errorPipeline = this .errorHandler
0571: .prepareErrorPipeline(ex);
0572: if (this .errorPipeline != null) {
0573: this .errorPipeline.prepareInternal(environment);
0574: return;
0575: }
0576: } catch (ProcessingException e) {
0577: // Log the original exception
0578: getLogger()
0579: .error(
0580: "Failed to process error handler for exception",
0581: ex);
0582: throw e;
0583: } catch (Exception e) {
0584: getLogger()
0585: .error(
0586: "Failed to process error handler for exception",
0587: ex);
0588: throw new ProcessingException(
0589: "Failed to handle exception <"
0590: + ex.getMessage() + ">", e);
0591: }
0592: } else {
0593: // propagate exception if we have no error handler
0594: throw ex;
0595: }
0596: }
0597:
0598: /**
0599: * @return true if error happened during internal pipeline prepare call.
0600: */
0601: protected boolean isInternalError() {
0602: return this .errorPipeline != null;
0603: }
0604:
0605: /**
0606: * Process the SAX event pipeline
0607: */
0608: protected boolean processXMLPipeline(Environment environment)
0609: throws ProcessingException {
0610:
0611: setMimeTypeForSerializer(environment);
0612: try {
0613: if (this .lastConsumer == null) {
0614: // internal processing
0615: this .generator.generate();
0616: } else {
0617: if (this .serializer.shouldSetContentLength()) {
0618: // set the output stream
0619: ByteArrayOutputStream os = new ByteArrayOutputStream();
0620: this .serializer.setOutputStream(os);
0621:
0622: // execute the pipeline:
0623: this .generator.generate();
0624: environment.setContentLength(os.size());
0625: os.writeTo(environment.getOutputStream(0));
0626: } else {
0627: // set the output stream
0628: this .serializer.setOutputStream(environment
0629: .getOutputStream(this .outputBufferSize));
0630: // execute the pipeline:
0631: this .generator.generate();
0632: }
0633: }
0634: } catch (Exception e) {
0635: handleException(e);
0636: }
0637:
0638: return true;
0639: }
0640:
0641: /**
0642: * Setup the reader
0643: */
0644: protected void setupReader(Environment environment)
0645: throws ProcessingException {
0646: try {
0647: this .reader.setup(environment,
0648: environment.getObjectModel(), readerSource,
0649: readerParam);
0650: // set the expires parameter on the pipeline if the reader is configured with one
0651: if (readerParam.isParameter("expires")) {
0652: // should this checking be done somewhere else??
0653: this .expires = readerParam
0654: .getParameterAsLong("expires");
0655: }
0656: } catch (Exception e) {
0657: handleException(e);
0658: }
0659: }
0660:
0661: /**
0662: * Set the mime-type for a reader
0663: * @param environment The current environment
0664: */
0665: protected void setMimeTypeForReader(Environment environment)
0666: throws ProcessingException {
0667: // Set the mime-type
0668: // the behaviour has changed from 2.1.6 to 2.1.7 according to bugs #10277 and #25121:
0669: // MIME type declared in the sitemap (instance or declaration, in this order)
0670: // Ask the Reader for a MIME type:
0671: // A *.doc reader could peek into the file
0672: // and return either text/plain or application/vnd.msword or
0673: // the reader can use MIME type declared in WEB-INF/web.xml or
0674: // by the server.
0675: if (this .readerMimeType != null) {
0676: // there was a mime-type defined on map:read in the sitemap
0677: environment.setContentType(this .readerMimeType);
0678: } else if (this .sitemapReaderMimeType != null) {
0679: // there was a mime-type defined on map:reader in the sitemap
0680: environment.setContentType(this .sitemapReaderMimeType);
0681: } else {
0682: // ask to the component itself
0683: final String mimeType = this .reader.getMimeType();
0684: if (mimeType != null) {
0685: environment.setContentType(mimeType);
0686: }
0687: // If no mimeType available, leave to to upstream proxy
0688: // or browser to deduce content-type from URL extension.
0689: }
0690: }
0691:
0692: /**
0693: * Set the mime-type for a serializer
0694: * @param environment The current environment
0695: */
0696: protected void setMimeTypeForSerializer(Environment environment)
0697: throws ProcessingException {
0698: if (this .lastConsumer == null) {
0699: // internal processing: text/xml
0700: environment.setContentType("text/xml");
0701: } else {
0702: // Set the mime-type
0703: // the behaviour has changed from 2.1.6 to 2.1.7 according to bugs #10277 and #25121:
0704: if (serializerMimeType != null) {
0705: // there was a mime-type defined on map:serialize in the sitemap
0706: environment.setContentType(serializerMimeType);
0707: } else if (sitemapSerializerMimeType != null) {
0708: // there was a mime-type defined on map:serializer in the sitemap
0709: environment.setContentType(sitemapSerializerMimeType);
0710: } else {
0711: // ask to the component itself
0712: String mimeType = this .serializer.getMimeType();
0713: if (mimeType != null) {
0714: environment.setContentType(mimeType);
0715: } else {
0716: // No mimeType available
0717: final String message = "Unable to determine MIME type for "
0718: + environment.getURIPrefix()
0719: + "/"
0720: + environment.getURI();
0721: throw new ProcessingException(message);
0722: }
0723: }
0724: }
0725: }
0726:
0727: protected boolean checkIfModified(Environment environment,
0728: long lastModified) throws ProcessingException {
0729: // has the read resource been modified?
0730: if (!environment.isResponseModified(lastModified)) {
0731: // environment supports this, so we are finished
0732: environment.setResponseIsNotModified();
0733: return true;
0734: }
0735: return false;
0736: }
0737:
0738: /**
0739: * Process the pipeline using a reader.
0740: * @throws ProcessingException if
0741: */
0742: protected boolean processReader(Environment environment)
0743: throws ProcessingException {
0744: try {
0745: this .setMimeTypeForReader(environment);
0746: if (this .reader.shouldSetContentLength()) {
0747: ByteArrayOutputStream os = new ByteArrayOutputStream();
0748: this .reader.setOutputStream(os);
0749: this .reader.generate();
0750: environment.setContentLength(os.size());
0751: os.writeTo(environment.getOutputStream(0));
0752: } else {
0753: this .reader.setOutputStream(environment
0754: .getOutputStream(this .outputBufferSize));
0755: this .reader.generate();
0756: }
0757: } catch (Exception e) {
0758: handleException(e);
0759: }
0760:
0761: return true;
0762: }
0763:
0764: public void recycle() {
0765: this .prepared = false;
0766:
0767: // Release reader.
0768: if (this .readerSelector != null) {
0769: this .readerSelector.release(this .reader);
0770: this .newManager.release(this .readerSelector);
0771: this .readerSelector = null;
0772: this .reader = null;
0773: this .readerParam = null;
0774: }
0775:
0776: // Release generator.
0777: if (this .generatorSelector != null) {
0778: this .generatorSelector.release(this .generator);
0779: this .newManager.release(this .generatorSelector);
0780: this .generatorSelector = null;
0781: this .generator = null;
0782: this .generatorParam = null;
0783: }
0784:
0785: // Release transformers
0786: int size = this .transformerSelectors.size();
0787: for (int i = 0; i < size; i++) {
0788: final ComponentSelector selector = (ComponentSelector) this .transformerSelectors
0789: .get(i);
0790: selector.release((Component) this .transformers.get(i));
0791: this .newManager.release(selector);
0792: }
0793: this .transformerSelectors.clear();
0794: this .transformers.clear();
0795: this .transformerParams.clear();
0796: this .transformerSources.clear();
0797:
0798: // Release serializer
0799: if (this .serializerSelector != null) {
0800: this .serializerSelector.release(this .serializer);
0801: this .newManager.release(this .serializerSelector);
0802: this .serializerSelector = null;
0803: this .serializerParam = null;
0804: }
0805: this .serializer = null;
0806: this .parameters = null;
0807: this .lastConsumer = null;
0808:
0809: // Release error handler
0810: this .errorHandler = null;
0811: if (this .errorPipeline != null) {
0812: this .errorPipeline.release();
0813: this .errorPipeline = null;
0814: }
0815: }
0816:
0817: /**
0818: * Process the given <code>Environment</code>, but do not use the
0819: * serializer. Instead all SAX events are streamed to the XMLConsumer.
0820: */
0821: public boolean process(Environment environment, XMLConsumer consumer)
0822: throws ProcessingException {
0823: if (this .reader != null) {
0824: throw new ProcessingException(
0825: "Streaming of an internal pipeline is not possible with a reader.");
0826: }
0827:
0828: // Exception happened during setup and was handled
0829: if (this .errorPipeline != null) {
0830: return this .errorPipeline.process(environment, consumer);
0831: }
0832:
0833: // Have to buffer events if error handler is specified.
0834: SaxBuffer buffer = null;
0835: this .lastConsumer = this .errorHandler == null ? consumer
0836: : (buffer = new SaxBuffer());
0837: try {
0838: connectPipeline(environment);
0839: return processXMLPipeline(environment);
0840: } catch (ProcessingException e) {
0841: buffer = null;
0842: return processErrorHandler(environment, e, consumer);
0843: } finally {
0844: if (buffer != null) {
0845: try {
0846: buffer.toSAX(consumer);
0847: } catch (SAXException e) {
0848: throw new ProcessingException(
0849: "Failed to execute pipeline.", e);
0850: }
0851: }
0852: }
0853: }
0854:
0855: protected boolean processErrorHandler(Environment environment,
0856: ProcessingException e, XMLConsumer consumer)
0857: throws ProcessingException {
0858: if (this .errorHandler != null) {
0859: try {
0860: this .errorPipeline = this .errorHandler
0861: .prepareErrorPipeline(e);
0862: if (this .errorPipeline != null) {
0863: this .errorPipeline.prepareInternal(environment);
0864: return this .errorPipeline.process(environment,
0865: consumer);
0866: }
0867: } catch (Exception ignored) {
0868: getLogger()
0869: .debug("Exception in error handler", ignored);
0870: }
0871: }
0872:
0873: throw e;
0874: }
0875:
0876: /**
0877: * Return valid validity objects for the event pipeline
0878: * If the "event pipeline" (= the complete pipeline without the
0879: * serializer) is cacheable and valid, return all validity objects.
0880: * Otherwise return <code>null</code>
0881: */
0882: public SourceValidity getValidityForEventPipeline() {
0883: return null;
0884: }
0885:
0886: /**
0887: * Return the key for the event pipeline
0888: * If the "event pipeline" (= the complete pipeline without the
0889: * serializer) is cacheable and valid, return a key.
0890: * Otherwise return <code>null</code>
0891: */
0892: public String getKeyForEventPipeline() {
0893: return null;
0894: }
0895:
0896: /**
0897: * Parse the expires parameter
0898: */
0899: private long parseExpires(String expire) {
0900: StringTokenizer tokens = new StringTokenizer(expire);
0901:
0902: // get <base>
0903: String current = tokens.nextToken();
0904: if (current.equals("modification")) {
0905: getLogger()
0906: .warn(
0907: "the \"modification\" keyword is not yet"
0908: + " implemented. Assuming \"now\" as the base attribute");
0909: current = "now";
0910: }
0911:
0912: if (!current.equals("now") && !current.equals("access")) {
0913: getLogger()
0914: .error(
0915: "bad <base> attribute, Expires header will not be set");
0916: return -1;
0917: }
0918:
0919: long number = 0;
0920: long modifier = 0;
0921: long expires = 0;
0922:
0923: while (tokens.hasMoreTokens()) {
0924: current = tokens.nextToken();
0925:
0926: // get rid of the optional <plus> keyword
0927: if (current.equals("plus")) {
0928: current = tokens.nextToken();
0929: }
0930:
0931: // We're expecting a sequence of <number> and <modification> here
0932: // get <number> first
0933: try {
0934: number = Long.parseLong(current);
0935: } catch (NumberFormatException nfe) {
0936: getLogger().error(
0937: "state violation: a number was expected here");
0938: return -1;
0939: }
0940:
0941: // now get <modifier>
0942: try {
0943: current = tokens.nextToken();
0944: } catch (NoSuchElementException nsee) {
0945: getLogger()
0946: .error(
0947: "State violation: expecting a modifier"
0948: + " but no one found: Expires header will not be set");
0949: }
0950: if (current.equals("years")) {
0951: modifier = 365L * 24L * 60L * 60L * 1000L;
0952: } else if (current.equals("months")) {
0953: modifier = 30L * 24L * 60L * 60L * 1000L;
0954: } else if (current.equals("weeks")) {
0955: modifier = 7L * 24L * 60L * 60L * 1000L;
0956: } else if (current.equals("days")) {
0957: modifier = 24L * 60L * 60L * 1000L;
0958: } else if (current.equals("hours")) {
0959: modifier = 60L * 60L * 1000L;
0960: } else if (current.equals("minutes")) {
0961: modifier = 60L * 1000L;
0962: } else if (current.equals("seconds")) {
0963: modifier = 1000L;
0964: } else {
0965: getLogger().error(
0966: "Bad modifier (" + current
0967: + "): ignoring expires configuration");
0968: return -1;
0969: }
0970: expires += number * modifier;
0971: }
0972:
0973: return expires;
0974: }
0975:
0976: protected Location getLocation(Parameters param) {
0977: Location location = null;
0978: if (param instanceof Locatable) {
0979: location = ((Locatable) param).getLocation();
0980: }
0981: if (location == null) {
0982: location = Location.UNKNOWN;
0983: }
0984: return location;
0985: }
0986:
0987: /**
0988: * Handles exception which can happen during pipeline processing.
0989: * If this not a connection reset, then all locations for pipeline components are
0990: * added to the exception.
0991: *
0992: * @throws ConnectionResetException if connection reset detected
0993: * @throws ProcessingException in all other cases
0994: */
0995: protected void handleException(Exception e)
0996: throws ProcessingException {
0997: // Check if the client aborted the connection
0998: if (e instanceof SocketException) {
0999: if (e.getMessage().indexOf("reset") > -1
1000: || e.getMessage().indexOf("aborted") > -1
1001: || e.getMessage().indexOf("Broken pipe") > -1
1002: || e.getMessage().indexOf("connection abort") > -1) {
1003: throw new ConnectionResetException(
1004: "Connection reset by peer", e);
1005: }
1006: } else if (e instanceof IOException) {
1007: // Tomcat5 wraps SocketException into ClientAbortException which extends IOException.
1008: if (e.getClass().getName().endsWith("ClientAbortException")) {
1009: throw new ConnectionResetException(
1010: "Connection reset by peer", e);
1011: }
1012: } else if (e instanceof ConnectionResetException) {
1013: // Exception comes up from a deeper pipeline
1014: throw (ConnectionResetException) e;
1015: }
1016:
1017: // Not a connection reset: add all location information
1018: if (this .reader == null) {
1019: // Add all locations in reverse order
1020: ArrayList locations = new ArrayList(this .transformers
1021: .size() + 2);
1022: locations.add(getLocation(this .serializerParam));
1023: for (int i = this .transformerParams.size() - 1; i >= 0; i--) {
1024: locations
1025: .add(getLocation((Parameters) this .transformerParams
1026: .get(i)));
1027: }
1028: locations.add(getLocation(this .generatorParam));
1029:
1030: throw ProcessingException.throwLocated(
1031: "Failed to process pipeline", e, locations);
1032:
1033: } else {
1034: // Add reader location
1035: throw ProcessingException.throwLocated(
1036: "Failed to process reader", e,
1037: getLocation(this.readerParam));
1038: }
1039: }
1040: }
|