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