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