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    
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    /**
061     * A helper class to transform to and from various JAXB types such as {@link Source} and {@link Document}
062     *
063     * @version $Revision: 766610 $
064     */
065    @Converter
066    public class XmlConverter {
067        public static final String DEFAULT_CHARSET_PROPERTY = "org.apache.camel.default.charset";
068    
069        public static String defaultCharset = ObjectHelper.getSystemProperty(DEFAULT_CHARSET_PROPERTY, "UTF-8");
070    
071        /*
072         * When converting a DOM tree to a SAXSource, we try to use Xalan internal DOM parser if
073         * available. Else, transform the DOM tree to a String and build a SAXSource on top of it.
074         */
075        private static final Class DOM_TO_SAX_CLASS;
076    
077        private DocumentBuilderFactory documentBuilderFactory;
078        private TransformerFactory transformerFactory;
079    
080        static {
081            Class cl = null;
082            try {
083                cl = ObjectHelper.loadClass("org.apache.xalan.xsltc.trax.DOM2SAX");
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 byte[] to a Source
133         */
134        @Converter
135        public BytesSource toSource(byte[] data) {
136            return new BytesSource(data);
137        }
138    
139    
140        /**
141         * Converts the given String to a Source
142         */
143        @Converter
144        public StringSource toSource(String data) {
145            return new StringSource(data);
146        }
147    
148        /**
149         * Converts the given Document to a Source
150         */
151        @Converter
152        public DOMSource toSource(Document document) {
153            return new DOMSource(document);
154        }
155    
156        /**
157         * Converts the given Node to a Source
158         */
159        @Converter
160        public Source toSource(Node node) {
161            return new DOMSource(node);
162        }
163    
164        /**
165         * Converts the given input Source into text
166         */
167        @Converter
168        public String toString(Source source) throws TransformerException {
169            if (source == null) {
170                return null;
171            } else if (source instanceof StringSource) {
172                return ((StringSource) source).getText();
173            } else if (source instanceof BytesSource) {
174                return new String(((BytesSource) source).getData());
175            } else {
176                StringWriter buffer = new StringWriter();
177                toResult(source, new StreamResult(buffer));
178                return buffer.toString();
179            }
180        }
181    
182        /**
183         * Converts the given input Source into bytes
184         */
185        @Converter
186        public byte[] toByteArray(Source source, Exchange exchange) throws TransformerException {
187            String answer = toString(source);
188            if (exchange != null) {
189                return exchange.getContext().getTypeConverter().convertTo(byte[].class, exchange, answer);
190            } else {
191                return answer.getBytes();
192            }
193        }
194    
195        /**
196         * Converts the given input Node into text
197         */
198        @Converter
199        public String toString(Node node) throws TransformerException {
200            return toString(new DOMSource(node));
201        }
202    
203        /**
204         * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
205         * supported (making it easy to derive from this class to add new kinds of conversion).
206         */
207        @Converter
208        public DOMSource toDOMSource(Source source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
209            if (source instanceof DOMSource) {
210                return (DOMSource) source;
211            } else if (source instanceof SAXSource) {
212                return toDOMSourceFromSAX((SAXSource) source);
213            } else if (source instanceof StreamSource) {
214                return toDOMSourceFromStream((StreamSource) source);
215            } else {
216                return null;
217            }
218        }
219    
220        /**
221         * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
222         * supported (making it easy to derive from this class to add new kinds of conversion).
223         */
224        @Converter
225        public DOMSource toDOMSource(String text) throws ParserConfigurationException, IOException, SAXException, TransformerException {
226            Source source = toSource(text);
227            if (source != null) {
228                return toDOMSourceFromStream((StreamSource) source);
229            } else {
230                return null;
231            }
232        }
233    
234        /**
235         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
236         * supported (making it easy to derive from this class to add new kinds of conversion).
237         */
238        @Converter
239        public SAXSource toSAXSource(String source) throws IOException, SAXException, TransformerException {
240            return toSAXSource(toSource(source));
241        }
242    
243        /**
244         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
245         * supported (making it easy to derive from this class to add new kinds of conversion).
246         */
247        @Converter
248        public SAXSource toSAXSource(InputStream source) throws IOException, SAXException, TransformerException {
249            return toSAXSource(toStreamSource(source));
250        }
251    
252        /**
253         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
254         * supported (making it easy to derive from this class to add new kinds of conversion).
255         */
256        @Converter
257        public SAXSource toSAXSource(Source source) throws IOException, SAXException, TransformerException {
258            if (source instanceof SAXSource) {
259                return (SAXSource) source;
260            } else if (source instanceof DOMSource) {
261                return toSAXSourceFromDOM((DOMSource) source);
262            } else if (source instanceof StreamSource) {
263                return toSAXSourceFromStream((StreamSource) source);
264            } else {
265                return null;
266            }
267        }
268    
269        @Converter
270        public StreamSource toStreamSource(Source source) throws TransformerException {
271            if (source instanceof StreamSource) {
272                return (StreamSource) source;
273            } else if (source instanceof DOMSource) {
274                return toStreamSourceFromDOM((DOMSource) source);
275            } else if (source instanceof SAXSource) {
276                return toStreamSourceFromSAX((SAXSource) source);
277            } else {
278                return null;
279            }
280        }
281    
282        @Converter
283        public StreamSource toStreamSource(InputStream in) throws TransformerException {
284            if (in != null) {
285                return new StreamSource(in);
286            }
287            return null;
288        }
289    
290        @Converter
291        public StreamSource toStreamSource(Reader in) throws TransformerException {
292            if (in != null) {
293                return new StreamSource(in);
294            }
295            return null;
296        }
297    
298        @Converter
299        public StreamSource toStreamSource(File in) throws TransformerException {
300            if (in != null) {
301                return new StreamSource(in);
302            }
303            return null;
304        }
305    
306        @Converter
307        public StreamSource toStreamSource(byte[] in, Exchange exchange) throws TransformerException {
308            if (in != null) {
309                InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, in);
310                return new StreamSource(is);
311            }
312            return null;
313        }
314    
315        @Converter
316        public StreamSource toStreamSource(ByteBuffer in, Exchange exchange) throws TransformerException {
317            if (in != null) {
318                InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, in);
319                return new StreamSource(is);
320            }
321            return null;
322        }
323    
324        @Converter
325        public StreamSource toStreamSourceFromSAX(SAXSource source) throws TransformerException {
326            InputSource inputSource = source.getInputSource();
327            if (inputSource != null) {
328                if (inputSource.getCharacterStream() != null) {
329                    return new StreamSource(inputSource.getCharacterStream());
330                }
331                if (inputSource.getByteStream() != null) {
332                    return new StreamSource(inputSource.getByteStream());
333                }
334            }
335            String result = toString(source);
336            return new StringSource(result);
337        }
338    
339        @Converter
340        public StreamSource toStreamSourceFromDOM(DOMSource source) throws TransformerException {
341            String result = toString(source);
342            return new StringSource(result);
343        }
344    
345        @Converter
346        public SAXSource toSAXSourceFromStream(StreamSource source) {
347            InputSource inputSource;
348            if (source.getReader() != null) {
349                inputSource = new InputSource(source.getReader());
350            } else {
351                inputSource = new InputSource(source.getInputStream());
352            }
353            inputSource.setSystemId(source.getSystemId());
354            inputSource.setPublicId(source.getPublicId());
355            return new SAXSource(inputSource);
356        }
357    
358        @Converter
359        public Reader toReaderFromSource(Source src) throws TransformerException {
360            StreamSource stSrc = toStreamSource(src);
361            Reader r = stSrc.getReader();
362            if (r == null) {
363                r = new InputStreamReader(stSrc.getInputStream());
364            }
365            return r;
366        }
367    
368        @Converter
369        public DOMSource toDOMSourceFromStream(StreamSource source) throws ParserConfigurationException, IOException, SAXException {
370            DocumentBuilder builder = createDocumentBuilder();
371            String systemId = source.getSystemId();
372            Document document = null;
373            Reader reader = source.getReader();
374            if (reader != null) {
375                document = builder.parse(new InputSource(reader));
376            } else {
377                InputStream inputStream = source.getInputStream();
378                if (inputStream != null) {
379                    InputSource inputsource = new InputSource(inputStream);
380                    inputsource.setSystemId(systemId);
381                    document = builder.parse(inputsource);
382                } else {
383                    throw new IOException("No input stream or reader available");
384                }
385            }
386            return new DOMSource(document, systemId);
387        }
388    
389        @Converter
390        public SAXSource toSAXSourceFromDOM(DOMSource source) throws TransformerException {
391            if (DOM_TO_SAX_CLASS != null) {
392                try {
393                    Constructor cns = DOM_TO_SAX_CLASS.getConstructor(Node.class);
394                    XMLReader converter = (XMLReader) cns.newInstance(source.getNode());
395                    return new SAXSource(converter, new InputSource());
396                } catch (Exception e) {
397                    throw new TransformerException(e);
398                }
399            } else {
400                String str = toString(source);
401                StringReader reader = new StringReader(str);
402                return new SAXSource(new InputSource(reader));
403            }
404        }
405    
406        @Converter
407        public DOMSource toDOMSourceFromSAX(SAXSource source) throws IOException, SAXException, ParserConfigurationException, TransformerException {
408            return new DOMSource(toDOMNodeFromSAX(source));
409        }
410    
411        @Converter
412        public Node toDOMNodeFromSAX(SAXSource source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
413            DOMResult result = new DOMResult();
414            toResult(source, result);
415            return result.getNode();
416        }
417    
418        /**
419         * Converts the given TRaX Source into a W3C DOM node
420         */
421        @Converter
422        public Node toDOMNode(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
423            DOMSource domSrc = toDOMSource(source);
424            return domSrc != null ? domSrc.getNode() :  null;
425        }
426    
427        /**
428         * Create a DOM element from the given source.
429         */
430        @Converter
431        public Element toDOMElement(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
432            Node node = toDOMNode(source);
433            return toDOMElement(node);
434        }
435    
436        /**
437         * Create a DOM element from the DOM node.
438         * Simply cast if the node is an Element, or
439         * return the root element if it is a Document.
440         */
441        @Converter
442        public Element toDOMElement(Node node) throws TransformerException {
443            // If the node is an document, return the root element
444            if (node instanceof Document) {
445                return ((Document) node).getDocumentElement();
446            // If the node is an element, just cast it
447            } else if (node instanceof Element) {
448                return (Element) node;
449            // Other node types are not handled
450            } else {
451                throw new TransformerException("Unable to convert DOM node to an Element");
452            }
453        }
454    
455        /**
456         * Converts the given data to a DOM document
457         *
458         * @param data is the data to be parsed
459         * @return the parsed document
460         */
461        @Converter
462        public Document toDOMDocument(byte[] data) throws IOException, SAXException, ParserConfigurationException {
463            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
464            return documentBuilder.parse(new ByteArrayInputStream(data));
465        }
466    
467        /**
468         * Converts the given {@link InputStream} to a DOM document
469         *
470         * @param in is the data to be parsed
471         * @return the parsed document
472         */
473        @Converter
474        public Document toDOMDocument(InputStream in) throws IOException, SAXException, ParserConfigurationException {
475            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
476            return documentBuilder.parse(in);
477        }
478    
479        /**
480         * Converts the given {@link InputStream} to a DOM document
481         *
482         * @param in is the data to be parsed
483         * @return the parsed document
484         */
485        @Converter
486        public Document toDOMDocument(Reader in) throws IOException, SAXException, ParserConfigurationException {
487            return toDOMDocument(new InputSource(in));
488        }
489    
490        /**
491         * Converts the given {@link InputSource} to a DOM document
492         *
493         * @param in is the data to be parsed
494         * @return the parsed document
495         */
496        @Converter
497        public Document toDOMDocument(InputSource in) throws IOException, SAXException, ParserConfigurationException {
498            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
499            return documentBuilder.parse(in);
500        }
501    
502        /**
503         * Converts the given {@link String} to a DOM document
504         *
505         * @param text is the data to be parsed
506         * @return the parsed document
507         */
508        @Converter
509        public Document toDOMDocument(String text) throws IOException, SAXException, ParserConfigurationException {
510            return toDOMDocument(new StringReader(text));
511        }
512    
513        /**
514         * Converts the given {@link File} to a DOM document
515         *
516         * @param file is the data to be parsed
517         * @return the parsed document
518         */
519        @Converter
520        public Document toDOMDocument(File file) throws IOException, SAXException, ParserConfigurationException {
521            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
522            return documentBuilder.parse(file);
523        }
524    
525        /**
526         * Create a DOM document from the given source.
527         */
528        @Converter
529        public Document toDOMDocument(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
530            Node node = toDOMNode(source);
531            return toDOMDocument(node);
532        }
533    
534        /**
535         * Create a DOM document from the given Node.
536         * If the node is an document, just cast it,
537         * if the node is an root element, retrieve its
538         * owner element or create a new document and import
539         * the node.
540         */
541        @Converter
542        public Document toDOMDocument(Node node) throws ParserConfigurationException, TransformerException {
543            // If the node is the document, just cast it
544            if (node instanceof Document) {
545                return (Document) node;
546            // If the node is an element
547            } else if (node instanceof Element) {
548                Element elem = (Element) node;
549                // If this is the root element, return its owner document
550                if (elem.getOwnerDocument().getDocumentElement() == elem) {
551                    return elem.getOwnerDocument();
552                // else, create a new doc and copy the element inside it
553                } else {
554                    Document doc = createDocument();
555                    doc.appendChild(doc.importNode(node, true));
556                    return doc;
557                }
558            // other element types are not handled
559            } else {
560                throw new TransformerException("Unable to convert DOM node to a Document");
561            }
562        }
563    
564        // Properties
565        //-------------------------------------------------------------------------
566        public DocumentBuilderFactory getDocumentBuilderFactory() {
567            if (documentBuilderFactory == null) {
568                documentBuilderFactory = createDocumentBuilderFactory();
569            }
570            return documentBuilderFactory;
571        }
572    
573        public void setDocumentBuilderFactory(DocumentBuilderFactory documentBuilderFactory) {
574            this.documentBuilderFactory = documentBuilderFactory;
575        }
576    
577    
578        // Helper methods
579        //-------------------------------------------------------------------------
580        public DocumentBuilderFactory createDocumentBuilderFactory() {
581            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
582            factory.setNamespaceAware(true);
583            factory.setIgnoringElementContentWhitespace(true);
584            factory.setIgnoringComments(true);
585            return factory;
586        }
587    
588    
589        public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
590            DocumentBuilderFactory factory = getDocumentBuilderFactory();
591            return factory.newDocumentBuilder();
592        }
593    
594        public Document createDocument() throws ParserConfigurationException {
595            DocumentBuilder builder = createDocumentBuilder();
596            return builder.newDocument();
597        }
598    
599        public TransformerFactory getTransformerFactory() {
600            if (transformerFactory == null) {
601                transformerFactory = createTransformerFactory();
602            }
603            return transformerFactory;
604        }
605    
606        public void setTransformerFactory(TransformerFactory transformerFactory) {
607            this.transformerFactory = transformerFactory;
608        }
609    
610        public Transformer createTransfomer() throws TransformerConfigurationException {
611            TransformerFactory factory = getTransformerFactory();
612            return factory.newTransformer();
613        }
614    
615        public TransformerFactory createTransformerFactory() {
616            return TransformerFactory.newInstance();
617        }
618    
619    }