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