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: 13627 $
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 toDOMSource(InputStream is) throws ParserConfigurationException, IOException, SAXException {
373            InputSource source = new InputSource(is);
374            String systemId = source.getSystemId();
375            DocumentBuilder builder = createDocumentBuilder();
376            Document document = builder.parse(source);
377            return new DOMSource(document, systemId);
378        }
379    
380        @Converter
381        public DOMSource toDOMSourceFromStream(StreamSource source) throws ParserConfigurationException, IOException, SAXException {
382            DocumentBuilder builder = createDocumentBuilder();
383            String systemId = source.getSystemId();
384            Document document = null;
385            Reader reader = source.getReader();
386            if (reader != null) {
387                document = builder.parse(new InputSource(reader));
388            } else {
389                InputStream inputStream = source.getInputStream();
390                if (inputStream != null) {
391                    InputSource inputsource = new InputSource(inputStream);
392                    inputsource.setSystemId(systemId);
393                    document = builder.parse(inputsource);
394                } else {
395                    throw new IOException("No input stream or reader available");
396                }
397            }
398            return new DOMSource(document, systemId);
399        }
400    
401        @Converter
402        public SAXSource toSAXSourceFromDOM(DOMSource source) throws TransformerException {
403            if (DOM_TO_SAX_CLASS != null) {
404                try {
405                    Constructor cns = DOM_TO_SAX_CLASS.getConstructor(Node.class);
406                    XMLReader converter = (XMLReader) cns.newInstance(source.getNode());
407                    return new SAXSource(converter, new InputSource());
408                } catch (Exception e) {
409                    throw new TransformerException(e);
410                }
411            } else {
412                String str = toString(source);
413                StringReader reader = new StringReader(str);
414                return new SAXSource(new InputSource(reader));
415            }
416        }
417    
418        @Converter
419        public DOMSource toDOMSourceFromSAX(SAXSource source) throws IOException, SAXException, ParserConfigurationException, TransformerException {
420            return new DOMSource(toDOMNodeFromSAX(source));
421        }
422    
423        @Converter
424        public Node toDOMNodeFromSAX(SAXSource source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
425            DOMResult result = new DOMResult();
426            toResult(source, result);
427            return result.getNode();
428        }
429    
430        /**
431         * Converts the given TRaX Source into a W3C DOM node
432         * @throws SAXException
433         * @throws IOException
434         * @throws ParserConfigurationException
435         */
436        @Converter
437        public Node toDOMNode(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
438            DOMSource domSrc = toDOMSource(source);
439            return domSrc != null ? domSrc.getNode() :  null;
440        }
441    
442        /**
443         * Create a DOM element from the given source.
444         */
445        @Converter
446        public Element toDOMElement(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
447            Node node = toDOMNode(source);
448            return toDOMElement(node);
449        }
450    
451        /**
452         * Create a DOM element from the DOM node.
453         * Simply cast if the node is an Element, or
454         * return the root element if it is a Document.
455         */
456        @Converter
457        public Element toDOMElement(Node node) throws TransformerException {
458            // If the node is an document, return the root element
459            if (node instanceof Document) {
460                return ((Document) node).getDocumentElement();
461            // If the node is an element, just cast it
462            } else if (node instanceof Element) {
463                return (Element) node;
464            // Other node types are not handled
465            } else {
466                throw new TransformerException("Unable to convert DOM node to an Element");
467            }
468        }
469    
470        /**
471         * Converts the given data to a DOM document
472         *
473         * @param data is the data to be parsed
474         * @return the parsed document
475         */
476        @Converter
477        public Document toDOMDocument(byte[] data) throws IOException, SAXException, ParserConfigurationException {
478            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
479            return documentBuilder.parse(new ByteArrayInputStream(data));
480        }
481    
482        /**
483         * Converts the given {@link InputStream} to a DOM document
484         *
485         * @param in is the data to be parsed
486         * @return the parsed document
487         */
488        @Converter
489        public Document toDOMDocument(InputStream in) throws IOException, SAXException, ParserConfigurationException {
490            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
491            return documentBuilder.parse(in);
492        }
493    
494        /**
495         * Converts the given {@link InputStream} to a DOM document
496         *
497         * @param in is the data to be parsed
498         * @return the parsed document
499         */
500        @Converter
501        public Document toDOMDocument(Reader in) throws IOException, SAXException, ParserConfigurationException {
502            return toDOMDocument(new InputSource(in));
503        }
504    
505        /**
506         * Converts the given {@link InputSource} to a DOM document
507         *
508         * @param in is the data to be parsed
509         * @return the parsed document
510         */
511        @Converter
512        public Document toDOMDocument(InputSource in) throws IOException, SAXException, ParserConfigurationException {
513            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
514            return documentBuilder.parse(in);
515        }
516    
517        /**
518         * Converts the given {@link String} to a DOM document
519         *
520         * @param text is the data to be parsed
521         * @return the parsed document
522         */
523        @Converter
524        public Document toDOMDocument(String text) throws IOException, SAXException, ParserConfigurationException {
525            return toDOMDocument(new StringReader(text));
526        }
527    
528        /**
529         * Converts the given {@link File} to a DOM document
530         *
531         * @param file is the data to be parsed
532         * @return the parsed document
533         */
534        @Converter
535        public Document toDOMDocument(File file) throws IOException, SAXException, ParserConfigurationException {
536            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
537            return documentBuilder.parse(file);
538        }
539    
540    
541        /**
542         * Create a DOM document from the given source.
543         */
544        @Converter
545        public Document toDOMDocument(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
546            Node node = toDOMNode(source);
547            return toDOMDocument(node);
548        }
549    
550        /**
551         * Create a DOM document from the given Node.
552         * If the node is an document, just cast it,
553         * if the node is an root element, retrieve its
554         * owner element or create a new document and import
555         * the node.
556         */
557        @Converter
558        public Document toDOMDocument(Node node) throws ParserConfigurationException, TransformerException {
559            // If the node is the document, just cast it
560            if (node instanceof Document) {
561                return (Document) node;
562            // If the node is an element
563            } else if (node instanceof Element) {
564                Element elem = (Element) node;
565                // If this is the root element, return its owner document
566                if (elem.getOwnerDocument().getDocumentElement() == elem) {
567                    return elem.getOwnerDocument();
568                // else, create a new doc and copy the element inside it
569                } else {
570                    Document doc = createDocument();
571                    doc.appendChild(doc.importNode(node, true));
572                    return doc;
573                }
574            // other element types are not handled
575            } else {
576                throw new TransformerException("Unable to convert DOM node to a Document");
577            }
578        }
579    
580        // Properties
581        //-------------------------------------------------------------------------
582        public DocumentBuilderFactory getDocumentBuilderFactory() {
583            if (documentBuilderFactory == null) {
584                documentBuilderFactory = createDocumentBuilderFactory();
585            }
586            return documentBuilderFactory;
587        }
588    
589        public void setDocumentBuilderFactory(DocumentBuilderFactory documentBuilderFactory) {
590            this.documentBuilderFactory = documentBuilderFactory;
591        }
592    
593    
594        // Helper methods
595        //-------------------------------------------------------------------------
596        public DocumentBuilderFactory createDocumentBuilderFactory() {
597            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
598            factory.setNamespaceAware(true);
599            factory.setIgnoringElementContentWhitespace(true);
600            factory.setIgnoringComments(true);
601            return factory;
602        }
603    
604    
605        public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
606            DocumentBuilderFactory factory = getDocumentBuilderFactory();
607            return factory.newDocumentBuilder();
608        }
609    
610        public Document createDocument() throws ParserConfigurationException {
611            DocumentBuilder builder = createDocumentBuilder();
612            return builder.newDocument();
613        }
614    
615        public TransformerFactory getTransformerFactory() {
616            if (transformerFactory == null) {
617                transformerFactory = createTransformerFactory();
618            }
619            return transformerFactory;
620        }
621    
622        public void setTransformerFactory(TransformerFactory transformerFactory) {
623            this.transformerFactory = transformerFactory;
624        }
625    
626        public Transformer createTransfomer() throws TransformerConfigurationException {
627            TransformerFactory factory = getTransformerFactory();
628            return factory.newTransformer();
629        }
630    
631        public TransformerFactory createTransformerFactory() {
632            TransformerFactory answer = TransformerFactory.newInstance();
633            return answer;
634        }
635    
636    }