KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > transformation > IncludeTransformer


1 /*
2  * Copyright 1999-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.transformation;
17
18 import org.apache.avalon.framework.CascadingRuntimeException;
19 import org.apache.avalon.framework.component.WrapperComponentManager;
20 import org.apache.avalon.framework.configuration.Configurable;
21 import org.apache.avalon.framework.configuration.Configuration;
22 import org.apache.avalon.framework.configuration.ConfigurationException;
23 import org.apache.avalon.framework.logger.AbstractLogEnabled;
24 import org.apache.avalon.framework.logger.Logger;
25 import org.apache.avalon.framework.parameters.Parameters;
26 import org.apache.avalon.framework.service.ServiceException;
27 import org.apache.avalon.framework.service.ServiceManager;
28 import org.apache.avalon.framework.service.Serviceable;
29
30 import org.apache.cocoon.ProcessingException;
31 import org.apache.cocoon.Processor;
32 import org.apache.cocoon.caching.CacheableProcessingComponent;
33 import org.apache.cocoon.components.CocoonComponentManager;
34 import org.apache.cocoon.components.source.SourceUtil;
35 import org.apache.cocoon.components.source.impl.MultiSourceValidity;
36 import org.apache.cocoon.components.thread.RunnableManager;
37 import org.apache.cocoon.environment.Environment;
38 import org.apache.cocoon.environment.SourceResolver;
39 import org.apache.cocoon.transformation.helpers.NOPRecorder;
40 import org.apache.cocoon.util.NetUtils;
41 import org.apache.cocoon.xml.AbstractXMLPipe;
42 import org.apache.cocoon.xml.IncludeXMLConsumer;
43 import org.apache.cocoon.xml.NamespacesTable;
44 import org.apache.cocoon.xml.SaxBuffer;
45 import org.apache.cocoon.xml.XMLConsumer;
46
47 import org.apache.excalibur.source.Source;
48 import org.apache.excalibur.source.SourceValidity;
49 import org.xml.sax.Attributes JavaDoc;
50 import org.xml.sax.ContentHandler JavaDoc;
51 import org.xml.sax.Locator JavaDoc;
52 import org.xml.sax.SAXException JavaDoc;
53 import org.xml.sax.ext.LexicalHandler JavaDoc;
54
55 import java.io.IOException JavaDoc;
56 import java.io.Serializable JavaDoc;
57 import java.io.UnsupportedEncodingException JavaDoc;
58 import java.util.HashMap JavaDoc;
59 import java.util.Map JavaDoc;
60 import java.util.Stack JavaDoc;
61
62 /**
63  * <p>A simple transformer including resolvable sources (accessed through
64  * Cocoon's {@link SourceResolver}) from its input.</p>
65  *
66  * <p>Inclusion is triggered by the <code>&lt;include ... /&gt;</code> element
67  * defined in the <code>http://apache.org/cocoon/include/1.0</code> namespace.</p>
68  *
69  * <p>Example:</p>
70  * <pre>
71  * &lt;i:include xmlns:i="http://apache.org/cocoon/include/1.0"
72  * SRC="cocoon://path/to/include"/&gt;
73  * </pre>
74  *
75  * <p>An interesting feature of this {@link Transformer} is that it implements the
76  * {@link CacheableProcessingComponent} interface and provides full support for
77  * caching. In other words, if the input given to this transformer has not changed,
78  * and all of the included sources are (cacheable) and still valid, this transformer
79  * will not force a pipeline re-generation like the {@link CIncludeTransformer}.</p>
80  *
81  *
82  * <h3>Relative Source Resolution</h3>
83  * <p>Include sources which are specified using relative URI will be resolved
84  * relative to the source document location. This is consistent with
85  * {@link XIncludeTransformer} behavior, but differs from {@link CIncludeTransformer}.
86  *
87  *
88  * <h3>Parameters Passing</h3>
89  * <p>Parameters to be passed to the included sources can be specified in two ways:
90  * the first one is to encode them onto the source itelf, for example:</p>
91  *
92  * <pre>
93  * &lt;i:include xmlns:i="http://apache.org/cocoon/include/1.0"
94  * SRC="cocoon://path/to/include?paramA=valueA&amp;paramB=valueB"/&gt;
95  * </pre>
96  *
97  * <p>Another approach allows the encoding of parameters to be done automatically by
98  * the transformer, so that one can easily pass parameter name or values containing
99  * the <code>&amp;</code> (amperstand) or <code>=</code> (equals) character, which are
100  * reserved characters in URIs. An example:</p>
101  *
102  * <pre>
103  * &lt;i:include xmlns:i="http://apache.org/cocoon/include/1.0"
104  * SRC="cocoon://path/to/include"&gt;
105  * &lt;i:parameter name="firstParameterName" value="firstParameterValue"/&gt;
106  * &lt;i:parameter name="other&amp;Para=Name" value="other=Para&amp;Value"/&gt;
107  * &lt;/i:include&gt;
108  * </pre>
109  *
110  *
111  * <h3>Fallback Element</h3>
112  * <p>IncludeTransformer allows fallback element to be specified within
113  * include element. XML content of the fallback element will be included instead
114  * of source content if source inclusion caused an exception. Fallback element
115  * can have nested include elements. An example:</p>
116  *
117  * <pre>
118  * &lt;i:include xmlns:i="http://apache.org/cocoon/include/1.0"
119  * SRC="cocoon://path/to/include"&gt;
120  * &lt;i:fallback&gt;
121  * <strong>The data is temporarily unavailable.</strong>
122  * We are sorry for the trouble; please try again later.
123  * &lt;/i:fallback&gt;
124  * &lt;/i:include&gt;
125  * </pre>
126  *
127  *
128  * <h3>Parallel Processing</h3>
129  * <p>Another feature of this {@link Transformer} is that it allows parallel processing
130  * of includes. By setting the optional parameter <code>parallel</code> to true,
131  * the various included contents are processed (included) in parallel threads rather
132  * than in series, in one thread. This parameter can be set in either the transformer
133  * definition (to affect all IncludeTransformer instances):</p>
134  * <pre>
135  * &lt;parallel&gt;true&lt;/parallel&gt;
136  * </pre>
137  *
138  * <p>or in a pipeline itself (to only affect that instance of the IncludeTransformer):</p>
139  * <pre>
140  * &lt;map:parameter name="parallel" value="true"/&gt;
141  * </pre>
142  * <p>By default, parallel processing is turned off.</p>
143  *
144  *
145  * <h3>Recursive Processing</h3>
146  * <p>This {@link Transformer} allows recursive processing of includes.
147  * By setting the optional parameter <code>recursive</code> to true,
148  * the various included contents are scanned for include elements, and processed
149  * in the same manner as incoming XML events. This parameter can be set in either
150  * the transformer definition (to affect all IncludeTransformer instances):</p>
151  * <pre>
152  * &lt;recursive&gt;true&lt;/recursive&gt;
153  * </pre>
154  *
155  * <p>or in a pipeline itself (to only affect that instance of the IncludeTransformer):</p>
156  * <pre>
157  * &lt;map:parameter name="recursive" value="true"/&gt;
158  * </pre>
159  * <p>This feature is similar to the XInclude processing. By default,
160  * recursive processing is turned off.</p>
161  *
162  *
163  * @cocoon.sitemap.component.documentation
164  * A simple transformer including resolvable sources (accessed through
165  * Cocoon's SourceResolver) from its input.
166  *
167  * @cocoon.sitemap.component.name include
168  * @cocoon.sitemap.component.logger sitemap.transformer.include
169  * @cocoon.sitemap.component.pooling.max 16
170  * @version $Id: IncludeTransformer.java 230770 2005-08-08 07:53:20Z antonio $
171  */

