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