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