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