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 */
017package org.apache.camel.converter.jaxp;
018
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.FileNotFoundException;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.InputStreamReader;
027import java.io.Reader;
028import java.io.StringReader;
029import java.io.StringWriter;
030import java.nio.ByteBuffer;
031import java.util.ArrayList;
032import java.util.List;
033import java.util.Map;
034import java.util.Properties;
035
036import javax.xml.parsers.DocumentBuilder;
037import javax.xml.parsers.DocumentBuilderFactory;
038import javax.xml.parsers.ParserConfigurationException;
039import javax.xml.parsers.SAXParserFactory;
040import javax.xml.stream.XMLStreamException;
041import javax.xml.stream.XMLStreamReader;
042import javax.xml.transform.OutputKeys;
043import javax.xml.transform.Result;
044import javax.xml.transform.Source;
045import javax.xml.transform.Transformer;
046import javax.xml.transform.TransformerConfigurationException;
047import javax.xml.transform.TransformerException;
048import javax.xml.transform.TransformerFactory;
049import javax.xml.transform.TransformerFactoryConfigurationError;
050import javax.xml.transform.dom.DOMResult;
051import javax.xml.transform.dom.DOMSource;
052import javax.xml.transform.sax.SAXSource;
053import javax.xml.transform.stax.StAXSource;
054import javax.xml.transform.stream.StreamResult;
055import javax.xml.transform.stream.StreamSource;
056
057import org.w3c.dom.Document;
058import org.w3c.dom.Element;
059import org.w3c.dom.Node;
060import org.w3c.dom.NodeList;
061
062import org.xml.sax.InputSource;
063import org.xml.sax.SAXException;
064import org.xml.sax.XMLReader;
065
066import org.apache.camel.BytesSource;
067import org.apache.camel.Converter;
068import org.apache.camel.Exchange;
069import org.apache.camel.StringSource;
070import org.apache.camel.util.IOHelper;
071import org.apache.camel.util.ObjectHelper;
072import org.slf4j.Logger;
073import org.slf4j.LoggerFactory;
074
075/**
076 * A helper class to transform to and from various JAXB types such as {@link Source} and {@link Document}
077 *
078 * @version
079 */
080@Converter
081public class XmlConverter {
082    @Deprecated
083    //It will be removed in Camel 3.0, please use the Exchange.DEFAULT_CHARSET
084    public static final String DEFAULT_CHARSET_PROPERTY = "org.apache.camel.default.charset";
085
086    public static final String OUTPUT_PROPERTIES_PREFIX = "org.apache.camel.xmlconverter.output.";
087    public static final String DOCUMENT_BUILDER_FACTORY_FEATURE = "org.apache.camel.xmlconverter.documentBuilderFactory.feature";
088    public static String defaultCharset = ObjectHelper.getSystemProperty(Exchange.DEFAULT_CHARSET_PROPERTY, "UTF-8");
089
090    private static final String JDK_FALLBACK_TRANSFORMER_FACTORY = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
091    private static final Logger LOG = LoggerFactory.getLogger(XmlConverter.class);
092
093    private volatile DocumentBuilderFactory documentBuilderFactory;
094    private volatile TransformerFactory transformerFactory;
095    private volatile XMLReaderPool xmlReaderPool;
096
097    public XmlConverter() {
098    }
099
100    public XmlConverter(DocumentBuilderFactory documentBuilderFactory) {
101        this.documentBuilderFactory = documentBuilderFactory;
102    }
103
104    /**
105     * Returns the default set of output properties for conversions.
106     */
107    public Properties defaultOutputProperties() {
108        Properties properties = new Properties();
109        properties.put(OutputKeys.ENCODING, defaultCharset);
110        properties.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
111        return properties;
112    }
113
114    /**
115     * Converts the given input Source into the required result
116     */
117    public void toResult(Source source, Result result) throws TransformerException {
118        toResult(source, result, defaultOutputProperties());
119    }
120
121    /**
122     * Converts the given input Source into the required result
123     */
124    public void toResult(Source source, Result result, Properties outputProperties) throws TransformerException {
125        if (source == null) {
126            return;
127        }
128
129        Transformer transformer = createTransformer();
130        if (transformer == null) {
131            throw new TransformerException("Could not create a transformer - JAXP is misconfigured!");
132        }
133        transformer.setOutputProperties(outputProperties);
134        transformer.transform(source, result);
135    }
136
137    /**
138     * Converts the given NodeList to a boolean
139     */
140    @Converter
141    public Boolean toBoolean(NodeList list) {
142        return list.getLength() > 0;
143    }
144
145    /**
146     * Converts the given byte[] to a Source
147     */
148    @Converter
149    public BytesSource toBytesSource(byte[] data) {
150        return new BytesSource(data);
151    }
152
153    /**
154     * Converts the given String to a Source
155     */
156    @Converter
157    public StringSource toStringSource(String data) {
158        return new StringSource(data);
159    }
160
161    /**
162     * Converts the given Document to a Source
163     * @deprecated use toDOMSource instead
164     */
165    @Deprecated
166    public DOMSource toSource(Document document) {
167        return new DOMSource(document);
168    }
169
170    /**
171     * Converts the given Node to a Source
172     * @throws TransformerException
173     * @throws ParserConfigurationException
174     * @deprecated  use toDOMSource instead
175     */
176    @Deprecated
177    public Source toSource(Node node) throws ParserConfigurationException, TransformerException {
178        return toDOMSource(node);
179    }
180
181    /**
182     * Converts the given Node to a Source
183     * @throws TransformerException
184     * @throws ParserConfigurationException
185     */
186    @Converter
187    public DOMSource toDOMSource(Node node) throws ParserConfigurationException, TransformerException {
188        Document document = toDOMDocument(node);
189        return new DOMSource(document);
190    }
191
192    /**
193     * Converts the given Document to a DOMSource
194     */
195    @Converter
196    public DOMSource toDOMSource(Document document) {
197        return new DOMSource(document);
198    }
199
200    /**
201     * Converts the given String to a Source
202     */
203    @Converter
204    public Source toSource(String data) {
205        return new StringSource(data);
206    }
207
208    /**
209     * Converts the given input Source into text.
210     *
211     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
212     */
213    @Deprecated
214    public String toString(Source source) throws TransformerException {
215        return toString(source, null);
216    }
217
218    /**
219     * Converts the given input Source into text
220     */
221    @Converter
222    public String toString(Source source, Exchange exchange) throws TransformerException {
223        if (source == null) {
224            return null;
225        } else if (source instanceof StringSource) {
226            return ((StringSource) source).getText();
227        } else if (source instanceof BytesSource) {
228            return new String(((BytesSource) source).getData());
229        } else {
230            StringWriter buffer = new StringWriter();
231            if (exchange != null) {
232                // check the camelContext properties first
233                Properties properties = ObjectHelper.getCamelPropertiesWithPrefix(OUTPUT_PROPERTIES_PREFIX, exchange.getContext());
234                if (properties.size() > 0) {
235                    toResult(source, new StreamResult(buffer), properties);
236                    return buffer.toString();
237                }
238            }
239            // using the old way to deal with it
240            toResult(source, new StreamResult(buffer));
241            return buffer.toString();
242        }
243    }
244
245    /**
246     * Converts the given input Source into bytes
247     */
248    @Converter
249    public byte[] toByteArray(Source source, Exchange exchange) throws TransformerException {
250        if (source instanceof BytesSource) {
251            return ((BytesSource)source).getData();
252        } else {
253            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
254            if (exchange != null) {
255                // check the camelContext properties first
256                Properties properties = ObjectHelper.getCamelPropertiesWithPrefix(OUTPUT_PROPERTIES_PREFIX,
257                                                                                  exchange.getContext());
258                if (properties.size() > 0) {
259                    toResult(source, new StreamResult(buffer), properties);
260                    return buffer.toByteArray();
261                }
262            }
263            // using the old way to deal with it
264            toResult(source, new StreamResult(buffer));
265            return buffer.toByteArray();
266        }
267    }
268
269    /**
270     * Converts the given input Node into text
271     *
272     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
273     */
274    @Deprecated
275    public String toString(Node node) throws TransformerException {
276        return toString(node, null);
277    }
278
279    /**
280     * Converts the given input Node into text
281     */
282    @Converter
283    public String toString(Node node, Exchange exchange) throws TransformerException {
284        return toString(new DOMSource(node), exchange);
285    }
286
287    /**
288     * Converts the given Document to into text
289     * @param document The document to convert
290     * @param outputOptions The {@link OutputKeys} properties to control various aspects of the XML output
291     * @return The string representation of the document
292     * @throws TransformerException
293     */
294    public String toStringFromDocument(Document document, Properties outputOptions) throws TransformerException {
295        if (document == null) {
296            return null;
297        }
298
299        DOMSource source = new DOMSource(document);
300        StringWriter buffer = new StringWriter();
301        toResult(source, new StreamResult(buffer), outputOptions);
302        return buffer.toString();
303    }
304
305    /**
306     * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
307     * supported (making it easy to derive from this class to add new kinds of conversion).
308     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
309     */
310    @Deprecated
311    public DOMSource toDOMSource(Source source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
312        return toDOMSource(source, null);
313    }
314    
315    /**
316     * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
317     * supported (making it easy to derive from this class to add new kinds of conversion).
318     */
319    @Converter
320    public DOMSource toDOMSource(Source source, Exchange exchange) throws ParserConfigurationException, IOException, SAXException, TransformerException {
321        if (source instanceof DOMSource) {
322            return (DOMSource) source;
323        } else if (source instanceof SAXSource) {
324            return toDOMSourceFromSAX((SAXSource) source);
325        } else if (source instanceof StreamSource) {
326            return toDOMSourceFromStream((StreamSource) source, exchange);
327        } else if (source instanceof StAXSource) {
328            return toDOMSourceFromStAX((StAXSource)source);
329        } else {
330            return null;
331        }
332    }
333
334    /**
335     * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
336     * supported (making it easy to derive from this class to add new kinds of conversion).
337     */
338    @Converter
339    public DOMSource toDOMSource(String text) throws ParserConfigurationException, IOException, SAXException, TransformerException {
340        Source source = toSource(text);
341        return toDOMSourceFromStream((StreamSource) source);
342    }
343
344    /**
345     * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
346     * supported (making it easy to derive from this class to add new kinds of conversion).
347     */
348    @Converter
349    public DOMSource toDOMSource(byte[] bytes) throws IOException, SAXException, ParserConfigurationException {
350        InputStream is = new ByteArrayInputStream(bytes);
351        try {
352            return toDOMSource(is);
353        } finally {
354            IOHelper.close(is);
355        }
356    }
357
358
359    /**
360     * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
361     * supported (making it easy to derive from this class to add new kinds of conversion).
362     *
363     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
364     */
365    @Deprecated
366    public SAXSource toSAXSource(String source) throws IOException, SAXException, TransformerException {
367        return toSAXSource(source, null);
368    }
369
370    /**
371     * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
372     * supported (making it easy to derive from this class to add new kinds of conversion).
373     */
374    @Converter
375    public SAXSource toSAXSource(String source, Exchange exchange) throws IOException, SAXException, TransformerException {
376        return toSAXSource(toSource(source), exchange);
377    }
378
379    /**
380     * Converts the source instance to a {@link StAXSource} or returns null if the conversion is not
381     * supported (making it easy to derive from this class to add new kinds of conversion).
382     * @throws XMLStreamException
383     */
384    @Converter
385    public StAXSource toStAXSource(String source, Exchange exchange) throws XMLStreamException {
386        XMLStreamReader r = new StaxConverter().createXMLStreamReader(new StringReader(source));
387        return new StAXSource(r);
388    }
389
390    /**
391     * Converts the source instance to a {@link StAXSource} or returns null if the conversion is not
392     * supported (making it easy to derive from this class to add new kinds of conversion).
393     * @throws XMLStreamException
394     */
395    @Converter
396    public StAXSource toStAXSource(byte[] in, Exchange exchange) throws XMLStreamException {
397        XMLStreamReader r = new StaxConverter().createXMLStreamReader(new ByteArrayInputStream(in), exchange);
398        return new StAXSource(r);
399    }
400
401    /**
402     * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
403     * supported (making it easy to derive from this class to add new kinds of conversion).
404     *
405     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
406     */
407    @Deprecated
408    public SAXSource toSAXSource(InputStream source) throws IOException, SAXException, TransformerException {
409        return toSAXSource(source, null);
410    }
411
412    /**
413     * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
414     * supported (making it easy to derive from this class to add new kinds of conversion).
415     */
416    @Converter
417    public SAXSource toSAXSource(InputStream source, Exchange exchange) throws IOException, SAXException, TransformerException {
418        return toSAXSource(toStreamSource(source), exchange);
419    }
420
421    /**
422     * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
423     * supported (making it easy to derive from this class to add new kinds of conversion).
424     */
425    @Converter
426    public SAXSource toSAXSource(byte[] in, Exchange exchange) throws IOException, SAXException, TransformerException {
427        return toSAXSource(toStreamSource(in, exchange), exchange);
428    }
429
430    /**
431     * Converts the source instance to a {@link StAXSource} or returns null if the conversion is not
432     * supported (making it easy to derive from this class to add new kinds of conversion).
433     * @throws XMLStreamException
434     */
435    @Converter
436    public StAXSource toStAXSource(InputStream source, Exchange exchange) throws XMLStreamException {
437        XMLStreamReader r = new StaxConverter().createXMLStreamReader(source, exchange);
438        return new StAXSource(r);
439    }
440
441    /**
442     * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
443     * supported (making it easy to derive from this class to add new kinds of conversion).
444     */
445    @Converter
446    public SAXSource toSAXSource(File file, Exchange exchange) throws IOException, SAXException, TransformerException {
447        InputStream is = IOHelper.buffered(new FileInputStream(file));
448        return toSAXSource(is, exchange);
449    }
450
451    /**
452     * Converts the source instance to a {@link StAXSource} or returns null if the conversion is not
453     * supported (making it easy to derive from this class to add new kinds of conversion).
454     * @throws FileNotFoundException
455     * @throws XMLStreamException
456     */
457    @Converter
458    public StAXSource toStAXSource(File file, Exchange exchange) throws FileNotFoundException, XMLStreamException {
459        InputStream is = IOHelper.buffered(new FileInputStream(file));
460        XMLStreamReader r = new StaxConverter().createXMLStreamReader(is, exchange);
461        return new StAXSource(r);
462    }
463
464    /**
465     * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
466     * supported (making it easy to derive from this class to add new kinds of conversion).
467     *
468     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
469     */
470    @Deprecated
471    public SAXSource toSAXSource(Source source) throws IOException, SAXException, TransformerException {
472        return toSAXSource(source, null);
473    }
474
475    /**
476     * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
477     * supported (making it easy to derive from this class to add new kinds of conversion).
478     */
479    @Converter
480    public SAXSource toSAXSource(Source source, Exchange exchange) throws IOException, SAXException, TransformerException {
481        if (source instanceof SAXSource) {
482            return (SAXSource) source;
483        } else if (source instanceof DOMSource) {
484            return toSAXSourceFromDOM((DOMSource) source, exchange);
485        } else if (source instanceof StreamSource) {
486            return toSAXSourceFromStream((StreamSource) source, exchange);
487        } else if (source instanceof StAXSource) {
488            return toSAXSourceFromStAX((StAXSource) source, exchange);
489        } else {
490            return null;
491        }
492    }
493
494    /**
495     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
496     */
497    @Deprecated
498    public StreamSource toStreamSource(Source source) throws TransformerException {
499        return toStreamSource(source, null);
500    }
501
502    @Converter
503    public StreamSource toStreamSource(Source source, Exchange exchange) throws TransformerException {
504        if (source instanceof StreamSource) {
505            return (StreamSource) source;
506        } else if (source instanceof DOMSource) {
507            return toStreamSourceFromDOM((DOMSource) source, exchange);
508        } else if (source instanceof SAXSource) {
509            return toStreamSourceFromSAX((SAXSource) source, exchange);
510        } else if (source instanceof StAXSource) {
511            return toStreamSourceFromStAX((StAXSource) source, exchange);
512        } else {
513            return null;
514        }
515    }
516
517    @Converter
518    public StreamSource toStreamSource(InputStream in) throws TransformerException {
519        return new StreamSource(in);
520    }
521
522    @Converter
523    public StreamSource toStreamSource(Reader in) throws TransformerException {
524        return new StreamSource(in);
525    }
526
527    @Converter
528    public StreamSource toStreamSource(File in) throws TransformerException {
529        return new StreamSource(in);
530    }
531
532    @Converter
533    public StreamSource toStreamSource(byte[] in, Exchange exchange) throws TransformerException {
534        InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, exchange, in);
535        return new StreamSource(is);
536    }
537
538    @Converter
539    public StreamSource toStreamSource(ByteBuffer in, Exchange exchange) throws TransformerException {
540        InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, exchange, in);
541        return new StreamSource(is);
542    }
543
544    /**
545     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
546     */
547    @Deprecated
548    public StreamSource toStreamSourceFromSAX(SAXSource source) throws TransformerException {
549        return toStreamSourceFromSAX(source, null);
550    }
551
552    @Converter
553    public StreamSource toStreamSourceFromSAX(SAXSource source, Exchange exchange) throws TransformerException {
554        InputSource inputSource = source.getInputSource();
555        if (inputSource != null) {
556            if (inputSource.getCharacterStream() != null) {
557                return new StreamSource(inputSource.getCharacterStream());
558            }
559            if (inputSource.getByteStream() != null) {
560                return new StreamSource(inputSource.getByteStream());
561            }
562        }
563        String result = toString(source, exchange);
564        return new StringSource(result);
565    }
566
567    /**
568     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
569     */
570    @Deprecated
571    public StreamSource toStreamSourceFromDOM(DOMSource source) throws TransformerException {
572        return toStreamSourceFromDOM(source, null);
573    }
574
575    @Converter
576    public StreamSource toStreamSourceFromDOM(DOMSource source, Exchange exchange) throws TransformerException {
577        String result = toString(source, exchange);
578        return new StringSource(result);
579    }
580    @Converter
581    public StreamSource toStreamSourceFromStAX(StAXSource source, Exchange exchange) throws TransformerException {
582        String result = toString(source, exchange);
583        return new StringSource(result);
584    }
585
586    /**
587     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
588     */
589    @Deprecated
590    public SAXSource toSAXSourceFromStream(StreamSource source) throws SAXException {
591        return toSAXSourceFromStream(source, null);
592    }
593    
594    @Converter
595    public SAXSource toSAXSourceFromStream(StreamSource source, Exchange exchange) throws SAXException {
596        InputSource inputSource;
597        if (source.getReader() != null) {
598            inputSource = new InputSource(source.getReader());
599        } else {
600            inputSource = new InputSource(source.getInputStream());
601        }
602        inputSource.setSystemId(source.getSystemId());
603        inputSource.setPublicId(source.getPublicId());
604
605        XMLReader xmlReader = null;
606        try {
607            // use the SAXPaserFactory which is set from exchange
608            if (exchange != null) {
609                SAXParserFactory sfactory = exchange.getProperty(Exchange.SAXPARSER_FACTORY, SAXParserFactory.class);
610                if (sfactory != null) {
611                    if (!sfactory.isNamespaceAware()) {
612                        sfactory.setNamespaceAware(true);
613                    }
614                    xmlReader = sfactory.newSAXParser().getXMLReader();
615                }
616            }
617            if (xmlReader == null) {
618                if (xmlReaderPool == null) {
619                    xmlReaderPool = new XMLReaderPool(createSAXParserFactory());
620                }
621                xmlReader = xmlReaderPool.createXMLReader();
622            }
623        } catch (Exception ex) {
624            LOG.warn("Cannot create the SAXParser XMLReader, due to {}", ex);
625        }
626        return new SAXSource(xmlReader, inputSource);
627    }
628
629    /**
630     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
631     */
632    @Deprecated
633    public Reader toReaderFromSource(Source src) throws TransformerException {
634        return toReaderFromSource(src, null);
635    }
636
637    @Converter
638    public Reader toReaderFromSource(Source src, Exchange exchange) throws TransformerException {
639        StreamSource stSrc = toStreamSource(src, exchange);
640        Reader r = stSrc.getReader();
641        if (r == null) {
642            r = new InputStreamReader(stSrc.getInputStream());
643        }
644        return r;
645    }
646
647    /**
648    * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
649    */
650    @Deprecated
651    public DOMSource toDOMSource(InputStream is) throws ParserConfigurationException, IOException, SAXException {
652        return toDOMSource(is, null);
653    }
654    
655    @Converter
656    public DOMSource toDOMSource(InputStream is, Exchange exchange) throws ParserConfigurationException, IOException, SAXException {
657        InputSource source = new InputSource(is);
658        String systemId = source.getSystemId();
659        DocumentBuilder builder = getDocumentBuilderFactory(exchange).newDocumentBuilder();
660        Document document = builder.parse(source);
661        return new DOMSource(document, systemId);
662    }
663
664    /**
665     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
666     */
667    @Deprecated
668    public DOMSource toDOMSource(File file) throws ParserConfigurationException, IOException, SAXException {
669        return toDOMSource(file, null);
670    }
671    
672    @Converter
673    public DOMSource toDOMSource(File file, Exchange exchange) throws ParserConfigurationException, IOException, SAXException {
674        InputStream is = IOHelper.buffered(new FileInputStream(file));
675        return toDOMSource(is, exchange);
676    }
677
678    /**
679     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
680     */
681    @Deprecated
682    public DOMSource toDOMSourceFromStream(StreamSource source) throws ParserConfigurationException, IOException, SAXException {
683        return toDOMSourceFromStream(source, null);
684    }
685    
686    @Converter
687    public DOMSource toDOMSourceFromStream(StreamSource source, Exchange exchange) throws ParserConfigurationException, IOException, SAXException {
688        Document document;
689        String systemId = source.getSystemId();
690
691        DocumentBuilder builder = getDocumentBuilderFactory(exchange).newDocumentBuilder();
692        Reader reader = source.getReader();
693        if (reader != null) {
694            document = builder.parse(new InputSource(reader));
695        } else {
696            InputStream inputStream = source.getInputStream();
697            if (inputStream != null) {
698                InputSource inputsource = new InputSource(inputStream);
699                inputsource.setSystemId(systemId);
700                document = builder.parse(inputsource);
701            } else {
702                throw new IOException("No input stream or reader available on StreamSource: " + source);
703            }
704        }
705        return new DOMSource(document, systemId);
706    }
707
708    /**
709     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
710     */
711    @Deprecated
712    public SAXSource toSAXSourceFromDOM(DOMSource source) throws TransformerException {
713        return toSAXSourceFromDOM(source, null);
714    }
715
716    @Converter
717    public SAXSource toSAXSourceFromDOM(DOMSource source, Exchange exchange) throws TransformerException {
718        String str = toString(source, exchange);
719        StringReader reader = new StringReader(str);
720        return new SAXSource(new InputSource(reader));
721    }
722
723    @Converter
724    public SAXSource toSAXSourceFromStAX(StAXSource source, Exchange exchange) throws TransformerException {
725        String str = toString(source, exchange);
726        StringReader reader = new StringReader(str);
727        return new SAXSource(new InputSource(reader));
728    }
729
730    @Converter
731    public DOMSource toDOMSourceFromSAX(SAXSource source) throws IOException, SAXException, ParserConfigurationException, TransformerException {
732        return new DOMSource(toDOMNodeFromSAX(source));
733    }
734
735    @Converter
736    public DOMSource toDOMSourceFromStAX(StAXSource source) throws IOException, SAXException, ParserConfigurationException, TransformerException {
737        return new DOMSource(toDOMNodeFromStAX(source));
738    }
739
740    @Converter
741    public Node toDOMNodeFromSAX(SAXSource source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
742        DOMResult result = new DOMResult();
743        toResult(source, result);
744        return result.getNode();
745    }
746
747    @Converter
748    public Node toDOMNodeFromStAX(StAXSource source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
749        DOMResult result = new DOMResult();
750        toResult(source, result);
751        return result.getNode();
752    }
753
754    /**
755     * Convert a NodeList consisting of just 1 node to a DOM Node.
756     * @param nl the NodeList
757     * @return the DOM Node
758     */
759    @Converter(allowNull = true)
760    public Node toDOMNodeFromSingleNodeList(NodeList nl) {
761        return nl.getLength() == 1 ? nl.item(0) : null;
762    }
763
764    /**
765     * Convert a NodeList consisting of just 1 node to a DOM Document.
766     * Cannot convert NodeList with length > 1 because they require a root node.
767     * @param nl the NodeList
768     * @return the DOM Document
769     */
770    @Converter(allowNull = true)
771    public Document toDOMDocumentFromSingleNodeList(NodeList nl) throws ParserConfigurationException, TransformerException {
772        if (nl.getLength() == 1) {
773            return toDOMDocument(nl.item(0));
774        } else if (nl instanceof Node) {
775            // as XML parsers may often have nodes that implement both Node and NodeList then the type converter lookup
776            // may lookup either a type converter from NodeList or Node. So let's fallback and try with Node
777            return toDOMDocument((Node) nl);
778        } else {
779            return null;
780        }
781    }
782
783    /**
784     * Converts the given TRaX Source into a W3C DOM node
785     */
786    @Converter(allowNull = true)
787    public Node toDOMNode(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
788        DOMSource domSrc = toDOMSource(source);
789        return domSrc != null ? domSrc.getNode() : null;
790    }
791
792    /**
793     * Create a DOM element from the given source.
794     */
795    @Converter
796    public Element toDOMElement(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
797        Node node = toDOMNode(source);
798        return toDOMElement(node);
799    }
800
801    /**
802     * Create a DOM element from the DOM node.
803     * Simply cast if the node is an Element, or
804     * return the root element if it is a Document.
805     */
806    @Converter
807    public Element toDOMElement(Node node) throws TransformerException {
808        // If the node is an document, return the root element
809        if (node instanceof Document) {
810            return ((Document) node).getDocumentElement();
811            // If the node is an element, just cast it
812        } else if (node instanceof Element) {
813            return (Element) node;
814            // Other node types are not handled
815        } else {
816            throw new TransformerException("Unable to convert DOM node to an Element");
817        }
818    }
819
820    
821    /**
822     * Converts the given data to a DOM document
823     *
824     * @param data is the data to be parsed
825     * @return the parsed document
826     */
827    @Deprecated
828    public Document toDOMDocument(byte[] data) throws IOException, SAXException, ParserConfigurationException {
829        return toDOMDocument(data, null);
830    }
831    
832    /**
833     * Converts the given data to a DOM document
834     *
835     * @param data is the data to be parsed
836     * @param exchange is the exchange to be used when calling the converter
837     * @return the parsed document
838     */
839    @Converter
840    public Document toDOMDocument(byte[] data, Exchange exchange) throws IOException, SAXException, ParserConfigurationException {
841        DocumentBuilder documentBuilder = getDocumentBuilderFactory(exchange).newDocumentBuilder();
842        return documentBuilder.parse(new ByteArrayInputStream(data));
843    }
844
845    /**
846     * Converts the given {@link InputStream} to a DOM document
847     *
848     * @param in is the data to be parsed
849     * @return the parsed document
850     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
851     */
852    @Deprecated
853    public Document toDOMDocument(InputStream in) throws IOException, SAXException, ParserConfigurationException {
854        return toDOMDocument(in, null);
855    }
856    
857    /**
858     * Converts the given {@link InputStream} to a DOM document
859     *
860     * @param in is the data to be parsed
861     * @param exchange is the exchange to be used when calling the converter
862     * @return the parsed document
863     */
864    @Converter
865    public Document toDOMDocument(InputStream in, Exchange exchange) throws IOException, SAXException, ParserConfigurationException {
866        DocumentBuilder documentBuilder = getDocumentBuilderFactory(exchange).newDocumentBuilder();
867        return documentBuilder.parse(in);
868    }
869
870    /**
871     * Converts the given {@link InputStream} to a DOM document
872     *
873     * @param in is the data to be parsed
874     * @return the parsed document
875     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
876     */
877    @Deprecated
878    public Document toDOMDocument(Reader in) throws IOException, SAXException, ParserConfigurationException {
879        return toDOMDocument(new InputSource(in));
880    }
881    
882    /**
883     * Converts the given {@link InputStream} to a DOM document
884     *
885     * @param in is the data to be parsed
886     * @param exchange is the exchange to be used when calling the converter
887     * @return the parsed document
888     */
889    @Converter
890    public Document toDOMDocument(Reader in, Exchange exchange) throws IOException, SAXException, ParserConfigurationException {
891        return toDOMDocument(new InputSource(in), exchange);
892    }
893
894    /**
895     * Converts the given {@link InputSource} to a DOM document
896     *
897     * @param in is the data to be parsed
898     * @return the parsed document
899     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
900     */
901    @Deprecated
902    public Document toDOMDocument(InputSource in) throws IOException, SAXException, ParserConfigurationException {
903        return toDOMDocument(in, null);
904    }
905    
906    /**
907     * Converts the given {@link InputSource} to a DOM document
908     *
909     * @param in is the data to be parsed
910     * @param exchange is the exchange to be used when calling the converter
911     * @return the parsed document
912     */
913    @Converter
914    public Document toDOMDocument(InputSource in, Exchange exchange) throws IOException, SAXException, ParserConfigurationException {
915        DocumentBuilder documentBuilder = getDocumentBuilderFactory(exchange).newDocumentBuilder();
916        return documentBuilder.parse(in);
917    }
918
919    /**
920     * Converts the given {@link String} to a DOM document
921     *
922     * @param text is the data to be parsed
923     * @return the parsed document
924     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
925     */
926    @Deprecated
927    public Document toDOMDocument(String text) throws IOException, SAXException, ParserConfigurationException {
928        return toDOMDocument(new StringReader(text));
929    }
930    
931    /**
932     * Converts the given {@link String} to a DOM document
933     *
934     * @param text is the data to be parsed
935     * @param exchange is the exchange to be used when calling the converter
936     * @return the parsed document
937     */
938    @Converter
939    public Document toDOMDocument(String text, Exchange exchange) throws IOException, SAXException, ParserConfigurationException {
940        return toDOMDocument(new StringReader(text), exchange);
941    }
942
943    /**
944     * Converts the given {@link File} to a DOM document
945     *
946     * @param file is the data to be parsed
947     * @return the parsed document
948     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
949     */
950    @Deprecated
951    public Document toDOMDocument(File file) throws IOException, SAXException, ParserConfigurationException {
952        return toDOMDocument(file, null);
953    }
954    
955    /**
956     * Converts the given {@link File} to a DOM document
957     *
958     * @param file is the data to be parsed
959     * @param exchange is the exchange to be used when calling the converter
960     * @return the parsed document
961     */
962    @Converter
963    public Document toDOMDocument(File file, Exchange exchange) throws IOException, SAXException, ParserConfigurationException {
964        DocumentBuilder documentBuilder = getDocumentBuilderFactory(exchange).newDocumentBuilder();
965        return documentBuilder.parse(file);
966    }
967
968    /**
969     * Create a DOM document from the given source.
970     */
971    @Converter
972    public Document toDOMDocument(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
973        Node node = toDOMNode(source);
974        return toDOMDocument(node);
975    }
976
977    /**
978     * Create a DOM document from the given Node.
979     *
980     * If the node is an document, just cast it, if the node is an root element, retrieve its
981     * owner element or create a new document and import the node.
982     */
983    @Converter
984    public Document toDOMDocument(final Node node) throws ParserConfigurationException, TransformerException {
985        ObjectHelper.notNull(node, "node");
986
987        // If the node is the document, just cast it
988        if (node instanceof Document) {
989            return (Document) node;
990            // If the node is an element
991        } else if (node instanceof Element) {
992            Element elem = (Element) node;
993            // If this is the root element, return its owner document
994            if (elem.getOwnerDocument().getDocumentElement() == elem) {
995                return elem.getOwnerDocument();
996                // else, create a new doc and copy the element inside it
997            } else {
998                Document doc = createDocument();
999                // import node must not occur concurrent on the same node (must be its owner)
1000                // so we need to synchronize on it
1001                synchronized (node.getOwnerDocument()) {
1002                    doc.appendChild(doc.importNode(node, true));
1003                }
1004                return doc;
1005            }
1006            // other element types are not handled
1007        } else {
1008            throw new TransformerException("Unable to convert DOM node to a Document: " + node);
1009        }
1010    }
1011
1012    /**
1013     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
1014     */
1015    @Deprecated
1016    public InputStream toInputStream(DOMSource source) throws TransformerException, IOException {
1017        return toInputStream(source, null);
1018    }
1019
1020    @Converter
1021    public InputStream toInputStream(DOMSource source, Exchange exchange) throws TransformerException, IOException {
1022        return new ByteArrayInputStream(toByteArray(source, exchange));
1023    }
1024
1025    /**
1026     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
1027     */
1028    @Deprecated
1029    public InputStream toInputStream(Document dom) throws TransformerException, IOException {
1030        return toInputStream(dom, null);
1031    }
1032
1033    @Converter
1034    public InputStream toInputStream(Document dom, Exchange exchange) throws TransformerException, IOException {
1035        return toInputStream(new DOMSource(dom), exchange);
1036    }
1037
1038    @Converter
1039    public InputSource toInputSource(InputStream is, Exchange exchange) {
1040        return new InputSource(is);
1041    }
1042
1043    @Converter
1044    public InputSource toInputSource(File file, Exchange exchange) throws FileNotFoundException {
1045        InputStream is = IOHelper.buffered(new FileInputStream(file));
1046        return new InputSource(is);
1047    }
1048
1049    // Properties
1050    //-------------------------------------------------------------------------
1051
1052    public DocumentBuilderFactory getDocumentBuilderFactory() {
1053        if (documentBuilderFactory == null) {
1054            documentBuilderFactory = createDocumentBuilderFactory();
1055        }
1056        return documentBuilderFactory;
1057    }
1058
1059    public void setDocumentBuilderFactory(DocumentBuilderFactory documentBuilderFactory) {
1060        this.documentBuilderFactory = documentBuilderFactory;
1061    }
1062
1063    public TransformerFactory getTransformerFactory() {
1064        if (transformerFactory == null) {
1065            transformerFactory = createTransformerFactory();
1066        }
1067        return transformerFactory;
1068    }
1069
1070    public void setTransformerFactory(TransformerFactory transformerFactory) {
1071        if (transformerFactory != null) {
1072            configureSaxonTransformerFactory(transformerFactory);
1073        }
1074        this.transformerFactory = transformerFactory;
1075    }
1076
1077    // Helper methods
1078    //-------------------------------------------------------------------------
1079
1080    protected void setupFeatures(DocumentBuilderFactory factory) {
1081        Properties properties = System.getProperties();
1082        List<String> features = new ArrayList<String>();
1083        for (Map.Entry<Object, Object> prop : properties.entrySet()) {
1084            String key = (String) prop.getKey();
1085            if (key.startsWith(XmlConverter.DOCUMENT_BUILDER_FACTORY_FEATURE)) {
1086                String uri = ObjectHelper.after(key, ":");
1087                Boolean value = Boolean.valueOf((String)prop.getValue());
1088                try {
1089                    factory.setFeature(uri, value);
1090                    features.add("feature " + uri + " value " + value);
1091                } catch (ParserConfigurationException e) {
1092                    LOG.warn("DocumentBuilderFactory doesn't support the feature {} with value {}, due to {}.", new Object[]{uri, value, e});
1093                }
1094            }
1095        }
1096        if (features.size() > 0) {
1097            StringBuilder featureString = new StringBuilder();
1098            // just log the configured feature
1099            for (String feature : features) {
1100                if (featureString.length() != 0) {
1101                    featureString.append(", ");
1102                }
1103                featureString.append(feature);
1104            }
1105            LOG.info("DocumentBuilderFactory has been set with features {{}}.", featureString.toString());
1106        }
1107
1108    }
1109    
1110    public DocumentBuilderFactory getDocumentBuilderFactory(Exchange exchange) {
1111        DocumentBuilderFactory answer = getDocumentBuilderFactory();
1112        // Get the DocumentBuilderFactory from the exchange header first
1113        if (exchange != null) {
1114            DocumentBuilderFactory factory = exchange.getProperty(Exchange.DOCUMENT_BUILDER_FACTORY, DocumentBuilderFactory.class);
1115            if (factory != null) {
1116                answer = factory;
1117            }
1118        }
1119        return answer;
1120    }
1121 
1122    public DocumentBuilderFactory createDocumentBuilderFactory() {
1123        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1124        factory.setNamespaceAware(true);
1125        factory.setIgnoringElementContentWhitespace(true);
1126        factory.setIgnoringComments(true);
1127        try {
1128            // Disable the external-general-entities by default
1129            factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
1130        } catch (ParserConfigurationException e) {
1131            LOG.warn("DocumentBuilderFactory doesn't support the feature {} with value {}, due to {}.",
1132                     new Object[]{"http://xml.org/sax/features/external-general-entities", false, e});
1133        }
1134        // setup the SecurityManager by default if it's apache xerces
1135        try {
1136            Class<?> smClass = ObjectHelper.loadClass("org.apache.xerces.util.SecurityManager");
1137            if (smClass != null) {
1138                Object sm = smClass.newInstance();
1139                // Here we just use the default setting of the SeurityManager
1140                factory.setAttribute("http://apache.org/xml/properties/security-manager", sm);
1141            }
1142        } catch (Exception e) {
1143            LOG.warn("DocumentBuilderFactory doesn't support the attribute {}, due to {}.",
1144                     new Object[]{"http://apache.org/xml/properties/security-manager", e});
1145        }
1146        // setup the feature from the system property
1147        setupFeatures(factory);
1148        return factory;
1149    }
1150
1151    public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
1152        DocumentBuilderFactory factory = getDocumentBuilderFactory();
1153        return factory.newDocumentBuilder();
1154    }
1155
1156    public Document createDocument() throws ParserConfigurationException {
1157        DocumentBuilder builder = createDocumentBuilder();
1158        return builder.newDocument();
1159    }
1160
1161    /**
1162     * @deprecated use {@link #createTransformer}, will be removed in Camel 3.0
1163     */
1164    @Deprecated
1165    public Transformer createTransfomer() throws TransformerConfigurationException {
1166        return createTransformer();
1167    }
1168
1169    public Transformer createTransformer() throws TransformerConfigurationException {
1170        TransformerFactory factory = getTransformerFactory();
1171        return factory.newTransformer();
1172    }
1173
1174    public TransformerFactory createTransformerFactory() {
1175        TransformerFactory factory;
1176        TransformerFactoryConfigurationError cause;
1177        try {
1178            factory = TransformerFactory.newInstance();
1179        } catch (TransformerFactoryConfigurationError e) {
1180            cause = e;
1181            // try fallback from the JDK
1182            try {
1183                LOG.debug("Cannot create/load TransformerFactory due: {}. Will attempt to use JDK fallback TransformerFactory: {}", e.getMessage(), JDK_FALLBACK_TRANSFORMER_FACTORY);
1184                factory = TransformerFactory.newInstance(JDK_FALLBACK_TRANSFORMER_FACTORY, null);
1185            } catch (Throwable t) {
1186                // okay we cannot load fallback then throw original exception
1187                throw cause;
1188            }
1189        }
1190        LOG.debug("Created TransformerFactory: {}", factory);
1191
1192        // Enable the Security feature by default
1193        try {
1194            factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
1195        } catch (TransformerConfigurationException e) {
1196            LOG.warn("TransformerFactory doesn't support the feature {} with value {}, due to {}.", new Object[]{javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, "true", e});
1197        }
1198        factory.setErrorListener(new XmlErrorListener());
1199        configureSaxonTransformerFactory(factory);
1200        return factory;
1201    }
1202
1203    /**
1204     * Make a Saxon TransformerFactory more JAXP compliant by configuring it to
1205     * send &lt;xsl:message&gt; output to the ErrorListener.
1206     *
1207     * @param factory
1208     *            the TransformerFactory
1209     */
1210    public void configureSaxonTransformerFactory(TransformerFactory factory) {
1211        // check whether we have a Saxon TransformerFactory ("net.sf.saxon" for open source editions (HE / B)
1212        // and "com.saxonica" for commercial editions (PE / EE / SA))
1213        Class<?> factoryClass = factory.getClass();
1214        if (factoryClass.getName().startsWith("net.sf.saxon")
1215                || factoryClass.getName().startsWith("com.saxonica")) {
1216
1217            // just in case there are multiple class loaders with different Saxon versions, use the
1218            // TransformerFactory's class loader to find Saxon support classes
1219            ClassLoader loader = factoryClass.getClassLoader();
1220
1221            // try to find Saxon's MessageWarner class that redirects <xsl:message> to the ErrorListener
1222            Class<?> messageWarner = null;
1223            try {
1224                // Saxon >= 9.3
1225                messageWarner = loader.loadClass("net.sf.saxon.serialize.MessageWarner");
1226            } catch (ClassNotFoundException cnfe) {
1227                try {
1228                    // Saxon < 9.3 (including Saxon-B / -SA)
1229                    messageWarner = loader.loadClass("net.sf.saxon.event.MessageWarner");
1230                } catch (ClassNotFoundException cnfe2) {
1231                    LOG.warn("Error loading Saxon's net.sf.saxon.serialize.MessageWarner class from the classpath!"
1232                            + " <xsl:message> output will not be redirected to the ErrorListener!");
1233                }
1234            }
1235
1236            if (messageWarner != null) {
1237                // set net.sf.saxon.FeatureKeys.MESSAGE_EMITTER_CLASS
1238                factory.setAttribute("http://saxon.sf.net/feature/messageEmitterClass", messageWarner.getName());
1239            }
1240        }
1241    }
1242
1243    public SAXParserFactory createSAXParserFactory() {
1244        SAXParserFactory sfactory = SAXParserFactory.newInstance();
1245        // Need to setup XMLReader security feature by default
1246        try {
1247            sfactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
1248        } catch (Exception e) {
1249            LOG.warn("SAXParser doesn't support the feature {} with value {}, due to {}.", new Object[]{javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, "true", e});
1250        }
1251        try {
1252            sfactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
1253        } catch (Exception e) {
1254            LOG.warn("SAXParser doesn't support the feature {} with value {}, due to {}.",
1255                     new Object[]{"http://xml.org/sax/features/external-general-entities", false, e});
1256        }
1257        sfactory.setNamespaceAware(true);
1258        return sfactory;
1259    }
1260}