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