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.transformation;
0018:
0019: import org.apache.avalon.framework.CascadingRuntimeException;
0020: import org.apache.avalon.framework.component.WrapperComponentManager;
0021: import org.apache.avalon.framework.configuration.Configurable;
0022: import org.apache.avalon.framework.configuration.Configuration;
0023: import org.apache.avalon.framework.configuration.ConfigurationException;
0024: import org.apache.avalon.framework.logger.AbstractLogEnabled;
0025: import org.apache.avalon.framework.logger.Logger;
0026: import org.apache.avalon.framework.parameters.Parameters;
0027: import org.apache.avalon.framework.service.ServiceException;
0028: import org.apache.avalon.framework.service.ServiceManager;
0029: import org.apache.avalon.framework.service.Serviceable;
0030:
0031: import org.apache.cocoon.ProcessingException;
0032: import org.apache.cocoon.Processor;
0033: import org.apache.cocoon.caching.CacheableProcessingComponent;
0034: import org.apache.cocoon.components.CocoonComponentManager;
0035: import org.apache.cocoon.components.source.SourceUtil;
0036: import org.apache.cocoon.components.source.impl.MultiSourceValidity;
0037: import org.apache.cocoon.components.thread.RunnableManager;
0038: import org.apache.cocoon.environment.Environment;
0039: import org.apache.cocoon.environment.SourceResolver;
0040: import org.apache.cocoon.transformation.helpers.NOPRecorder;
0041: import org.apache.cocoon.util.NetUtils;
0042: import org.apache.cocoon.xml.AbstractXMLPipe;
0043: import org.apache.cocoon.xml.IncludeXMLConsumer;
0044: import org.apache.cocoon.xml.NamespacesTable;
0045: import org.apache.cocoon.xml.SaxBuffer;
0046: import org.apache.cocoon.xml.XMLConsumer;
0047:
0048: import org.apache.excalibur.source.Source;
0049: import org.apache.excalibur.source.SourceValidity;
0050: import org.xml.sax.Attributes;
0051: import org.xml.sax.ContentHandler;
0052: import org.xml.sax.Locator;
0053: import org.xml.sax.SAXException;
0054: import org.xml.sax.ext.LexicalHandler;
0055:
0056: import java.io.IOException;
0057: import java.io.Serializable;
0058: import java.io.UnsupportedEncodingException;
0059: import java.util.HashMap;
0060: import java.util.Map;
0061: import java.util.Stack;
0062:
0063: /**
0064: * <p>A simple transformer including resolvable sources (accessed through
0065: * Cocoon's {@link SourceResolver}) from its input.</p>
0066: *
0067: * <p>Inclusion is triggered by the <code><include ... /></code> element
0068: * defined in the <code>http://apache.org/cocoon/include/1.0</code> namespace.</p>
0069: *
0070: * <p>Example:</p>
0071: * <pre>
0072: * <i:include xmlns:i="http://apache.org/cocoon/include/1.0"
0073: * src="cocoon://path/to/include"/>
0074: * </pre>
0075: *
0076: * <p>An interesting feature of this {@link Transformer} is that it implements the
0077: * {@link CacheableProcessingComponent} interface and provides full support for
0078: * caching. In other words, if the input given to this transformer has not changed,
0079: * and all of the included sources are (cacheable) and still valid, this transformer
0080: * will not force a pipeline re-generation like the {@link CIncludeTransformer}.</p>
0081: *
0082: *
0083: * <h3>Relative Source Resolution</h3>
0084: * <p>Include sources which are specified using relative URI will be resolved
0085: * relative to the source document location. This is consistent with
0086: * {@link XIncludeTransformer} behavior, but differs from {@link CIncludeTransformer}.
0087: *
0088: *
0089: * <h3>Parameters Passing</h3>
0090: * <p>Parameters to be passed to the included sources can be specified in two ways:
0091: * the first one is to encode them onto the source itelf, for example:</p>
0092: *
0093: * <pre>
0094: * <i:include xmlns:i="http://apache.org/cocoon/include/1.0"
0095: * src="cocoon://path/to/include?paramA=valueA&paramB=valueB"/>
0096: * </pre>
0097: *
0098: * <p>Another approach allows the encoding of parameters to be done automatically by
0099: * the transformer, so that one can easily pass parameter name or values containing
0100: * the <code>&</code> (amperstand) or <code>=</code> (equals) character, which are
0101: * reserved characters in URIs. An example:</p>
0102: *
0103: * <pre>
0104: * <i:include xmlns:i="http://apache.org/cocoon/include/1.0"
0105: * src="cocoon://path/to/include">
0106: * <i:parameter name="firstParameterName" value="firstParameterValue"/>
0107: * <i:parameter name="other&Para=Name" value="other=Para&Value"/>
0108: * </i:include>
0109: * </pre>
0110: *
0111: *
0112: * <h3>Fallback Element</h3>
0113: * <p>IncludeTransformer allows fallback element to be specified within
0114: * include element. XML content of the fallback element will be included instead
0115: * of source content if source inclusion caused an exception. Fallback element
0116: * can have nested include elements. An example:</p>
0117: *
0118: * <pre>
0119: * <i:include xmlns:i="http://apache.org/cocoon/include/1.0"
0120: * src="cocoon://path/to/include">
0121: * <i:fallback>
0122: * <strong>The data is temporarily unavailable.</strong>
0123: * We are sorry for the trouble; please try again later.
0124: * </i:fallback>
0125: * </i:include>
0126: * </pre>
0127: *
0128: *
0129: * <h3>Parallel Processing</h3>
0130: * <p>Another feature of this {@link Transformer} is that it allows parallel processing
0131: * of includes. By setting the optional parameter <code>parallel</code> to true,
0132: * the various included contents are processed (included) in parallel threads rather
0133: * than in series, in one thread. This parameter can be set in either the transformer
0134: * definition (to affect all IncludeTransformer instances):</p>
0135: * <pre>
0136: * <parallel>true</parallel>
0137: * </pre>
0138: *
0139: * <p>or in a pipeline itself (to only affect that instance of the IncludeTransformer):</p>
0140: * <pre>
0141: * <map:parameter name="parallel" value="true"/>
0142: * </pre>
0143: * <p>By default, parallel processing is turned off.</p>
0144: *
0145: *
0146: * <h3>Recursive Processing</h3>
0147: * <p>This {@link Transformer} allows recursive processing of includes.
0148: * By setting the optional parameter <code>recursive</code> to true,
0149: * the various included contents are scanned for include elements, and processed
0150: * in the same manner as incoming XML events. This parameter can be set in either
0151: * the transformer definition (to affect all IncludeTransformer instances):</p>
0152: * <pre>
0153: * <recursive>true</recursive>
0154: * </pre>
0155: *
0156: * <p>or in a pipeline itself (to only affect that instance of the IncludeTransformer):</p>
0157: * <pre>
0158: * <map:parameter name="recursive" value="true"/>
0159: * </pre>
0160: * <p>This feature is similar to the XInclude processing. By default,
0161: * recursive processing is turned off.</p>
0162: *
0163: *
0164: * @cocoon.sitemap.component.documentation
0165: * A simple transformer including resolvable sources (accessed through
0166: * Cocoon's SourceResolver) from its input.
0167: *
0168: * @cocoon.sitemap.component.name include
0169: * @cocoon.sitemap.component.logger sitemap.transformer.include
0170: * @cocoon.sitemap.component.pooling.max 16
0171: * @version $Id: IncludeTransformer.java 433543 2006-08-22 06:22:54Z crossley $
0172: */
0173: public class IncludeTransformer extends AbstractTransformer implements
0174: Serviceable, Configurable, CacheableProcessingComponent {
0175:
0176: /** <p>The namespace URI of the elements recognized by this transformer.</p> */
0177: private static final String NS_URI = "http://apache.org/cocoon/include/1.0";
0178:
0179: /** <p>The name of the element triggering inclusion of sources.</p> */
0180: private static final String INCLUDE_ELEMENT = "include";
0181:
0182: /** <p>The name of the element defining a fallback content.</p> */
0183: private static final String FALLBACK_ELEMENT = "fallback";
0184:
0185: /** <p>The name of the element defining an included subrequest parameter.</p> */
0186: private static final String PARAMETER_ELEMENT = "parameter";
0187:
0188: /** <p>The name of the attribute indicating the included source URI.</p> */
0189: private static final String SRC_ATTRIBUTE = "src";
0190:
0191: /** <p>The name of the mime type attribute containing the hint for the {@link org.apache.excalibur.xmlizer.XMLizer}.</p> */
0192: private static final String MIME_ATTRIBUTE = "mime-type";
0193:
0194: /** <p>The name of the parse attribute indicating type of included source processing: xml or text.</p> */
0195: private static final String PARSE_ATTRIBUTE = "parse";
0196:
0197: /** <p>The name of the attribute indicating the parameter name.</p> */
0198: private static final String NAME_ATTRIBUTE = "name";
0199:
0200: /** <p>The name of the attribute indicating the parameter name.</p> */
0201: private static final String VALUE_ATTRIBUTE = "value";
0202:
0203: /** <p>The encoding to use for parameter names and values.</p> */
0204: private static final String ENCODING = "US-ASCII";
0205:
0206: //
0207: // Global configuration
0208: //
0209:
0210: /** The {@link ServiceManager} instance associated with this instance. */
0211: protected ServiceManager manager;
0212:
0213: /** Configuration option controlling recursive includes processing */
0214: private boolean defaultRecursive;
0215:
0216: /** Configuration option controlling parallel (in multiple threads) includes processing */
0217: private boolean defaultParallel;
0218:
0219: /** Configuration option controlling parallel (in multiple threads) includes processing in the recursive includes */
0220: private boolean defaultRecursiveParallel;
0221:
0222: /** The name of the thread pool to use (for parallel processing). */
0223: protected String threadPool;
0224:
0225: /** The default value to be appended to the caching key. */
0226: private String defaultKey;
0227:
0228: //
0229: // Current configuration
0230: //
0231:
0232: /** The {@link SourceResolver} used to resolve included URIs. */
0233: protected SourceResolver resolver;
0234:
0235: /** The {@link Environment} used within parallel threads */
0236: protected Environment environment;
0237:
0238: /** The {@link Processor} used within parallel threads */
0239: private Processor processor;
0240:
0241: /** The value to be appended to the caching key. */
0242: private String key;
0243:
0244: //
0245: // Current state
0246: //
0247:
0248: /** The {@link SourceValidity} instance associated with this request. */
0249: protected MultiSourceValidity validity;
0250:
0251: /** A {@link NamespacesTable} used to filter namespace declarations. */
0252: private NamespacesTable namespaces;
0253:
0254: /** The {@link IncludeXMLPipe} which is doing all the work */
0255: private final IncludeXMLPipe pipe;
0256:
0257: /**
0258: * <p>Create a new {@link IncludeTransformer} instance.</p>
0259: */
0260: public IncludeTransformer() {
0261: pipe = new IncludeXMLPipe();
0262: }
0263:
0264: /**
0265: * <p>Initialize own and {@link #pipe} loggers</p>
0266: */
0267: public void enableLogging(Logger logger) {
0268: super .enableLogging(logger);
0269: pipe.enableLogging(logger);
0270: }
0271:
0272: /**
0273: * <p>Setup the {@link ServiceManager} available for this instance.</p>
0274: *
0275: * @see Serviceable#service(ServiceManager)
0276: */
0277: public void service(ServiceManager manager) throws ServiceException {
0278: this .manager = manager;
0279: }
0280:
0281: /* (non-Javadoc)
0282: * @see Configurable#configure(Configuration)
0283: */
0284: public void configure(Configuration configuration)
0285: throws ConfigurationException {
0286: /* Read configuration nodes for recursive, parallel, recursive-parallel */
0287: this .defaultRecursive = configuration.getChild("recursive")
0288: .getValueAsBoolean(false);
0289: this .defaultParallel = configuration.getChild("parallel")
0290: .getValueAsBoolean(false);
0291: this .defaultRecursiveParallel = configuration.getChild(
0292: "recursive-parallel").getValueAsBoolean(false);
0293: /* Read configuration node for thread pool name */
0294: this .threadPool = configuration.getChild("thread-pool")
0295: .getValue("default");
0296: this .defaultKey = configuration.getChild("key").getValue(null);
0297: }
0298:
0299: /**
0300: * <p>Setup this component instance in the context of its pipeline and
0301: * current request.</p>
0302: *
0303: * @see Serviceable#service(ServiceManager)
0304: */
0305: public void setup(SourceResolver resolver, Map om, String src,
0306: Parameters parameters) throws ProcessingException,
0307: SAXException, IOException {
0308: /* Read sitemap parameters */
0309: this .pipe.recursive = parameters.getParameterAsBoolean(
0310: "recursive", this .defaultRecursive);
0311: this .pipe.parallel = parameters.getParameterAsBoolean(
0312: "parallel", this .defaultParallel);
0313: this .pipe.recursiveParallel = parameters.getParameterAsBoolean(
0314: "recursive-parallel", this .defaultRecursiveParallel);
0315: this .key = parameters.getParameter("key", this .defaultKey);
0316:
0317: /* Init transformer state */
0318: if (this .pipe.parallel) {
0319: this .environment = CocoonComponentManager
0320: .getCurrentEnvironment();
0321: this .processor = CocoonComponentManager
0322: .getCurrentProcessor();
0323: }
0324: this .namespaces = new NamespacesTable();
0325: this .resolver = resolver;
0326: this .validity = null;
0327:
0328: // Set root include pipe as consumer.
0329: // Won't use setter methods here - they are overridden
0330: super .xmlConsumer = pipe;
0331: super .contentHandler = pipe;
0332: super .lexicalHandler = pipe;
0333: }
0334:
0335: public void setConsumer(XMLConsumer consumer) {
0336: pipe.setConsumer(consumer);
0337: }
0338:
0339: public void setContentHandler(ContentHandler handler) {
0340: pipe.setContentHandler(handler);
0341: }
0342:
0343: public void setLexicalHandler(LexicalHandler handler) {
0344: pipe.setLexicalHandler(handler);
0345: }
0346:
0347: /**
0348: * <p>Recycle this component instance.</p>
0349: *
0350: * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
0351: */
0352: public void recycle() {
0353: this .namespaces = null;
0354: this .validity = null;
0355:
0356: /* Make sure all threads completed their work */
0357: this .pipe.recycle();
0358:
0359: // Resolver can be nulled out when all threads completed processing
0360: // and released their Sources.
0361: this .resolver = null;
0362:
0363: super .recycle();
0364: }
0365:
0366: /**
0367: * <p>Receive notification of the beginning of an XML document.</p>
0368: *
0369: * @see ContentHandler#startDocument
0370: */
0371: public void startDocument() throws SAXException {
0372: /* Make sure that we have a validity while processing */
0373: getValidity();
0374:
0375: super .startDocument();
0376: }
0377:
0378: /**
0379: * <p>Receive notification of the end of an XML document.</p>
0380: *
0381: * @see ContentHandler#startDocument()
0382: */
0383: public void endDocument() throws SAXException {
0384: /* Make sure that the validity is "closed" at the end */
0385: this .validity.close();
0386:
0387: super .endDocument();
0388: }
0389:
0390: /**
0391: * <p>Receive notification of the start of a prefix mapping.</p>
0392: *
0393: * <p>This transformer will remove all prefix mapping declarations for those
0394: * prefixes associated with the <code>http://apache.org/cocoon/include/1.0</code>
0395: * namespace.</p>
0396: *
0397: * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
0398: */
0399: public void startPrefixMapping(String prefix, String nsuri)
0400: throws SAXException {
0401: if (NS_URI.equals(nsuri)) {
0402: /* Skipping mapping for the current prefix as it's ours */
0403: this .namespaces.addDeclaration(prefix, nsuri);
0404: } else {
0405: /* Map the current prefix, as we don't know it */
0406: super .startPrefixMapping(prefix, nsuri);
0407: }
0408: }
0409:
0410: /**
0411: * <p>Receive notification of the end of a prefix mapping.</p>
0412: *
0413: * <p>This transformer will remove all prefix mapping declarations for those
0414: * prefixes associated with the <code>http://apache.org/cocoon/include/1.0</code>
0415: * namespace.</p>
0416: *
0417: * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
0418: */
0419: public void endPrefixMapping(String prefix) throws SAXException {
0420: if (NS_URI.equals(this .namespaces.getUri(prefix))) {
0421: /* Skipping unmapping for the current prefix as it's ours */
0422: this .namespaces.removeDeclaration(prefix);
0423: } else {
0424: /* Unmap the current prefix, as we don't know it */
0425: super .endPrefixMapping(prefix);
0426: }
0427: }
0428:
0429: /**
0430: * <p>Return the caching key associated with this transformation.</p>
0431: *
0432: * <p>When including <code>cocoon://</code> sources with dynamic
0433: * content depending on environment (request parameters, session attributes,
0434: * etc), it makes sense to provide such environment values to the transformer
0435: * to be included into the key using <code>key</code> sitemap parameter.</p>
0436: *
0437: * @see CacheableProcessingComponent#getKey()
0438: */
0439: public Serializable getKey() {
0440: /*
0441: * In case of including "cocoon://" or other dynamic sources key
0442: * ideally has to include ProcessingPipelineKey of the included
0443: * "cocoon://" sources, but it's not possible as at this time
0444: * we don't know yet which sources will get included into the
0445: * response.
0446: *
0447: * Hence, javadoc recommends providing key using sitemap parameter.
0448: */
0449: return key == null ? "I" : "I" + key;
0450: }
0451:
0452: /**
0453: * <p>Generate (or return) the {@link SourceValidity} instance used to
0454: * possibly validate cached generations.</p>
0455: *
0456: * @return a <b>non null</b> {@link SourceValidity}.
0457: * @see org.apache.cocoon.caching.CacheableProcessingComponent#getValidity()
0458: */
0459: public SourceValidity getValidity() {
0460: if (validity == null) {
0461: validity = new MultiSourceValidity(resolver, -1);
0462: }
0463: return validity;
0464: }
0465:
0466: /**
0467: * Description of the include element
0468: */
0469: private class IncludeElement extends AbstractLogEnabled {
0470: /** Parameter controlling recursive includes processing */
0471: private boolean recursive;
0472:
0473: /** Parameter controlling parallel (in multiple threads) includes processing */
0474: private boolean parallel;
0475:
0476: /** Parameter controlling parallel (in multiple threads) includes processing in recursive includes */
0477: private boolean recursiveParallel;
0478:
0479: /** The source base URI. */
0480: private String base;
0481:
0482: /** The source URI to be included declared in an src attribute of the include element. */
0483: protected String source;
0484:
0485: /** The flag indicating whether source content has to be parsed into XML or included as text. */
0486: protected boolean parse;
0487:
0488: /** The mime type hint to the {@link org.apache.excalibur.xmlizer.XMLizer} when parsing the source content. */
0489: protected String mimeType;
0490:
0491: /** The buffer collecting fallback content. */
0492: protected SaxBuffer fallback;
0493:
0494: /** A {@link Map} of the parameters to supply to the included source. */
0495: protected Map parameters;
0496:
0497: /** The current parameter name captured. */
0498: protected String parameter;
0499:
0500: /** The current parameter value (as a {@link StringBuffer}). */
0501: protected StringBuffer value;
0502:
0503: /** Create include element */
0504: private IncludeElement(String base, boolean parallel,
0505: boolean recursive, boolean recursiveParallel,
0506: Logger logger) {
0507: this .base = base;
0508: this .parallel = parallel;
0509: this .recursive = recursive;
0510: this .recursiveParallel = recursiveParallel;
0511: this .enableLogging(logger);
0512: }
0513:
0514: /**
0515: * Process element into the buffer.
0516: * This can not be shared buffer, as it must be cleaned if fallback is invoked.
0517: */
0518: public void process(SaxBuffer buffer) throws SAXException {
0519: try {
0520: process0(buffer, buffer);
0521: } catch (SAXException e) {
0522: buffer.recycle();
0523: if (this .fallback == null) {
0524: throw e;
0525: }
0526:
0527: if (getLogger().isInfoEnabled()) {
0528: getLogger().info(
0529: "Failed to load <" + this .source
0530: + ">, using fallback.", e);
0531: }
0532: // Stream fallback through IncludeXMLPipe
0533: this .fallback.toSAX(new IncludeXMLPipe(getLogger(),
0534: buffer, buffer, recursive,
0535: recursiveParallel ? parallel : false,
0536: recursiveParallel));
0537: }
0538: }
0539:
0540: /** Load URI into the provided handlers, process fallback */
0541: public void process(ContentHandler contentHandler,
0542: LexicalHandler lexicalHandler) throws SAXException {
0543: try {
0544: if (this .fallback != null) {
0545: SaxBuffer buffer = new SaxBuffer();
0546: process(buffer);
0547: buffer.toSAX(contentHandler);
0548: } else {
0549: process0(contentHandler, lexicalHandler);
0550: }
0551: } catch (SAXException e) {
0552: // source must not be cached if an error occurs
0553: validity = null;
0554: throw e;
0555: }
0556: }
0557:
0558: /** Load URI into the provided handlers. */
0559: private void process0(ContentHandler contentHandler,
0560: LexicalHandler lexicalHandler) throws SAXException {
0561: Source source = null;
0562: if (getLogger().isDebugEnabled()) {
0563: getLogger().debug("Loading <" + this .source + ">");
0564: }
0565:
0566: // Setup this thread's environment
0567: try {
0568: if (base != null) {
0569: source = resolver.resolveURI(this .source, base,
0570: null);
0571: } else {
0572: source = resolver.resolveURI(this .source);
0573: }
0574: if (validity != null) {
0575: synchronized (validity) {
0576: validity.addSource(source);
0577: }
0578: }
0579:
0580: // Include source
0581: if (this .parse && recursive) {
0582: SourceUtil
0583: .toSAX(
0584: manager,
0585: source,
0586: this .mimeType,
0587: new IncludeXMLPipe(
0588: getLogger(),
0589: contentHandler,
0590: lexicalHandler,
0591: recursive,
0592: recursiveParallel ? parallel
0593: : false,
0594: recursiveParallel));
0595: } else if (this .parse) {
0596: SourceUtil.toSAX(manager, source, this .mimeType,
0597: new IncludeXMLConsumer(contentHandler,
0598: lexicalHandler));
0599: } else {
0600: SourceUtil.toCharacters(source, "utf-8",
0601: contentHandler);
0602: }
0603:
0604: if (getLogger().isDebugEnabled()) {
0605: getLogger().debug("Loaded <" + this .source + ">");
0606: }
0607: } catch (SAXException e) {
0608: if (getLogger().isDebugEnabled()) {
0609: getLogger().debug(
0610: "Failed to load <" + this .source + ">", e);
0611: }
0612:
0613: throw e;
0614:
0615: } catch (ProcessingException e) {
0616: if (getLogger().isDebugEnabled()) {
0617: getLogger().debug(
0618: "Failed to load <" + this .source + ">", e);
0619: }
0620:
0621: throw new SAXException(e);
0622:
0623: } catch (IOException e) {
0624: if (getLogger().isDebugEnabled()) {
0625: getLogger().debug(
0626: "Failed to load <" + this .source + ">", e);
0627: }
0628:
0629: throw new SAXException(e);
0630:
0631: } finally {
0632: if (source != null) {
0633: resolver.release(source);
0634: }
0635: }
0636: }
0637: }
0638:
0639: /**
0640: * XML pipe reacting on the elements in the include namespace.
0641: */
0642: private class IncludeXMLPipe extends AbstractXMLPipe {
0643:
0644: //
0645: // Configuration
0646: //
0647:
0648: /** Indicates whether this is root include pipe (owned by transformer) or a nested one */
0649: private final boolean root;
0650:
0651: /** Parameter controlling recursive includes processing */
0652: private boolean recursive;
0653:
0654: /** Parameter controlling parallel (in multiple threads) includes processing */
0655: private boolean parallel;
0656:
0657: /** Parameter controlling parallel (in multiple threads) includes processing in recursive includes */
0658: private boolean recursiveParallel;
0659:
0660: //
0661: // Current state
0662: //
0663:
0664: /** Stack of {@link XMLConsumer}s */
0665: private final Stack consumers = new Stack();
0666:
0667: /** Current depth of nested elements in the include namespace */
0668: private int depth;
0669:
0670: /** Base URI used for the resolving included sources */
0671: private String base;
0672:
0673: /** The source to be included declared in an include element. */
0674: private IncludeElement element;
0675:
0676: /** If parallel processing is enabled, then this boolean tells us whether buffering has started yet. */
0677: private boolean buffering;
0678:
0679: /**
0680: * <p>The IncludeBuffer that is used to buffering events if parallel
0681: * processing is turned on.</p>
0682: * <p>This object is also used as a lock for the thread counter <code>threads</code>.</p>
0683: */
0684: private SaxBuffer buffer;
0685:
0686: /** Inclusion threads/tasks counter (if executing in parallel) */
0687: private int threads;
0688:
0689: /**
0690: * <p>Create a new {@link IncludeXMLPipe} instance.</p>
0691: */
0692: public IncludeXMLPipe() {
0693: root = true;
0694: }
0695:
0696: /**
0697: * <p>Create a new {@link IncludeXMLPipe} instance.</p>
0698: */
0699: public IncludeXMLPipe(Logger logger,
0700: ContentHandler contentHandler,
0701: LexicalHandler lexicalHandler, boolean recursive,
0702: boolean parallel, boolean recursiveParallel) {
0703: root = false;
0704: this .enableLogging(logger);
0705: this .setContentHandler(contentHandler);
0706: this .setLexicalHandler(lexicalHandler);
0707: this .recursive = recursive;
0708: this .parallel = parallel;
0709: this .recursiveParallel = recursiveParallel;
0710: }
0711:
0712: /**
0713: * Finish processing.
0714: */
0715: public void recycle() {
0716: if (this .buffering) {
0717: // Wait for threads to complete and release Sources
0718: waitForThreads();
0719: this .buffering = false;
0720: this .buffer = null;
0721: }
0722: this .threads = 0;
0723:
0724: this .consumers.clear();
0725: this .base = null;
0726: this .element = null;
0727:
0728: super .recycle();
0729: }
0730:
0731: /** Push current consumer into the stack, replace with new one */
0732: private void push(XMLConsumer consumer) {
0733: this .consumers.push(new Object[] { super .xmlConsumer,
0734: super .contentHandler, super .lexicalHandler });
0735: this .setConsumer(consumer);
0736: }
0737:
0738: /** Pop consumer from the stack, replace current one */
0739: private void pop() {
0740: Object[] consumer = (Object[]) this .consumers.pop();
0741: if (consumer[0] != null) {
0742: this .setConsumer((XMLConsumer) consumer[0]);
0743: } else {
0744: this .setContentHandler((ContentHandler) consumer[1]);
0745: this .setLexicalHandler((LexicalHandler) consumer[2]);
0746: }
0747: }
0748:
0749: //
0750: // ContentHandler interface
0751: //
0752:
0753: public void setDocumentLocator(Locator locator) {
0754: try {
0755: if (locator != null && locator.getSystemId() != null) {
0756: Source source = resolver.resolveURI(locator
0757: .getSystemId());
0758: try {
0759: base = source.getURI();
0760: } finally {
0761: resolver.release(source);
0762: }
0763: }
0764: } catch (IOException e) {
0765: getLogger().warn(
0766: "Unable to resolve document base URI: <"
0767: + locator.getSystemId() + ">");
0768: }
0769:
0770: super .setDocumentLocator(locator);
0771: }
0772:
0773: /**
0774: * <p>Receive notification of the beginning of an XML document.</p>
0775: * @see ContentHandler#startDocument
0776: */
0777: public void startDocument() throws SAXException {
0778: if (root) {
0779: super .startDocument();
0780: }
0781: }
0782:
0783: /**
0784: * <p>Receive notification of the end of an XML document.</p>
0785: * @see ContentHandler#startDocument
0786: */
0787: public void endDocument() throws SAXException {
0788: /* This is the end of the line - process the buffered events */
0789: if (this .buffering) {
0790: pop();
0791: this .buffer.toSAX(super .contentHandler);
0792: }
0793:
0794: if (root) {
0795: super .endDocument();
0796: }
0797: }
0798:
0799: /**
0800: * <p>Receive notification of the start of an element.</p>
0801: * @see ContentHandler#startElement
0802: */
0803: public void startElement(String uri, String localName,
0804: String qName, Attributes atts) throws SAXException {
0805:
0806: /* Check the namespace declaration */
0807: if (NS_URI.equals(uri)) {
0808:
0809: /*
0810: * Depth 0: Outside of any include tag
0811: * Depth 1: Must be Inside <include> tag
0812: * Depth 2: Inside <fallback> tag
0813: */
0814: depth++;
0815:
0816: /* Inclusion will not happen here but when we close this tag */
0817: if (INCLUDE_ELEMENT.equals(localName) && depth == 1) {
0818: /* Check before we include (we don't want nested stuff) */
0819: if (element != null) {
0820: throw new SAXException("Element "
0821: + INCLUDE_ELEMENT
0822: + " nested in another one.");
0823: }
0824: element = new IncludeElement(this .base,
0825: this .parallel, this .recursive,
0826: this .recursiveParallel, getLogger());
0827:
0828: /* Remember the source we are trying to include */
0829: element.source = atts.getValue(SRC_ATTRIBUTE);
0830: if (element.source == null
0831: || element.source.length() == 0) {
0832: throw new SAXException("Attribute '"
0833: + SRC_ATTRIBUTE + "' empty or missing.");
0834: }
0835:
0836: /* Defaults to 'xml' */
0837: String value = atts.getValue(PARSE_ATTRIBUTE);
0838: if (value == null || value.equals("xml")) {
0839: element.parse = true;
0840: } else if (value.equals("text")) {
0841: element.parse = false;
0842: } else {
0843: throw new SAXException("Attribute '"
0844: + PARSE_ATTRIBUTE
0845: + "' has invalid value.");
0846: }
0847:
0848: /* Defaults to 'text/xml' */
0849: element.mimeType = atts.getValue(MIME_ATTRIBUTE);
0850: if (!element.parse && element.mimeType != null) {
0851: throw new SAXException(
0852: "Attribute '"
0853: + MIME_ATTRIBUTE
0854: + "' can't be specified for text inclusions.");
0855: } else if (element.mimeType == null) {
0856: element.mimeType = "text/xml";
0857: }
0858:
0859: /* Ignore nested content */
0860: push(new NOPRecorder() {
0861: });
0862:
0863: /* Done with this element */
0864: return;
0865: }
0866:
0867: /* If this is a fallback parameter, capture its content. */
0868: if (FALLBACK_ELEMENT.equals(localName) && depth == 2) {
0869: /* Check if we are in the right context */
0870: if (element == null) {
0871: throw new SAXException("Element "
0872: + FALLBACK_ELEMENT
0873: + " specified outside of "
0874: + INCLUDE_ELEMENT + ".");
0875: }
0876: if (element.fallback != null) {
0877: throw new SAXException("Duplicate element "
0878: + FALLBACK_ELEMENT + ".");
0879: }
0880:
0881: /* Buffer fallback content */
0882: push(element.fallback = new SaxBuffer());
0883:
0884: /* Done with this element */
0885: return;
0886: }
0887:
0888: /* If this is a parameter, then make sure we prepare. */
0889: if (PARAMETER_ELEMENT.equals(localName) && depth == 2) {
0890: /* Check if we are in the right context */
0891: if (element == null) {
0892: throw new SAXException("Element "
0893: + PARAMETER_ELEMENT
0894: + " specified outside of "
0895: + INCLUDE_ELEMENT + ".");
0896: }
0897: if (element.parameter != null) {
0898: throw new SAXException("Element "
0899: + PARAMETER_ELEMENT
0900: + " nested in another one.");
0901: }
0902:
0903: /* Get and process the parameter name */
0904: element.parameter = atts.getValue(NAME_ATTRIBUTE);
0905: if (element.parameter == null
0906: || element.parameter.length() == 0) {
0907: throw new SAXException("Attribute '"
0908: + NAME_ATTRIBUTE
0909: + "' empty or missing.");
0910: }
0911:
0912: /* Make some room for the parameter value */
0913: String value = atts.getValue(VALUE_ATTRIBUTE);
0914: if (value != null) {
0915: element.value = new StringBuffer(value);
0916: }
0917:
0918: /* Done with this element */
0919: return;
0920: }
0921:
0922: /* We don't have a clue of why we got here (wrong element?) */
0923: if (depth < 2) {
0924: throw new SAXException("Element '" + localName
0925: + "' was not expected here.");
0926: }
0927: }
0928:
0929: super .startElement(uri, localName, qName, atts);
0930: }
0931:
0932: /**
0933: * <p>Receive notification of the end of an element.</p>
0934: * @see ContentHandler#endElement
0935: */
0936: public void endElement(String uri, String localName,
0937: String qName) throws SAXException {
0938: /* Check the namespace declaration */
0939: if (NS_URI.equals(uri)) {
0940:
0941: /*
0942: * Depth 0: Outside of any include tag
0943: * Depth 1: Inside <include> tag
0944: * Depth 2: Inside <fallback> tag
0945: */
0946: depth--;
0947:
0948: /* Inclusion will happen here, when we close the include element */
0949: if (INCLUDE_ELEMENT.equals(localName) && depth == 0) {
0950: /* End ignoring nested content */
0951: pop();
0952:
0953: /* Get the source discovered opening the element and include */
0954: if (element.parameters != null) {
0955: element.source = NetUtils.parameterize(
0956: element.source, element.parameters);
0957: element.parameters = null;
0958: }
0959:
0960: /* Check for parallel processing */
0961: if (this .parallel) {
0962: if (!this .buffering) {
0963: this .buffering = true;
0964: buffer = new SaxBuffer();
0965: push(buffer);
0966: }
0967:
0968: /* Process include element in separate thread */
0969: buffer.xmlizable(new IncludeBuffer(element));
0970:
0971: } else {
0972: /* Process include element inline */
0973: element.process(super .contentHandler,
0974: super .lexicalHandler);
0975: }
0976:
0977: /* We are done with this include element */
0978: this .element = null;
0979: return;
0980: }
0981:
0982: if (FALLBACK_ELEMENT.equals(localName) && depth == 1) {
0983: /* End buffering fallback content */
0984: pop();
0985:
0986: /* Done with this element */
0987: return;
0988: }
0989:
0990: /* Addition of parameters happens here (so that we can capture chars) */
0991: if (PARAMETER_ELEMENT.equals(localName) && depth == 1) {
0992: String value = (element.value != null ? element.value
0993: .toString()
0994: : "");
0995:
0996: /* Store the parameter name and value */
0997: try {
0998: /*
0999: * Note: the parameter name and value are URL encoded, so that
1000: * weird characters such as "&" or "=" (have special meaning)
1001: * are passed through flawlessly.
1002: */
1003: if (element.parameters == null) {
1004: element.parameters = new HashMap(5);
1005: }
1006: element.parameters.put(NetUtils.encode(
1007: element.parameter, ENCODING), NetUtils
1008: .encode(value, ENCODING));
1009: } catch (UnsupportedEncodingException e) {
1010: throw new SAXException(
1011: "Your platform does not support the "
1012: + ENCODING + " encoding", e);
1013: }
1014:
1015: /* We are done with this parameter element */
1016: element.value = null;
1017: element.parameter = null;
1018: return;
1019: }
1020: }
1021:
1022: /* This is not our namespace, pass the event on! */
1023: super .endElement(uri, localName, qName);
1024: }
1025:
1026: /**
1027: * <p>Receive notification of characters.</p>
1028: * @see ContentHandler#characters
1029: */
1030: public void characters(char[] data, int offset, int length)
1031: throws SAXException {
1032: if (element != null && element.parameter != null) {
1033: /* If we have a parameter value to add to, let's add this chunk */
1034: if (element.value == null) {
1035: element.value = new StringBuffer();
1036: }
1037: element.value.append(data, offset, length);
1038: return;
1039: }
1040:
1041: /* Forward */
1042: super .characters(data, offset, length);
1043: }
1044:
1045: //
1046: // Thread management
1047: //
1048:
1049: /**
1050: * Increment active threads counter
1051: */
1052: int incrementThreads() {
1053: synchronized (buffer) {
1054: return ++threads;
1055: }
1056: }
1057:
1058: /**
1059: * Decrement active threads counter
1060: */
1061: void decrementThreads() {
1062: synchronized (buffer) {
1063: if (--threads <= 0) {
1064: buffer.notify();
1065: }
1066: }
1067: }
1068:
1069: /**
1070: * Wait till there is no active threads
1071: */
1072: private void waitForThreads() {
1073: synchronized (buffer) {
1074: if (threads > 0) {
1075: if (getLogger().isDebugEnabled()) {
1076: getLogger()
1077: .debug(
1078: threads
1079: + " threads in progress, waiting");
1080: }
1081:
1082: try {
1083: buffer.wait();
1084: } catch (InterruptedException e) { /* ignored */
1085: }
1086: // Don't continue waiting if interrupted.
1087: }
1088: }
1089: }
1090:
1091: /**
1092: * Buffer for loading included source in separate thread.
1093: * Streaming of the loaded buffer possible only when source is
1094: * loaded completely. If loading is not complete, toSAX method
1095: * will block.
1096: */
1097: private class IncludeBuffer extends SaxBuffer implements
1098: Runnable {
1099:
1100: private IncludeElement element;
1101: private int thread;
1102: private boolean finished;
1103: private SAXException e;
1104:
1105: public IncludeBuffer(IncludeElement element) {
1106: this .element = element;
1107:
1108: RunnableManager runnable = null;
1109: try {
1110: runnable = (RunnableManager) IncludeTransformer.this .manager
1111: .lookup(RunnableManager.ROLE);
1112: runnable.execute(
1113: IncludeTransformer.this .threadPool, this );
1114: } catch (final ServiceException e) {
1115: // In case we failed to spawn a thread
1116: throw new CascadingRuntimeException(e.getMessage(),
1117: e);
1118: } finally {
1119: IncludeTransformer.this .manager.release(runnable);
1120: }
1121:
1122: // Increment active threads counter
1123: this .thread = incrementThreads();
1124: }
1125:
1126: /**
1127: * Load content of the source into this buffer.
1128: */
1129: public void run() {
1130: try {
1131: if (getLogger().isDebugEnabled()) {
1132: getLogger().debug(
1133: "Thread #" + thread + " loading <"
1134: + element.source + ">");
1135: }
1136:
1137: // Setup this thread's environment
1138: CocoonComponentManager.enterEnvironment(
1139: environment, new WrapperComponentManager(
1140: manager), processor);
1141: try {
1142: element.process(this );
1143:
1144: } catch (SAXException e) {
1145: this .e = e;
1146:
1147: } finally {
1148: CocoonComponentManager.leaveEnvironment();
1149: }
1150: } catch (RuntimeException e) {
1151: /* Unable to set thread's environment */
1152: this .e = new SAXException(e);
1153:
1154: } finally {
1155: synchronized (this ) {
1156: this .finished = true;
1157: notify();
1158: }
1159:
1160: // Make sure that active threads counter is decremented
1161: decrementThreads();
1162: }
1163:
1164: if (getLogger().isDebugEnabled()) {
1165: if (this .e == null) {
1166: getLogger().debug(
1167: "Thread #" + thread + " loaded <"
1168: + element.source + ">");
1169: } else {
1170: getLogger().debug(
1171: "Thread #" + thread
1172: + " failed to load <"
1173: + element.source + ">", this .e);
1174: }
1175: }
1176: }
1177:
1178: /**
1179: * Stream content of this buffer when it is loaded completely.
1180: * This method blocks if loading is not complete.
1181: */
1182: public void toSAX(ContentHandler contentHandler)
1183: throws SAXException {
1184: synchronized (this ) {
1185: if (!this .finished) {
1186: try {
1187: wait();
1188: } catch (InterruptedException e) { /* ignored */
1189: }
1190: // Don't continue waiting if interrupted.
1191: }
1192: }
1193:
1194: if (this.e != null) {
1195: throw this.e;
1196: }
1197:
1198: super.toSAX(contentHandler);
1199: }
1200: }
1201: }
1202: }
|