001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.converter.jaxp;
018    
019    import java.io.ByteArrayInputStream;
020    import java.io.File;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.io.InputStreamReader;
024    import java.io.Reader;
025    import java.io.StringReader;
026    import java.io.StringWriter;
027    import java.lang.reflect.Constructor;
028    import java.nio.ByteBuffer;
029    import java.util.Properties;
030    
031    import javax.xml.parsers.DocumentBuilder;
032    import javax.xml.parsers.DocumentBuilderFactory;
033    import javax.xml.parsers.ParserConfigurationException;
034    import javax.xml.transform.OutputKeys;
035    import javax.xml.transform.Result;
036    import javax.xml.transform.Source;
037    import javax.xml.transform.Transformer;
038    import javax.xml.transform.TransformerConfigurationException;
039    import javax.xml.transform.TransformerException;
040    import javax.xml.transform.TransformerFactory;
041    import javax.xml.transform.dom.DOMResult;
042    import javax.xml.transform.dom.DOMSource;
043    import javax.xml.transform.sax.SAXSource;
044    import javax.xml.transform.stream.StreamResult;
045    import javax.xml.transform.stream.StreamSource;
046    
047    import org.w3c.dom.Document;
048    import org.w3c.dom.Element;
049    import org.w3c.dom.Node;
050    import org.w3c.dom.NodeList;
051    import org.xml.sax.InputSource;
052    import org.xml.sax.SAXException;
053    import org.xml.sax.XMLReader;
054    
055    import org.apache.camel.Converter;
056    import org.apache.camel.Exchange;
057    import org.apache.camel.util.ObjectHelper;
058    
059    /**
060     * A helper class to transform to and from various JAXB types such as {@link Source} and {@link Document}
061     *
062     * @version $Revision: 900466 $
063     */
064    @Converter
065    public class XmlConverter {
066        public static final String DEFAULT_CHARSET_PROPERTY = "org.apache.camel.default.charset";
067    
068        public static String defaultCharset = ObjectHelper.getSystemProperty(DEFAULT_CHARSET_PROPERTY, "UTF-8");
069    
070        /*
071         * When converting a DOM tree to a SAXSource, we try to use Xalan internal DOM parser if
072         * available. Else, transform the DOM tree to a String and build a SAXSource on top of it.
073         */
074        private static final Class<?> DOM_TO_SAX_CLASS;
075    
076        private DocumentBuilderFactory documentBuilderFactory;
077        private TransformerFactory transformerFactory;
078    
079        static {
080            Class<?> cl = null;
081            try {
082                // will not warn the user if the class could not be found
083                cl = ObjectHelper.loadClass("org.apache.xalan.xsltc.trax.DOM2SAX", XmlConverter.class.getClassLoader(), false);
084            } catch (Exception e) {
085                // ignore
086            }
087            DOM_TO_SAX_CLASS = cl;
088        }
089    
090    
091        public XmlConverter() {
092        }
093    
094        public XmlConverter(DocumentBuilderFactory documentBuilderFactory) {
095            this.documentBuilderFactory = documentBuilderFactory;
096        }
097    
098        /**
099         * Returns the default set of output properties for conversions.
100         */
101        public Properties defaultOutputProperties() {
102            Properties properties = new Properties();
103            properties.put(OutputKeys.ENCODING, defaultCharset);
104            properties.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
105            return properties;
106        }
107    
108        /**
109         * Converts the given input Source into the required result
110         */
111        public void toResult(Source source, Result result) throws TransformerException {
112            toResult(source, result, defaultOutputProperties());
113        }
114    
115        /**
116         * Converts the given input Source into the required result
117         */
118        public void toResult(Source source, Result result, Properties outputProperties) throws TransformerException {
119            if (source == null) {
120                return;
121            }
122    
123            Transformer transformer = createTransfomer();
124            if (transformer == null) {
125                throw new TransformerException("Could not create a transformer - JAXP is misconfigured!");
126            }
127            transformer.setOutputProperties(outputProperties);
128            transformer.transform(source, result);
129        }
130    
131        /**
132         * Converts the given NodeList to a boolean
133         */
134        @Converter
135        public Boolean toBoolean(NodeList list) {
136            return list.getLength() > 0;
137        }
138    
139        /**
140         * Converts the given byte[] to a Source
141         */
142        @Converter
143        public BytesSource toSource(byte[] data) {
144            return new BytesSource(data);
145        }
146    
147    
148        /**
149         * Converts the given String to a Source
150         */
151        @Converter
152        public StringSource toSource(String data) {
153            return new StringSource(data);
154        }
155    
156        /**
157         * Converts the given Document to a Source
158         */
159        @Converter
160        public DOMSource toSource(Document document) {
161            return new DOMSource(document);
162        }
163    
164        /**
165         * Converts the given Node to a Source
166         */
167        @Converter
168        public Source toSource(Node node) {
169            return new DOMSource(node);
170        }
171    
172        /**
173         * Converts the given input Source into text
174         */
175        @Converter
176        public String toString(Source source) throws TransformerException {
177            if (source == null) {
178                return null;
179            } else if (source instanceof StringSource) {
180                return ((StringSource) source).getText();
181            } else if (source instanceof BytesSource) {
182                return new String(((BytesSource) source).getData());
183            } else {
184                StringWriter buffer = new StringWriter();
185                toResult(source, new StreamResult(buffer));
186                return buffer.toString();
187            }
188        }
189    
190        /**
191         * Converts the given input Source into bytes
192         */
193        @Converter
194        public byte[] toByteArray(Source source, Exchange exchange) throws TransformerException {
195            String answer = toString(source);
196            if (exchange != null) {
197                return exchange.getContext().getTypeConverter().convertTo(byte[].class, exchange, answer);
198            } else {
199                return answer.getBytes();
200            }
201        }
202    
203        /**
204         * Converts the given input Node into text
205         */
206        @Converter
207        public String toString(Node node) throws TransformerException {
208            return toString(new DOMSource(node));
209        }
210    
211        /**
212         * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
213         * supported (making it easy to derive from this class to add new kinds of conversion).
214         */
215        @Converter
216        public DOMSource toDOMSource(Source source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
217            if (source instanceof DOMSource) {
218                return (DOMSource) source;
219            } else if (source instanceof SAXSource) {
220                return toDOMSourceFromSAX((SAXSource) source);
221            } else if (source instanceof StreamSource) {
222                return toDOMSourceFromStream((StreamSource) source);
223            } else {
224                return null;
225            }
226        }
227    
228        /**
229         * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
230         * supported (making it easy to derive from this class to add new kinds of conversion).
231         */
232        @Converter
233        public DOMSource toDOMSource(String text) throws ParserConfigurationException, IOException, SAXException, TransformerException {
234            Source source = toSource(text);
235            if (source != null) {
236                return toDOMSourceFromStream((StreamSource) source);
237            } else {
238                return null;
239            }
240        }
241    
242        /**
243         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
244         * supported (making it easy to derive from this class to add new kinds of conversion).
245         */
246        @Converter
247        public SAXSource toSAXSource(String source) throws IOException, SAXException, TransformerException {
248            return toSAXSource(toSource(source));
249        }
250    
251        /**
252         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
253         * supported (making it easy to derive from this class to add new kinds of conversion).
254         */
255        @Converter
256        public SAXSource toSAXSource(InputStream source) throws IOException, SAXException, TransformerException {
257            return toSAXSource(toStreamSource(source));
258        }
259    
260        /**
261         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
262         * supported (making it easy to derive from this class to add new kinds of conversion).
263         */
264        @Converter
265        public SAXSource toSAXSource(Source source) throws IOException, SAXException, TransformerException {
266            if (source instanceof SAXSource) {
267                return (SAXSource) source;
268            } else if (source instanceof DOMSource) {
269                return toSAXSourceFromDOM((DOMSource) source);
270            } else if (source instanceof StreamSource) {
271                return toSAXSourceFromStream((StreamSource) source);
272            } else {
273                return null;
274            }
275        }
276    
277        @Converter
278        public StreamSource toStreamSource(Source source) throws TransformerException {
279            if (source instanceof StreamSource) {
280                return (StreamSource) source;
281            } else if (source instanceof DOMSource) {
282                return toStreamSourceFromDOM((DOMSource) source);
283            } else if (source instanceof SAXSource) {
284                return toStreamSourceFromSAX((SAXSource) source);
285            } else {
286                return null;
287            }
288        }
289    
290        @Converter
291        public StreamSource toStreamSource(InputStream in) throws TransformerException {
292            if (in != null) {
293                return new StreamSource(in);
294            }
295            return null;
296        }
297    
298        @Converter
299        public StreamSource toStreamSource(Reader in) throws TransformerException {
300            if (in != null) {
301                return new StreamSource(in);
302            }
303            return null;
304        }
305    
306        @Converter
307        public StreamSource toStreamSource(File in) throws TransformerException {
308            if (in != null) {
309                return new StreamSource(in);
310            }
311            return null;
312        }
313    
314        @Converter
315        public StreamSource toStreamSource(byte[] in, Exchange exchange) throws TransformerException {
316            if (in != null) {
317                InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, in);
318                return new StreamSource(is);
319            }
320            return null;
321        }
322    
323        @Converter
324        public StreamSource toStreamSource(ByteBuffer in, Exchange exchange) throws TransformerException {
325            if (in != null) {
326                InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, in);
327                return new StreamSource(is);
328            }
329            return null;
330        }
331    
332        @Converter
333        public StreamSource toStreamSourceFromSAX(SAXSource source) throws TransformerException {
334            InputSource inputSource = source.getInputSource();
335            if (inputSource != null) {
336                if (inputSource.getCharacterStream() != null) {
337                    return new StreamSource(inputSource.getCharacterStream());
338                }
339                if (inputSource.getByteStream() != null) {
340                    return new StreamSource(inputSource.getByteStream());
341                }
342            }
343            String result = toString(source);
344            return new StringSource(result);
345        }
346    
347        @Converter
348        public StreamSource toStreamSourceFromDOM(DOMSource source) throws TransformerException {
349            String result = toString(source);
350            return new StringSource(result);
351        }
352    
353        @Converter
354        public SAXSource toSAXSourceFromStream(StreamSource source) {
355            InputSource inputSource;
356            if (source.getReader() != null) {
357                inputSource = new InputSource(source.getReader());
358            } else {
359                inputSource = new InputSource(source.getInputStream());
360            }
361            inputSource.setSystemId(source.getSystemId());
362            inputSource.setPublicId(source.getPublicId());
363            return new SAXSource(inputSource);
364        }
365    
366        @Converter
367        public Reader toReaderFromSource(Source src) throws TransformerException {
368            StreamSource stSrc = toStreamSource(src);
369            Reader r = stSrc.getReader();
370            if (r == null) {
371                r = new InputStreamReader(stSrc.getInputStream());
372            }
373            return r;
374        }
375    
376        @Converter
377        public DOMSource toDOMSource(InputStream is) throws ParserConfigurationException, IOException, SAXException {
378            InputSource source = new InputSource(is);
379            String systemId = source.getSystemId();
380            DocumentBuilder builder = createDocumentBuilder();
381            Document document = builder.parse(source);
382            return new DOMSource(document, systemId);
383        }
384    
385        @Converter
386        public DOMSource toDOMSourceFromStream(StreamSource source) throws ParserConfigurationException, IOException, SAXException {
387            DocumentBuilder builder = createDocumentBuilder();
388            String systemId = source.getSystemId();
389            Document document = null;
390            Reader reader = source.getReader();
391            if (reader != null) {
392                document = builder.parse(new InputSource(reader));
393            } else {
394                InputStream inputStream = source.getInputStream();
395                if (inputStream != null) {
396                    InputSource inputsource = new InputSource(inputStream);
397                    inputsource.setSystemId(systemId);
398                    document = builder.parse(inputsource);
399                } else {
400                    throw new IOException("No input stream or reader available");
401                }
402            }
403            return new DOMSource(document, systemId);
404        }
405    
406        @Converter
407        public SAXSource toSAXSourceFromDOM(DOMSource source) throws TransformerException {
408            if (DOM_TO_SAX_CLASS != null) {
409                try {
410                    Constructor<?> cns = DOM_TO_SAX_CLASS.getConstructor(Node.class);
411                    XMLReader converter = (XMLReader) cns.newInstance(source.getNode());
412                    return new SAXSource(converter, new InputSource());
413                } catch (Exception e) {
414                    throw new TransformerException(e);
415                }
416            } else {
417                String str = toString(source);
418                StringReader reader = new StringReader(str);
419                return new SAXSource(new InputSource(reader));
420            }
421        }
422    
423        @Converter
424        public DOMSource toDOMSourceFromSAX(SAXSource source) throws IOException, SAXException, ParserConfigurationException, TransformerException {
425            return new DOMSource(toDOMNodeFromSAX(source));
426        }
427    
428        @Converter
429        public Node toDOMNodeFromSAX(SAXSource source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
430            DOMResult result = new DOMResult();
431            toResult(source, result);
432            return result.getNode();
433        }
434    
435        /**
436         * Converts the given TRaX Source into a W3C DOM node
437         */
438        @Converter
439        public Node toDOMNode(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
440            DOMSource domSrc = toDOMSource(source);
441            return domSrc != null ? domSrc.getNode() : null;
442        }
443    
444        /**
445         * Create a DOM element from the given source.
446         */
447        @Converter
448        public Element toDOMElement(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
449            Node node = toDOMNode(source);
450            return toDOMElement(node);
451        }
452    
453        /**
454         * Create a DOM element from the DOM node.
455         * Simply cast if the node is an Element, or
456         * return the root element if it is a Document.
457         */
458        @Converter
459        public Element toDOMElement(Node node) throws TransformerException {
460            // If the node is an document, return the root element
461            if (node instanceof Document) {
462                return ((Document) node).getDocumentElement();
463                // If the node is an element, just cast it
464            } else if (node instanceof Element) {
465                return (Element) node;
466                // Other node types are not handled
467            } else {
468                throw new TransformerException("Unable to convert DOM node to an Element");
469            }
470        }
471    
472        /**
473         * Converts the given data to a DOM document
474         *
475         * @param data is the data to be parsed
476         * @return the parsed document
477         */
478        @Converter
479        public Document toDOMDocument(byte[] data) throws IOException, SAXException, ParserConfigurationException {
480            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
481            return documentBuilder.parse(new ByteArrayInputStream(data));
482        }
483    
484        /**
485         * Converts the given {@link InputStream} to a DOM document
486         *
487         * @param in is the data to be parsed
488         * @return the parsed document
489         */
490        @Converter
491        public Document toDOMDocument(InputStream in) throws IOException, SAXException, ParserConfigurationException {
492            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
493            return documentBuilder.parse(in);
494        }
495    
496        /**
497         * Converts the given {@link InputStream} to a DOM document
498         *
499         * @param in is the data to be parsed
500         * @return the parsed document
501         */
502        @Converter
503        public Document toDOMDocument(Reader in) throws IOException, SAXException, ParserConfigurationException {
504            return toDOMDocument(new InputSource(in));
505        }
506    
507        /**
508         * Converts the given {@link InputSource} to a DOM document
509         *
510         * @param in is the data to be parsed
511         * @return the parsed document
512         */
513        @Converter
514        public Document toDOMDocument(InputSource in) throws IOException, SAXException, ParserConfigurationException {
515            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
516            return documentBuilder.parse(in);
517        }
518    
519        /**
520         * Converts the given {@link String} to a DOM document
521         *
522         * @param text is the data to be parsed
523         * @return the parsed document
524         */
525        @Converter
526        public Document toDOMDocument(String text) throws IOException, SAXException, ParserConfigurationException {
527            return toDOMDocument(new StringReader(text));
528        }
529    
530        /**
531         * Converts the given {@link File} to a DOM document
532         *
533         * @param file is the data to be parsed
534         * @return the parsed document
535         */
536        @Converter
537        public Document toDOMDocument(File file) throws IOException, SAXException, ParserConfigurationException {
538            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
539            return documentBuilder.parse(file);
540        }
541    
542        /**
543         * Create a DOM document from the given source.
544         */
545        @Converter
546        public Document toDOMDocument(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
547            Node node = toDOMNode(source);
548            return toDOMDocument(node);
549        }
550    
551        /**
552         * Create a DOM document from the given Node.
553         * If the node is an document, just cast it,
554         * if the node is an root element, retrieve its
555         * owner element or create a new document and import
556         * the node.
557         */
558        @Converter
559        public Document toDOMDocument(Node node) throws ParserConfigurationException, TransformerException {
560            // If the node is the document, just cast it
561            if (node instanceof Document) {
562                return (Document) node;
563                // If the node is an element
564            } else if (node instanceof Element) {
565                Element elem = (Element) node;
566                // If this is the root element, return its owner document
567                if (elem.getOwnerDocument().getDocumentElement() == elem) {
568                    return elem.getOwnerDocument();
569                    // else, create a new doc and copy the element inside it
570                } else {
571                    Document doc = createDocument();
572                    doc.appendChild(doc.importNode(node, true));
573                    return doc;
574                }
575                // other element types are not handled
576            } else {
577                throw new TransformerException("Unable to convert DOM node to a Document");
578            }
579        }
580    
581        @Converter
582        public InputStream toInputStrean(DOMSource source) throws TransformerException, IOException {
583            String s = toString(source);
584            return new ByteArrayInputStream(s.getBytes());
585        }
586    
587        @Converter
588        public InputStream toInputStrean(Document dom) throws TransformerException, IOException {
589            String s = toString(dom);
590            return new ByteArrayInputStream(s.getBytes());
591        }
592    
593        @Converter
594        public InputSource toInputSource(InputStream is) {
595            return new InputSource(is);
596        }
597    
598        // Properties
599        //-------------------------------------------------------------------------
600        public DocumentBuilderFactory getDocumentBuilderFactory() {
601            if (documentBuilderFactory == null) {
602                documentBuilderFactory = createDocumentBuilderFactory();
603            }
604            return documentBuilderFactory;
605        }
606    
607        public void setDocumentBuilderFactory(DocumentBuilderFactory documentBuilderFactory) {
608            this.documentBuilderFactory = documentBuilderFactory;
609        }
610    
611    
612        // Helper methods
613        //-------------------------------------------------------------------------
614        public DocumentBuilderFactory createDocumentBuilderFactory() {
615            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
616            factory.setNamespaceAware(true);
617            factory.setIgnoringElementContentWhitespace(true);
618            factory.setIgnoringComments(true);
619            return factory;
620        }
621    
622    
623        public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
624            DocumentBuilderFactory factory = getDocumentBuilderFactory();
625            return factory.newDocumentBuilder();
626        }
627    
628        public Document createDocument() throws ParserConfigurationException {
629            DocumentBuilder builder = createDocumentBuilder();
630            return builder.newDocument();
631        }
632    
633        public TransformerFactory getTransformerFactory() {
634            if (transformerFactory == null) {
635                transformerFactory = createTransformerFactory();
636            }
637            return transformerFactory;
638        }
639    
640        public void setTransformerFactory(TransformerFactory transformerFactory) {
641            this.transformerFactory = transformerFactory;
642        }
643    
644        public Transformer createTransfomer() throws TransformerConfigurationException {
645            TransformerFactory factory = getTransformerFactory();
646            return factory.newTransformer();
647        }
648    
649        public TransformerFactory createTransformerFactory() {
650            return TransformerFactory.newInstance();
651        }
652    
653    }