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