KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > serialization > AbstractTextSerializer


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.serialization;
17
18 import org.apache.avalon.framework.configuration.Configurable;
19 import org.apache.avalon.framework.configuration.Configuration;
20 import org.apache.avalon.framework.configuration.ConfigurationException;
21 import org.apache.avalon.framework.context.Context;
22 import org.apache.avalon.framework.context.ContextException;
23 import org.apache.avalon.framework.context.Contextualizable;
24 import org.apache.cocoon.Constants;
25 import org.apache.cocoon.caching.CacheableProcessingComponent;
26 import org.apache.cocoon.util.ClassUtils;
27 import org.apache.cocoon.util.TraxErrorHandler;
28 import org.apache.cocoon.xml.AbstractXMLPipe;
29 import org.apache.cocoon.xml.XMLConsumer;
30 import org.apache.cocoon.xml.XMLUtils;
31
32 import org.apache.commons.lang.BooleanUtils;
33 import org.apache.excalibur.source.SourceValidity;
34 import org.apache.excalibur.source.impl.validity.NOPValidity;
35 import org.xml.sax.Attributes JavaDoc;
36 import org.xml.sax.ContentHandler JavaDoc;
37 import org.xml.sax.SAXException JavaDoc;
38 import org.xml.sax.ext.LexicalHandler JavaDoc;
39 import org.xml.sax.helpers.AttributesImpl JavaDoc;
40
41 import javax.xml.transform.OutputKeys JavaDoc;
42 import javax.xml.transform.TransformerFactory JavaDoc;
43 import javax.xml.transform.TransformerException JavaDoc;
44 import javax.xml.transform.sax.SAXTransformerFactory JavaDoc;
45 import javax.xml.transform.sax.TransformerHandler JavaDoc;
46 import javax.xml.transform.stream.StreamResult JavaDoc;
47 import java.io.IOException JavaDoc;
48 import java.io.OutputStream JavaDoc;
49 import java.io.StringWriter JavaDoc;
50 import java.util.ArrayList JavaDoc;
51 import java.util.HashMap JavaDoc;
52 import java.util.List JavaDoc;
53 import java.util.Map JavaDoc;
54 import java.util.Properties JavaDoc;
55
56 /**
57  * @author <a HREF="mailto:pier@apache.org">Pierpaolo Fumagalli</a>
58  * (Apache Software Foundation)
59  * @author <a HREF="mailto:stefano@apache.org">Stefano Mazzocchi</a>
60  * @author <a HREF="mailto:sylvain.wallez@anyware-tech.com">Sylvain Wallez</a>
61  * @version $Id: AbstractTextSerializer.java 265735 2005-09-01 14:04:30Z cziegeler $
62  */

