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