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