63 public abstract class AbstractTextSerializer extends AbstractSerializer
64         implements Configurable, CacheableProcessingComponent, Contextualizable {
65
66     /**
67      * Cache for avoiding unnecessary checks of namespaces abilities.
68      * It associates a Boolean to the transformer class name.
69      */

70     private static final Map JavaDoc needsNamespaceCache = new HashMap JavaDoc();
71
72     /**
73      * The trax <code>TransformerFactory</code> used by this serializer.
74      */

75     private SAXTransformerFactory JavaDoc tfactory;
76
77     /**
78      * The <code>Properties</code> used by this serializer.
79      */

80     protected final Properties JavaDoc format = new Properties JavaDoc();
81
82     /**
83      * The pipe that adds namespaces as xmlns attributes.
84      */

85     private NamespaceAsAttributes namespacePipe;
86
87     /**
88      * The caching key
89      */

90     private String JavaDoc cachingKey = "1";
91
92     /**
93      * Interpose namespace pipe if needed.
94      */

95     public void setConsumer(XMLConsumer consumer) {
96         if (this.namespacePipe == null) {
97             super.setConsumer(consumer);
98         } else {
99             this.namespacePipe.setConsumer(consumer);
100             super.setConsumer(this.namespacePipe);
101         }
102     }
103
104     /**
105      * Interpose namespace pipe if needed.
106      */

107     public void setContentHandler(ContentHandler JavaDoc handler) {
108         if (this.namespacePipe == null) {
109             super.setContentHandler(handler);
110         } else {
111             this.namespacePipe.setContentHandler(handler);
112             super.setContentHandler(this.namespacePipe);
113         }
114     }
115
116     /**
117      * Interpose namespace pipe if needed.
118      */

119     public void setLexicalHandler(LexicalHandler JavaDoc handler) {
120         if (this.namespacePipe == null) {
121             super.setLexicalHandler(handler);
122         } else {
123             this.namespacePipe.setLexicalHandler(handler);
124             super.setLexicalHandler(this.namespacePipe);
125         }
126     }
127
128     /**
129      * Helper for TransformerFactory.
130      */

131     protected SAXTransformerFactory JavaDoc getTransformerFactory() {
132         return tfactory;
133     }
134
135     /**
136      * Helper for TransformerHandler.
137      */

138     protected TransformerHandler JavaDoc getTransformerHandler() throws TransformerException JavaDoc {
139         return this.getTransformerFactory().newTransformerHandler();
140     }
141
142     /**
143      * Set the {@link OutputStream} where the requested resource should
144      * be serialized.
145      */

146     public void setOutputStream(OutputStream JavaDoc out) throws IOException JavaDoc {
147         /*
148          * Add a level of buffering to the output stream. Xalan serializes
149          * every character individually. In conjunction with chunked
150          * transfer encoding this would otherwise lead to a whopping 6-fold
151          * increase of data on the wire.
152          */

153         // if (outputBufferSize > 0) {
154
// super.setOutputStream(
155
// new BufferedOutputStream(out, outputBufferSize));
156
// } else {
157
super.setOutputStream(out);
158         // }
159
}
160
161     /**
162      * Uses the context to retrieve a default encoding for the serializers.
163      */

164     public void contextualize(Context context) throws ContextException {
165         String JavaDoc defaultEncoding = (String JavaDoc)context.get(Constants.CONTEXT_DEFAULT_ENCODING);
166         if (defaultEncoding != null) {
167             this.format.setProperty(OutputKeys.ENCODING, defaultEncoding);
168         }
169     }
170
171     /**
172      * Set the configurations for this serializer.
173      */

174     public void configure(Configuration conf) throws ConfigurationException {
175         // configure buffer size
176
// Configuration bsc = conf.getChild("buffer-size", false);
177
// if(null != bsc)
178
// outputBufferSize = bsc.getValueAsInteger(DEFAULT_BUFFER_SIZE);
179

180         // configure xalan
181
String JavaDoc cdataSectionElements = conf.getChild("cdata-section-elements").getValue(null);
182         String JavaDoc dtPublic = conf.getChild("doctype-public").getValue(null);
183         String JavaDoc dtSystem = conf.getChild("doctype-system").getValue(null);
184         String JavaDoc encoding = conf.getChild("encoding").getValue(null);
185         String JavaDoc indent = conf.getChild("indent").getValue(null);
186         String JavaDoc mediaType = conf.getChild("media-type").getValue(null);
187         String JavaDoc method = conf.getChild("method").getValue(null);
188         String JavaDoc omitXMLDeclaration = conf.getChild("omit-xml-declaration").getValue(null);
189         String JavaDoc standAlone = conf.getChild("standalone").getValue(null);
190         String JavaDoc version = conf.getChild("version").getValue(null);
191
192         final StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
193
194         if (cdataSectionElements != null) {
195             format.put(OutputKeys.CDATA_SECTION_ELEMENTS, cdataSectionElements);
196             buffer.append(";cdata-section-elements=").append(cdataSectionElements);
197         }
198         if (dtPublic != null) {
199             format.put(OutputKeys.DOCTYPE_PUBLIC, dtPublic);
200             buffer.append(";doctype-public=").append(dtPublic);
201         }
202         if (dtSystem != null) {
203             format.put(OutputKeys.DOCTYPE_SYSTEM, dtSystem);
204             buffer.append(";doctype-system=").append(dtSystem);
205         }
206         if (encoding != null) {
207             format.put(OutputKeys.ENCODING, encoding);
208             buffer.append(";encoding=").append(encoding);
209         }
210         if (indent != null) {
211             format.put(OutputKeys.INDENT, indent);
212             buffer.append(";indent=").append(indent);
213         }
214         if (mediaType != null) {
215             format.put(OutputKeys.MEDIA_TYPE, mediaType);
216             buffer.append(";media-type=").append(mediaType);
217         }
218         if (method != null) {
219             format.put(OutputKeys.METHOD, method);
220             buffer.append(";method=").append(method);
221         }
222         if (omitXMLDeclaration != null) {
223             format.put(OutputKeys.OMIT_XML_DECLARATION, omitXMLDeclaration);
224             buffer.append(";omit-xml-declaration=").append(omitXMLDeclaration);
225         }
226         if (standAlone != null) {
227             format.put(OutputKeys.STANDALONE, standAlone);
228             buffer.append(";standalone=").append(standAlone);
229         }
230         if (version != null) {
231             format.put(OutputKeys.VERSION, version);
232             buffer.append(";version=").append(version);
233         }
234
235         if ( buffer.length() > 0 ) {
236             this.cachingKey = buffer.toString();
237         }
238
239         String JavaDoc tFactoryClass = conf.getChild("transformer-factory").getValue(null);
240         if (tFactoryClass != null) {
241             try {
242                 this.tfactory = (SAXTransformerFactory JavaDoc) ClassUtils.newInstance(tFactoryClass);
243                 if (getLogger().isDebugEnabled()) {
244                     getLogger().debug("Using transformer factory " + tFactoryClass);
245                 }
246             } catch (Exception JavaDoc e) {
247                 throw new ConfigurationException("Cannot load transformer factory " + tFactoryClass, e);
248             }
249         } else {
250             // Standard TrAX behaviour
251
this.tfactory = (SAXTransformerFactory JavaDoc) TransformerFactory.newInstance();
252         }
253         tfactory.setErrorListener(new TraxErrorHandler(getLogger()));
254
255         // Check if we need namespace as attributes.
256
try {
257             if (needsNamespacesAsAttributes()) {
258                 // Setup a correction pipe
259
this.namespacePipe = new NamespaceAsAttributes();
260                 this.namespacePipe.enableLogging(getLogger());
261             }
262         } catch (Exception JavaDoc e) {
263             getLogger().warn("Cannot know if transformer needs namespaces attributes - assuming NO.", e);
264         }
265     }
266
267     /**
268      * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
269      */

270     public void recycle() {
271         super.recycle();
272
273         if (this.namespacePipe != null) {
274             this.namespacePipe.recycle();
275         }
276     }
277
278     /**
279      * Generate the unique key.
280      * This key must be unique inside the space of this component.
281      * This method must be invoked before the generateValidity() method.
282      *
283      * @return The generated key or <code>0</code> if the component
284      * is currently not cacheable.
285      */

286     public java.io.Serializable JavaDoc getKey() {
287         return this.cachingKey;
288     }
289
290     /**
291      * Generate the validity object.
292      * Before this method can be invoked the generateKey() method
293      * must be invoked.
294      *
295      * @return The generated validity object or <code>null</code> if the
296      * component is currently not cacheable.
297      */

298     public SourceValidity getValidity() {
299         return NOPValidity.SHARED_INSTANCE;
300     }
301
302     /**
303      * Checks if the used Trax implementation correctly handles namespaces set using
304      * <code>startPrefixMapping()</code>, but wants them also as 'xmlns:' attributes.
305      * <p>
306      * The check consists in sending SAX events representing a minimal namespaced document
307      * with namespaces defined only with calls to <code>startPrefixMapping</code> (no
308      * xmlns:xxx attributes) and check if they are present in the resulting text.
309      */

310     protected boolean needsNamespacesAsAttributes() throws Exception JavaDoc {
311
312         SAXTransformerFactory JavaDoc factory = getTransformerFactory();
313
314         Boolean JavaDoc cacheValue = (Boolean JavaDoc) needsNamespaceCache.get(factory.getClass().getName());
315         if (cacheValue != null) {
316             return cacheValue.booleanValue();
317         } else {
318             // Serialize a minimal document to check how namespaces are handled.
319
StringWriter JavaDoc writer = new StringWriter JavaDoc();
320
321             String JavaDoc uri = "namespaceuri";
322             String JavaDoc prefix = "nsp";
323             String JavaDoc check = "xmlns:" + prefix + "='" + uri + "'";
324
325             TransformerHandler JavaDoc handler = this.getTransformerHandler();
326
327             handler.getTransformer().setOutputProperties(format);
328             handler.setResult(new StreamResult JavaDoc(writer));
329
330             // Output a single element
331
handler.startDocument();
332             handler.startPrefixMapping(prefix, uri);
333             handler.startElement(uri, "element", "", XMLUtils.EMPTY_ATTRIBUTES);
334             handler.endPrefixMapping(prefix);
335             handler.endDocument();
336
337             String JavaDoc text = writer.toString();
338
339             // Check if the namespace is there (replace " by ' to be sure of what we search in)
340
boolean needsIt = (text.replace('"', '\'').indexOf(check) == -1);
341
342             String JavaDoc msg = needsIt ? " needs namespace attributes (will be slower)." : " handles correctly namespaces.";
343
344             getLogger().debug("Trax handler " + handler.getClass().getName() + msg);
345
346             needsNamespaceCache.put(factory.getClass().getName(), BooleanUtils.toBooleanObject(needsIt));
347
348             return needsIt;
349         }
350     }
351
352     //--------------------------------------------------------------------------------------------
353

354     /**
355      * A pipe that ensures that all namespace prefixes are also present as
356      * 'xmlns:' attributes. This used to circumvent Xalan's serialization behaviour
357      * which is to ignore namespaces if they're not present as 'xmlns:xxx' attributes.
358      */

359     public static class NamespaceAsAttributes extends AbstractXMLPipe {
360
361         /**
362          * The prefixes of startPrefixMapping() declarations for the coming element.
363          */

364         private List JavaDoc prefixList = new ArrayList JavaDoc();
365
366         /**
367          * The URIs of startPrefixMapping() declarations for the coming element.
368          */

369         private List JavaDoc uriList = new ArrayList JavaDoc();
370
371         /**
372          * Maps of URI<->prefix mappings. Used to work around a bug in the Xalan
373          * serializer.
374          */

375         private Map JavaDoc uriToPrefixMap = new HashMap JavaDoc();
376         private Map JavaDoc prefixToUriMap = new HashMap JavaDoc();
377
378         /**
379          * True if there has been some startPrefixMapping() for the coming element.
380          */

381         private boolean hasMappings = false;
382
383         public void startDocument() throws SAXException JavaDoc {
384             // Cleanup
385
this.uriToPrefixMap.clear();
386             this.prefixToUriMap.clear();
387             clearMappings();
388             super.startDocument();
389         }
390
391         /**
392          * Track mappings to be able to add <code>xmlns:</code> attributes
393          * in <code>startElement()</code>.
394          */

395         public void startPrefixMapping(String JavaDoc prefix, String JavaDoc uri) throws SAXException JavaDoc {
396             // Store the mappings to reconstitute xmlns:attributes
397
// except prefixes starting with "xml": these are reserved
398
// VG: (uri != null) fixes NPE in startElement
399
if (uri != null && !prefix.startsWith("xml")) {
400                 this.hasMappings = true;
401                 this.prefixList.add(prefix);
402                 this.uriList.add(uri);
403
404                 // append the prefix colon now, in order to save concatenations later, but
405
// only for non-empty prefixes.
406
if (prefix.length() > 0) {
407                     this.uriToPrefixMap.put(uri, prefix + ":");
408                 } else {
409                     this.uriToPrefixMap.put(uri, prefix);
410                 }
411
412                 this.prefixToUriMap.put(prefix, uri);
413             }
414             super.startPrefixMapping(prefix, uri);
415         }
416
417         /**
418          * Ensure all namespace declarations are present as <code>xmlns:</code> attributes
419          * and add those needed before calling superclass. This is a workaround for a Xalan bug
420          * (at least in version 2.0.1) : <code>org.apache.xalan.serialize.SerializerToXML</code>
421          * ignores <code>start/endPrefixMapping()</code>.
422          */

423         public void startElement(String JavaDoc eltUri, String JavaDoc eltLocalName, String JavaDoc eltQName, Attributes JavaDoc attrs)
424                 throws SAXException JavaDoc {
425
426             // try to restore the qName. The map already contains the colon
427
if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) {
428                 eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName;
429             }
430             if (this.hasMappings) {
431                 // Add xmlns* attributes where needed
432

433                 // New Attributes if we have to add some.
434
AttributesImpl JavaDoc newAttrs = null;
435
436                 int mappingCount = this.prefixList.size();
437                 int attrCount = attrs.getLength();
438
439                 for (int mapping = 0; mapping < mappingCount; mapping++) {
440
441                     // Build infos for this namespace
442
String JavaDoc uri = (String JavaDoc) this.uriList.get(mapping);
443                     String JavaDoc prefix = (String JavaDoc) this.prefixList.get(mapping);
444                     String JavaDoc qName = prefix.equals("") ? "xmlns" : ("xmlns:" + prefix);
445
446                     // Search for the corresponding xmlns* attribute
447
boolean found = false;
448                     for (int attr = 0; attr < attrCount; attr++) {
449                         if (qName.equals(attrs.getQName(attr))) {
450                             // Check if mapping and attribute URI match
451
if (!uri.equals(attrs.getValue(attr))) {
452                                 getLogger().error("URI in prefix mapping and attribute do not match : '"
453                                                   + uri + "' - '" + attrs.getURI(attr) + "'");
454                                 throw new SAXException JavaDoc("URI in prefix mapping and attribute do not match");
455                             }
456                             found = true;
457                             break;
458                         }
459                     }
460
461                     if (!found) {
462                         // Need to add this namespace
463
if (newAttrs == null) {
464                             // Need to test if attrs is empty or we go into an infinite loop...
465
// Well know SAX bug which I spent 3 hours to remind of :-(
466
if (attrCount == 0) {
467                                 newAttrs = new AttributesImpl JavaDoc();
468                             } else {
469                                 newAttrs = new AttributesImpl JavaDoc(attrs);
470                             }
471                         }
472
473                         if (prefix.equals("")) {
474                             newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, "xmlns", "xmlns", "CDATA", uri);
475                         } else {
476                             newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, prefix, qName, "CDATA", uri);
477                         }
478                     }
479                 } // end for mapping
480

481                 // Cleanup for the next element
482
clearMappings();
483
484                 // Start element with new attributes, if any
485
super.startElement(eltUri, eltLocalName, eltQName, newAttrs == null ? attrs : newAttrs);
486             } else {
487                 // Normal job
488
super.startElement(eltUri, eltLocalName, eltQName, attrs);
489             }
490         }
491
492
493         /**
494          * Receive notification of the end of an element.
495          * Try to restore the element qName.
496          */