172 public class IncludeTransformer extends AbstractTransformer
173                                 implements Serviceable, Configurable,
174                                            Transformer, CacheableProcessingComponent {
175
176     /** <p>The namespace URI of the elements recognized by this transformer.</p> */
177     private static final String JavaDoc NS_URI = "http://apache.org/cocoon/include/1.0";
178
179     /** <p>The name of the element triggering inclusion of sources.</p> */
180     private static final String JavaDoc INCLUDE_ELEMENT = "include";
181
182     /** <p>The name of the element defining a fallback content.</p> */
183     private static final String JavaDoc FALLBACK_ELEMENT = "fallback";
184
185     /** <p>The name of the element defining an included subrequest parameter.</p> */
186     private static final String JavaDoc PARAMETER_ELEMENT = "parameter";
187
188     /** <p>The name of the attribute indicating the included source URI.</p> */
189     private static final String JavaDoc SRC_ATTRIBUTE = "src";
190
191     /** <p>The name of the mime type attribute containing the hint for the {@link org.apache.excalibur.xmlizer.XMLizer}.</p> */
192     private static final String JavaDoc MIME_ATTRIBUTE = "mime-type";
193
194     /** <p>The name of the parse attribute indicating type of included source processing: xml or text.</p> */
195     private static final String JavaDoc PARSE_ATTRIBUTE = "parse";
196
197     /** <p>The name of the attribute indicating the parameter name.</p> */
198     private static final String JavaDoc NAME_ATTRIBUTE = "name";
199
200     /** <p>The name of the attribute indicating the parameter name.</p> */
201     private static final String JavaDoc VALUE_ATTRIBUTE = "value";
202
203     /** <p>The encoding to use for parameter names and values.</p> */
204     private static final String JavaDoc ENCODING = "US-ASCII";
205
206     //
207
// Global configuration
208
//
209

210     /** The {@link ServiceManager} instance associated with this instance. */
211     private ServiceManager manager;
212
213     /** Configuration option controlling recursive includes processing */
214     private boolean defaultRecursive;
215
216     /** Configuration option controlling parallel (in multiple threads) includes processing */
217     private boolean defaultParallel;
218
219     /** Configuration option controlling parallel (in multiple threads) includes processing in the recursive includes */
220     private boolean defaultRecursiveParallel;
221
222     /** The name of the thread pool to use (for parallel processing). */
223     private String JavaDoc threadPool;
224
225     /** The default value to be appended to the caching key. */
226     private String JavaDoc defaultKey;
227
228     //
229
// Current configuration
230
//
231

232     /** The {@link SourceResolver} used to resolve included URIs. */
233     private SourceResolver resolver;
234
235     /** The {@link Environment} used within parallel threads */
236     private Environment environment;
237
238     /** The {@link Processor} used within parallel threads */
239     private Processor processor;
240
241     /** The value to be appended to the caching key. */
242     private String JavaDoc key;
243
244     //
245
// Current state
246
//
247

248     /** The {@link SourceValidity} instance associated with this request. */
249     private MultiSourceValidity validity;
250
251     /** A {@link NamespacesTable} used to filter namespace declarations. */
252     private NamespacesTable namespaces;
253
254     /** The {@link IncludeXMLPipe} which is doing all the work */
255     private final IncludeXMLPipe pipe;
256
257     /**
258      * <p>Create a new {@link IncludeTransformer} instance.</p>
259      */

260     public IncludeTransformer() {
261         pipe = new IncludeXMLPipe();
262     }
263
264     /**
265      * <p>Initialize own and {@link #pipe} loggers</p>
266      */

267     public void enableLogging(Logger logger) {
268         super.enableLogging(logger);
269         pipe.enableLogging(logger);
270     }
271
272     /**
273      * <p>Setup the {@link ServiceManager} available for this instance.</p>
274      *
275      * @see Serviceable#service(ServiceManager)
276      */

277     public void service(ServiceManager manager) throws ServiceException {
278         this.manager = manager;
279     }
280
281     /* (non-Javadoc)
282      * @see Configurable#configure(Configuration)
283      */

284     public void configure(Configuration configuration) throws ConfigurationException {
285         /* Read configuration nodes for recursive, parallel, recursive-parallel */
286         this.defaultRecursive = configuration.getChild("recursive").getValueAsBoolean(false);
287         this.defaultParallel = configuration.getChild("parallel").getValueAsBoolean(false);
288         this.defaultRecursiveParallel = configuration.getChild("recursive-parallel").getValueAsBoolean(false);
289         /* Read configuration node for thread pool name */
290         this.threadPool = configuration.getChild("thread-pool").getValue("default");
291         this.defaultKey = configuration.getChild("key").getValue(null);
292     }
293
294     /**
295      * <p>Setup this component instance in the context of its pipeline and
296      * current request.</p>
297      *
298      * @see Serviceable#service(ServiceManager)
299      */

300     public void setup(SourceResolver resolver, Map JavaDoc om, String JavaDoc src, Parameters parameters)
301     throws ProcessingException, SAXException JavaDoc, IOException JavaDoc {
302         /* Read sitemap parameters */
303         this.pipe.recursive = parameters.getParameterAsBoolean("recursive", this.defaultRecursive);
304         this.pipe.parallel = parameters.getParameterAsBoolean("parallel", this.defaultParallel);
305         this.pipe.recursiveParallel = parameters.getParameterAsBoolean("recursive-parallel", this.defaultRecursiveParallel);
306         this.key = parameters.getParameter("key", this.defaultKey);
307
308         /* Init transformer state */
309         if (this.pipe.parallel) {
310             this.environment = CocoonComponentManager.getCurrentEnvironment();
311             this.processor = CocoonComponentManager.getCurrentProcessor();
312         }
313         this.namespaces = new NamespacesTable();
314         this.resolver = resolver;
315         this.validity = null;
316
317         // Set root include pipe as consumer.
318
// Won't use setter methods here - they are overridden
319
super.xmlConsumer = pipe;
320         super.contentHandler = pipe;
321         super.lexicalHandler = pipe;
322     }
323
324     public void setConsumer(XMLConsumer consumer) {
325         pipe.setConsumer(consumer);
326     }
327
328     public void setContentHandler(ContentHandler JavaDoc handler) {
329         pipe.setContentHandler(handler);
330     }
331
332     public void setLexicalHandler(LexicalHandler JavaDoc handler) {
333         pipe.setLexicalHandler(handler);
334     }
335
336     /**
337      * <p>Recycle this component instance.</p>
338      *
339      * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
340      */

341     public void recycle() {
342         this.namespaces = null;
343         this.validity = null;
344
345         /* Make sure all threads completed their work */
346         this.pipe.recycle();
347
348         // Resolver can be nulled out when all threads completed processing
349
// and released their Sources.
350
this.resolver = null;
351
352         super.recycle();
353     }
354
355
356     /**
357      * <p>Receive notification of the beginning of an XML document.</p>
358      *
359      * @see ContentHandler#startDocument
360      */

361     public void startDocument()
362     throws SAXException JavaDoc {
363         /* Make sure that we have a validity while processing */
364         getValidity();
365
366         super.startDocument();
367     }
368
369     /**
370      * <p>Receive notification of the end of an XML document.</p>
371      *
372      * @see ContentHandler#startDocument()
373      */

374     public void endDocument()
375     throws SAXException JavaDoc {
376         /* Make sure that the validity is "closed" at the end */
377         this.validity.close();
378
379         super.endDocument();
380     }
381
382     /**
383      * <p>Receive notification of the start of a prefix mapping.</p>
384      *
385      * <p>This transformer will remove all prefix mapping declarations for those
386      * prefixes associated with the <code>http://apache.org/cocoon/include/1.0</code>
387      * namespace.</p>
388      *
389      * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
390      */

391     public void startPrefixMapping(String JavaDoc prefix, String JavaDoc nsuri)
392     throws SAXException JavaDoc {
393         if (NS_URI.equals(nsuri)) {
394             /* Skipping mapping for the current prefix as it's ours */
395             this.namespaces.addDeclaration(prefix, nsuri);
396         } else {
397             /* Map the current prefix, as we don't know it */
398             super.startPrefixMapping(prefix, nsuri);
399         }
400     }
401
402     /**
403      * <p>Receive notification of the end of a prefix mapping.</p>
404      *
405      * <p>This transformer will remove all prefix mapping declarations for those
406      * prefixes associated with the <code>http://apache.org/cocoon/include/1.0</code>
407      * namespace.</p>
408      *
409      * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
410      */

411     public void endPrefixMapping(String JavaDoc prefix)
412     throws SAXException JavaDoc {
413         if (NS_URI.equals(this.namespaces.getUri(prefix))) {
414             /* Skipping unmapping for the current prefix as it's ours */
415             this.namespaces.removeDeclaration(prefix);
416         } else {
417             /* Unmap the current prefix, as we don't know it */
418             super.endPrefixMapping(prefix);
419         }
420     }
421
422     /**
423      * <p>Return the caching key associated with this transformation.</p>
424      *
425      * <p>When including <code>cocoon://</code> sources with dynamic
426      * content depending on environment (request parameters, session attributes,
427      * etc), it makes sense to provide such environment values to the transformer
428      * to be included into the key using <code>key</code> sitemap parameter.</p>
429      *
430      * @see CacheableProcessingComponent#getKey()
431      */

432     public Serializable JavaDoc getKey() {
433         /*
434          * In case of including "cocoon://" or other dynamic sources key
435          * ideally has to include ProcessingPipelineKey of the included
436          * "cocoon://" sources, but it's not possible as at this time
437          * we don't know yet which sources will get included into the
438          * response.
439          *
440          * Hence, javadoc recommends providing key using sitemap parameter.
441          */

442         return key == null? "I": "I" + key;
443     }
444
445     /**
446      * <p>Generate (or return) the {@link SourceValidity} instance used to
447      * possibly validate cached generations.</p>
448      *
449      * @return a <b>non null</b> {@link SourceValidity}.
450      * @see org.apache.cocoon.caching.CacheableProcessingComponent#getValidity()
451      */

452     public SourceValidity getValidity() {
453         if (validity == null) {
454             validity = new MultiSourceValidity(resolver, -1);
455         }
456         return validity;
457     }
458
459     /**
460      * Description of the include element
461      */

462     private class IncludeElement extends AbstractLogEnabled {
463         /** Parameter controlling recursive includes processing */
464         private boolean recursive;
465
466         /** Parameter controlling parallel (in multiple threads) includes processing */
467         private boolean parallel;
468
469         /** Parameter controlling parallel (in multiple threads) includes processing in recursive includes */
470         private boolean recursiveParallel;
471
472         /** The source base URI. */
473         private String JavaDoc base;
474
475         /** The source URI to be included declared in an src attribute of the include element. */
476         private String JavaDoc source;
477
478         /** The flag indicating whether source content has to be parsed into XML or included as text. */
479         private boolean parse;
480
481         /** The mime type hint to the {@link org.apache.excalibur.xmlizer.XMLizer} when parsing the source content. */
482         private String JavaDoc mimeType;
483
484         /** The buffer collecting fallback content. */
485         private SaxBuffer fallback;
486
487         /** A {@link Map} of the parameters to supply to the included source. */
488         private Map JavaDoc parameters;
489
490         /** The current parameter name captured. */
491         private String JavaDoc parameter;
492
493         /** The current parameter value (as a {@link StringBuffer}). */
494         private StringBuffer JavaDoc value;
495         
496         /** Create include element */
497         private IncludeElement(String JavaDoc base, boolean parallel, boolean recursive, boolean recursiveParallel, Logger logger) {
498             this.base = base;
499             this.parallel = parallel;
500             this.recursive = recursive;
501             this.recursiveParallel = recursiveParallel;
502             this.enableLogging(logger);
503         }
504
505         /**
506          * Process element into the buffer.
507          * This can not be shared buffer, as it must be cleaned if fallback is invoked.
508          */

509         public void process(SaxBuffer buffer)
510         throws SAXException JavaDoc {
511             try {
512                 process0(buffer, buffer);
513             } catch (SAXException JavaDoc e) {
514                 buffer.recycle();
515                 if (this.fallback == null) {
516                     throw e;
517                 }
518
519                 if (getLogger().isInfoEnabled()) {
520                     getLogger().info("Failed to load <" + this.source + ">, using fallback.", e);
521                 }
522                 // Stream fallback through IncludeXMLPipe
523
this.fallback.toSAX(new IncludeXMLPipe(getLogger(), buffer, buffer,
524                                                        recursive, recursiveParallel? parallel: false, recursiveParallel));
525             }
526         }
527
528         /** Load URI into the provided handlers, process fallback */
529         public void process(ContentHandler JavaDoc contentHandler, LexicalHandler JavaDoc lexicalHandler)
530         throws SAXException JavaDoc {
531             if (this.fallback != null) {
532                 SaxBuffer buffer = new SaxBuffer();
533                 process(buffer);
534                 buffer.toSAX(contentHandler);
535             } else {
536                 process0(contentHandler, lexicalHandler);
537             }
538         }
539
540         /** Load URI into the provided handlers. */
541         private void process0(ContentHandler JavaDoc contentHandler, LexicalHandler JavaDoc lexicalHandler)
542         throws SAXException JavaDoc {
543             Source source = null;
544             if (getLogger().isDebugEnabled()) {
545                 getLogger().debug("Loading <" + this.source + ">");
546             }
547
548             // Setup this thread's environment
549
try {
550                 if (base != null) {
551                     source = resolver.resolveURI(this.source, base, null);
552                 } else {
553                     source = resolver.resolveURI(this.source);
554                 }
555                 if (validity != null) {
556                     synchronized (validity) {
557                         validity.addSource(source);
558                     }
559                 }
560
561                 // Include source
562
if (this.parse && recursive) {
563                     SourceUtil.toSAX(manager, source, this.mimeType,
564                                      new IncludeXMLPipe(getLogger(), contentHandler, lexicalHandler,
565                                                         recursive, recursiveParallel? parallel: false, recursiveParallel));
566                 } else if (this.parse) {
567                     SourceUtil.toSAX(manager, source, this.mimeType,
568                                      new IncludeXMLConsumer(contentHandler, lexicalHandler));
569                 } else {
570                     SourceUtil.toCharacters(source, "utf-8",
571                                             contentHandler);
572                 }
573
574                 if (getLogger().isDebugEnabled()) {
575                     getLogger().debug("Loaded <" + this.source + ">");
576                 }
577             } catch (SAXException JavaDoc e) {
578                 if (getLogger().isDebugEnabled()) {
579                     getLogger().debug("Failed to load <" + this.source + ">", e);
580                 }
581
582                 throw e;
583
584             } catch (ProcessingException e) {
585                 if (getLogger().isDebugEnabled()) {
586                     getLogger().debug("Failed to load <" + this.source + ">", e);
587                 }
588
589                 throw new SAXException JavaDoc(e);
590
591             } catch (IOException JavaDoc e) {
592                 if (getLogger().isDebugEnabled()) {
593                     getLogger().debug("Failed to load <" + this.source + ">", e);
594                 }
595
596                 throw new SAXException JavaDoc(e);
597
598             } finally {
599                 if (source != null) {
600                     resolver.release(source);
601                 }
602             }
603         }
604     }
605
606     /**
607      * XML pipe reacting on the elements in the include namespace.
608      */

609     private class IncludeXMLPipe extends AbstractXMLPipe {
610
611         //
612
// Configuration
613
//
614

615         /** Indicates whether this is root include pipe (owned by transformer) or a nested one */
616         private final boolean root;
617
618         /** Parameter controlling recursive includes processing */
619         private boolean recursive;
620
621         /** Parameter controlling parallel (in multiple threads) includes processing */
622         private boolean parallel;
623
624         /** Parameter controlling parallel (in multiple threads) includes processing in recursive includes */
625         private boolean recursiveParallel;
626
627         //
628
// Current state
629
//
630

631         /** Stack of {@link XMLConsumer}s */
632         private final Stack JavaDoc consumers = new Stack JavaDoc();
633
634         /** Current depth of nested elements in the include namespace */
635         private int depth;
636
637         /** Base URI used for the resolving included sources */
638         private String JavaDoc base;
639
640         /** The source to be included declared in an include element. */
641         private IncludeElement element;
642
643         /** If parallel processing is enabled, then this boolean tells us whether buffering has started yet. */
644         private boolean buffering;
645
646         /**
647          * <p>The IncludeBuffer that is used to buffering events if parallel
648          * processing is turned on.</p>
649          * <p>This object is also used as a lock for the thread counter <code>threads</code>.</p>
650          */

651         private SaxBuffer buffer;
652
653         /** Inclusion threads/tasks counter (if executing in parallel) */
654         private int threads;
655
656         /**
657          * <p>Create a new {@link IncludeXMLPipe} instance.</p>
658          */

659         public IncludeXMLPipe() {
660             root = true;
661         }
662
663         /**
664          * <p>Create a new {@link IncludeXMLPipe} instance.</p>
665          */

666         public IncludeXMLPipe(Logger logger, ContentHandler JavaDoc contentHandler, LexicalHandler JavaDoc lexicalHandler,
667                               boolean recursive, boolean parallel, boolean recursiveParallel) {
668             root = false;
669             this.enableLogging(logger);
670             this.setContentHandler(contentHandler);
671             this.setLexicalHandler(lexicalHandler);
672             this.recursive = recursive;
673             this.parallel = parallel;
674             this.recursiveParallel = recursiveParallel;
675         }
676
677         /**
678          * Finish processing.
679          */

680         public void recycle() {
681             if (this.buffering) {
682                 // Wait for threads to complete and release Sources
683
waitForThreads();
684                 this.buffering = false;
685                 this.buffer = null;
686             }
687             this.threads = 0;
688
689             this.consumers.clear();
690             this.base = null;
691             this.element = null;
692
693             super.recycle();
694         }
695
696         /** Push current consumer into the stack, replace with new one */
697         private void push(XMLConsumer consumer) {
698             this.consumers.push(new Object JavaDoc[]{ super.xmlConsumer, super.contentHandler, super.lexicalHandler });
699             this.setConsumer(consumer);
700         }
701
702         /** Pop consumer from the stack, replace current one */
703         private void pop() {
704             Object JavaDoc[] consumer = (Object JavaDoc[]) this.consumers.pop();
705             if (consumer[0] != null) {
706                 this.setConsumer((XMLConsumer) consumer[0]);
707             } else {
708                 this.setContentHandler((ContentHandler JavaDoc) consumer[1]);
709                 this.setLexicalHandler((LexicalHandler JavaDoc) consumer[2]);
710             }
711         }
712
713         //
714
// ContentHandler interface
715
//
716

717         public void setDocumentLocator(Locator JavaDoc locator) {
718             try {
719                 if (locator != null && locator.getSystemId() != null) {
720                     Source source = resolver.resolveURI(locator.getSystemId());
721                     try {
722                         base = source.getURI();
723                     } finally {
724                         resolver.release(source);
725                     }
726                 }
727             } catch (IOException JavaDoc e) {
728                 getLogger().warn("Unable to resolve document base URI: <" + locator.getSystemId() + ">");
729             }
730
731             super.setDocumentLocator(locator);
732         }
733
734         /**
735          * <p>Receive notification of the beginning of an XML document.</p>
736          * @see ContentHandler#startDocument
737          */

738         public void startDocument() throws SAXException JavaDoc {
739             if (root) {
740                 super.startDocument();
741             }
742         }
743
744         /**
745          * <p>Receive notification of the end of an XML document.</p>
746          * @see ContentHandler#startDocument
747          */

748         public void endDocument() throws SAXException JavaDoc {
749             /* This is the end of the line - process the buffered events */
750             if (this.buffering) {
751                 pop();
752                 this.buffer.toSAX(super.contentHandler);
753             }
754
755             if (root) {
756                 super.endDocument();
757             }
758         }
759
760         /**
761          * <p>Receive notification of the start of an element.</p>
762          * @see ContentHandler#startElement
763          */

764         public void startElement(String JavaDoc uri, String JavaDoc localName, String JavaDoc qName, Attributes JavaDoc atts)
765         throws SAXException JavaDoc {
766
767             /* Check the namespace declaration */
768             if (NS_URI.equals(uri)) {
769
770                 /*
771                  * Depth 0: Outside of any include tag
772                  * Depth 1: Must be Inside <include> tag
773                  * Depth 2: Inside <fallback> tag
774                  */

775                 depth++;
776
777                 /* Inclusion will not happen here but when we close this tag */
778                 if (INCLUDE_ELEMENT.equals(localName) && depth == 1) {
779                     /* Check before we include (we don't want nested stuff) */
780                     if (element != null) {
781                         throw new SAXException JavaDoc("Element " + INCLUDE_ELEMENT + " nested in another one.");
782                     }
783                     element = new IncludeElement(this.base, this.parallel, this.recursive, this.recursiveParallel, getLogger());
784
785                     /* Remember the source we are trying to include */
786                     element.source = atts.getValue(SRC_ATTRIBUTE);
787                     if (element.source == null || element.source.length() == 0) {
788                         throw new SAXException JavaDoc("Attribute '" + SRC_ATTRIBUTE + "' empty or missing.");
789                     }
790
791                     /* Defaults to 'xml' */
792                     String JavaDoc value = atts.getValue(PARSE_ATTRIBUTE);
793                     if (value == null || value.equals("xml")) {
794                         element.parse = true;
795                     } else if (value.equals("text")) {
796                         element.parse = false;
797                     } else {
798                         throw new SAXException JavaDoc("Attribute '" + PARSE_ATTRIBUTE + "' has invalid value.");
799                     }
800
801                     /* Defaults to 'text/xml' */
802                     element.mimeType = atts.getValue(MIME_ATTRIBUTE);
803                     if (!element.parse && element.mimeType != null) {
804                         throw new SAXException JavaDoc("Attribute '" + MIME_ATTRIBUTE + "' can't be specified for text inclusions.");
805                     } else if (element.mimeType == null) {
806                         element.mimeType = "text/xml";
807                     }
808
809                     /* Ignore nested content */
810                     push(new NOPRecorder(){});
811
812                     /* Done with this element */
813                     return;
814                 }
815
816                 /* If this is a fallback parameter, capture its content. */
817                 if (FALLBACK_ELEMENT.equals(localName) && depth == 2) {
818                     /* Check if we are in the right context */
819                     if (element == null) {
820                         throw new SAXException JavaDoc("Element " + FALLBACK_ELEMENT + " specified outside of " + INCLUDE_ELEMENT + ".");
821                     }
822                     if (element.fallback != null) {
823                         throw new SAXException JavaDoc("Duplicate element " + FALLBACK_ELEMENT + ".");
824                     }
825
826                     /* Buffer fallback content */
827                     push(element.fallback = new SaxBuffer());
828
829                     /* Done with this element */
830                     return;
831                 }
832
833                 /* If this is a parameter, then make sure we prepare. */
834                 if (PARAMETER_ELEMENT.equals(localName) && depth == 2) {
835                     /* Check if we are in the right context */
836                     if (element == null) {
837                         throw new SAXException JavaDoc("Element " + PARAMETER_ELEMENT + " specified outside of " + INCLUDE_ELEMENT + ".");
838                     }
839                     if (element.parameter != null) {
840                         throw new SAXException JavaDoc("Element " + PARAMETER_ELEMENT + " nested in another one.");
841                     }
842
843                     /* Get and process the parameter name */
844                     element.parameter = atts.getValue(NAME_ATTRIBUTE);
845                     if (element.parameter == null || element.parameter.length() == 0) {
846                         throw new SAXException JavaDoc("Attribute '" + NAME_ATTRIBUTE + "' empty or missing.");
847                     }
848
849                     /* Make some room for the parameter value */
850                     String JavaDoc value = atts.getValue(VALUE_ATTRIBUTE);
851                     if (value != null) {
852                         element.value = new StringBuffer JavaDoc(value);
853                     }
854
855                     /* Done with this element */
856                     return;
857                 }
858
859                 /* We don't have a clue of why we got here (wrong element?) */
860                 if (depth < 2) {
861                     throw new SAXException JavaDoc("Element '" + localName + "' was not expected here.");
862                 }
863             }
864
865             super.startElement(uri, localName, qName, atts);
866         }
867
868         /**
869          * <p>Receive notification of the end of an element.</p>
870          * @see ContentHandler#endElement
871          */

872         public void endElement(String JavaDoc uri, String JavaDoc localName, String JavaDoc qName)
873         throws SAXException JavaDoc {
874             /* Check the namespace declaration */
875             if (NS_URI.equals(uri)) {
876
877                 /*
878                  * Depth 0: Outside of any include tag
879                  * Depth 1: Inside <include> tag
880                  * Depth 2: Inside <fallback> tag
881                  */

882                 depth--;
883
884                 /* Inclusion will happen here, when we close the include element */
885                 if (INCLUDE_ELEMENT.equals(localName) && depth == 0) {
886                     /* End ignoring nested content */
887                     pop();
888
889                     /* Get the source discovered opening the element and include */
890                     if (element.parameters != null) {
891                         element.source = NetUtils.parameterize(element.source,
892                                                                element.parameters);
893                         element.parameters = null;
894                     }
895
896                     /* Check for parallel processing */
897                     if (this.parallel) {
898                         if (!this.buffering) {
899                             this.buffering = true;
900                             buffer = new SaxBuffer();
901                             push(buffer);
902                         }
903
904                         /* Process include element in separate thread */
905                         buffer.xmlizable(new IncludeBuffer(element));
906
907                     } else {
908                         /* Process include element inline */
909                         element.process(super.contentHandler, super.lexicalHandler);
910                     }
911
912                     /* We are done with this include element */
913                     this.element = null;
914                     return;
915                 }
916
917                 if (FALLBACK_ELEMENT.equals(localName) && depth == 1) {
918                     /* End buffering fallback content */
919                     pop();
920
921                     /* Done with this element */
922                     return;
923                 }
924
925                 /* Addition of parameters happens here (so that we can capture chars) */
926                 if (PARAMETER_ELEMENT.equals(localName) && depth == 1) {
927                     String JavaDoc value = (element.value != null? element.value.toString(): "");
928
929                     /* Store the parameter name and value */
930                     try {
931                         /*
932                          * Note: the parameter name and value are URL encoded, so that
933                          * weird characters such as "&" or "=" (have special meaning)
934                          * are passed through flawlessly.
935                          */

936                         if (element.parameters == null) {
937                             element.parameters = new HashMap JavaDoc(5);
938                         }
939                         element.parameters.put(NetUtils.encode(element.parameter, ENCODING),
940                                                NetUtils.encode(value, ENCODING));
941                     } catch (UnsupportedEncodingException JavaDoc e) {
942                         throw new SAXException JavaDoc("Your platform does not support the " +
943                                                ENCODING + " encoding", e);
944                     }
945
946                     /* We are done with this parameter element */
947                     element.value = null;
948                     element.parameter = null;
949                     return;
950                 }
951             }
952
953             /* This is not our namespace, pass the event on! */
954             super.endElement(uri, localName, qName);
955         }
956
957         /**
958          * <p>Receive notification of characters.</p>
959          * @see ContentHandler#characters
960          */

961         public void characters(char[] data, int offset, int length)
962         throws SAXException JavaDoc {
963             if (element != null && element.parameter != null) {
964                 /* If we have a parameter value to add to, let's add this chunk */
965                 if (element.value == null) {
966                     element.value = new StringBuffer JavaDoc();
967                 }
968                 element.value.append(data, offset, length);
969                 return;
970             }
971
972             /* Forward */
973             super.characters(data, offset, length);
974         }
975
976         //
977
// Thread management
978
//
979

980         /**
981          * Increment active threads counter
982          */

983         int incrementThreads() {
984             synchronized (buffer) {
985                 return ++threads;
986             }
987         }
988
989         /**
990          * Decrement active threads counter
991          */

992         void decrementThreads() {
993             synchronized (buffer) {
994                 if (--threads <= 0) {
995                     buffer.notify();
996                 }
997             }
998         }
999
1000        /**
1001         * Wait till there is no active threads
1002         */

1003        private void waitForThreads() {
1004            synchronized (buffer) {
1005                if (threads > 0) {
1006                    if (getLogger().isDebugEnabled()) {
1007                        getLogger().debug(threads + " threads in progress, waiting");
1008                    }
1009
1010                    try {
1011                        buffer.wait();
1012                    } catch (InterruptedException JavaDoc ignored) { }
1013                    // Don't continue waiting if interrupted.
1014
}
1015            }
1016        }
1017
1018        /**
1019         * Buffer for loading included source in separate thread.
1020         * Streaming of the loaded buffer possible only when source is
1021         * loaded completely. If loading is not complete, toSAX method
1022         * will block.
1023         */

1024        private class IncludeBuffer extends SaxBuffer
1025                                    implements Runnable JavaDoc {
1026
1027            private IncludeElement element;
1028            private int thread;
1029            private boolean finished;
1030            private SAXException JavaDoc e;
1031
1032
1033            public IncludeBuffer(IncludeElement element) {
1034                this.element = element;
1035
1036                RunnableManager runnable = null;
1037                try {
1038                    runnable = (RunnableManager) IncludeTransformer.this.manager.lookup(RunnableManager.ROLE);
1039                    runnable.execute(IncludeTransformer.this.threadPool, this);
1040                } catch (final ServiceException e) {
1041                    // In case we failed to spawn a thread
1042
throw new CascadingRuntimeException(e.getMessage(), e);
1043                } finally {
1044                    IncludeTransformer.this.manager.release(runnable);
1045                }
1046
1047                // Increment active threads counter
1048
this.thread = incrementThreads();
1049            }
1050
1051            /**
1052             * Load content of the source into this buffer.
1053             */

1054            public void run() {
1055                try {
1056                    Source source = null;
1057                    if (getLogger().isDebugEnabled()) {
1058                        getLogger().debug("Thread #" + thread + " loading <" + element.source + ">");
1059                    }
1060
1061                    // Setup this thread's environment
1062
CocoonComponentManager.enterEnvironment(environment, new WrapperComponentManager(manager), processor);
1063                    try {
1064                        element.process(this);
1065
1066                    } catch (SAXException JavaDoc e) {
1067                        this.e = e;
1068
1069                    } finally {
1070                        if (source != null) {
1071                            resolver.release(source);
1072                        }
1073                        CocoonComponentManager.leaveEnvironment();
1074                    }
1075                } catch (RuntimeException JavaDoc e) {
1076                    /* Unable to set thread's environment */
1077                    this.e = new SAXException JavaDoc(e);
1078
1079                } finally {
1080                    synchronized (this) {
1081                        this.finished = true;
1082                        notify();
1083                    }
1084
1085                    // Make sure that active threads counter is decremented
1086
decrementThreads();
1087                }
1088
1089                if (getLogger().isDebugEnabled()) {
1090                    if (this.e == null) {
1091                        getLogger().debug("Thread #" + thread + " loaded <" + element.source + ">");
1092                    } else {
1093                        getLogger().debug("Thread #" + thread + " failed to load <" + element.source + ">", this.e);
1094                    }
1095                }
1096            }
1097
1098            /**
1099             * Stream content of this buffer when it is loaded completely.
1100             * This method blocks if loading is not complete.
1101             */

1102            public void toSAX(ContentHandler JavaDoc contentHandler)
1103            throws SAXException JavaDoc {
1104                synchronized (this) {
1105                    if (!this.finished) {
1106                        try {
1107                            wait();
1108                        } catch (InterruptedException JavaDoc ignored) { }
1109                        // Don't continue waiting if interrupted.
1110
}
1111                }
1112
1113                if (this.e != null) {
1114                    throw this.e;
1115                }
1116
1117                super.toSAX(contentHandler);
1118            }
1119        }
1120    }
1121}
1122
Popular Tags