497         public void endElement(String JavaDoc eltUri, String JavaDoc eltLocalName, String JavaDoc eltQName) throws SAXException JavaDoc {
498             // try to restore the qName. The map already contains the colon
499
if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) {
500                 eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName;
501             }
502             super.endElement(eltUri, eltLocalName, eltQName);
503         }
504
505         /**
506          * End the scope of a prefix-URI mapping:
507          * remove entry from mapping tables.
508          */

509         public void endPrefixMapping(String JavaDoc prefix) throws SAXException JavaDoc {
510             // remove mappings for xalan-bug-workaround.
511
// Unfortunately, we're not passed the uri, but the prefix here,
512
// so we need to maintain maps in both directions.
513
if (this.prefixToUriMap.containsKey(prefix)) {
514                 this.uriToPrefixMap.remove(this.prefixToUriMap.get(prefix));
515                 this.prefixToUriMap.remove(prefix);
516             }
517
518             if (hasMappings) {
519                 // most of the time, start/endPrefixMapping calls have an element event between them,
520
// which will clear the hasMapping flag and so this code will only be executed in the
521
// rather rare occasion when there are start/endPrefixMapping calls with no element
522
// event in between. If we wouldn't remove the items from the prefixList and uriList here,
523
// the namespace would be incorrectly declared on the next element following the
524
// endPrefixMapping call.
525
int pos = prefixList.lastIndexOf(prefix);
526                 if (pos != -1) {
527                     prefixList.remove(pos);
528                     uriList.remove(pos);
529                 }
530             }
531
532             super.endPrefixMapping(prefix);
533         }
534
535         /**
536          *
537          */

538         public void endDocument() throws SAXException JavaDoc {
539             // Cleanup
540
this.uriToPrefixMap.clear();
541             this.prefixToUriMap.clear();
542             clearMappings();
543             super.endDocument();
544         }
545
546         private void clearMappings() {
547             this.hasMappings = false;
548             this.prefixList.clear();
549             this.uriList.clear();
550         }
551     }
552
553     /* (non-Javadoc)
554      * @see org.xml.sax.ContentHandler#endDocument()
555      */

556     public void endDocument() throws SAXException JavaDoc {
557         super.endDocument();
558
559         // if (this.output != null) {
560
// try {
561
// this.output.flush();
562
// } catch (IOException ignored) {
563
// }
564
// }
565
}
566
567 }
568
Popular Tags