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     */
017    package org.apache.camel.converter.jaxp;
018    
019    import java.io.ByteArrayInputStream;
020    import java.io.ByteArrayOutputStream;
021    import java.io.File;
022    import java.io.FileInputStream;
023    import java.io.FileNotFoundException;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.InputStreamReader;
027    import java.io.Reader;
028    import java.io.StringReader;
029    import java.io.StringWriter;
030    import java.nio.ByteBuffer;
031    import java.util.ArrayList;
032    import java.util.List;
033    import java.util.Map;
034    import java.util.Properties;
035    
036    import javax.xml.parsers.DocumentBuilder;
037    import javax.xml.parsers.DocumentBuilderFactory;
038    import javax.xml.parsers.ParserConfigurationException;
039    import javax.xml.stream.XMLStreamException;
040    import javax.xml.stream.XMLStreamReader;
041    import javax.xml.transform.OutputKeys;
042    import javax.xml.transform.Result;
043    import javax.xml.transform.Source;
044    import javax.xml.transform.Transformer;
045    import javax.xml.transform.TransformerConfigurationException;
046    import javax.xml.transform.TransformerException;
047    import javax.xml.transform.TransformerFactory;
048    import javax.xml.transform.dom.DOMResult;
049    import javax.xml.transform.dom.DOMSource;
050    import javax.xml.transform.sax.SAXSource;
051    import javax.xml.transform.stax.StAXSource;
052    import javax.xml.transform.stream.StreamResult;
053    import javax.xml.transform.stream.StreamSource;
054    
055    import org.w3c.dom.Document;
056    import org.w3c.dom.Element;
057    import org.w3c.dom.Node;
058    import org.w3c.dom.NodeList;
059    
060    import org.xml.sax.InputSource;
061    import org.xml.sax.SAXException;
062    
063    import org.apache.camel.BytesSource;
064    import org.apache.camel.Converter;
065    import org.apache.camel.Exchange;
066    import org.apache.camel.StringSource;
067    import org.apache.camel.builder.xml.XPathBuilder;
068    import org.apache.camel.util.IOHelper;
069    import org.apache.camel.util.ObjectHelper;
070    
071    import org.slf4j.Logger;
072    import 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
080    public 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(XPathBuilder.class);
090        
091        private DocumentBuilderFactory documentBuilderFactory;
092        private TransformerFactory transformerFactory;
093    
094        public XmlConverter() {
095        }
096    
097        public XmlConverter(DocumentBuilderFactory documentBuilderFactory) {
098            this.documentBuilderFactory = documentBuilderFactory;
099        }
100    
101        /**
102         * Returns the default set of output properties for conversions.
103         */
104        public Properties defaultOutputProperties() {
105            Properties properties = new Properties();
106            properties.put(OutputKeys.ENCODING, defaultCharset);
107            properties.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
108            return properties;
109        }
110    
111        /**
112         * Converts the given input Source into the required result
113         */
114        public void toResult(Source source, Result result) throws TransformerException {
115            toResult(source, result, defaultOutputProperties());
116        }
117    
118        /**
119         * Converts the given input Source into the required result
120         */
121        public void toResult(Source source, Result result, Properties outputProperties) throws TransformerException {
122            if (source == null) {
123                return;
124            }
125    
126            Transformer transformer = createTransformer();
127            if (transformer == null) {
128                throw new TransformerException("Could not create a transformer - JAXP is misconfigured!");
129            }
130            transformer.setOutputProperties(outputProperties);
131            transformer.transform(source, result);
132        }
133    
134        /**
135         * Converts the given NodeList to a boolean
136         */
137        @Converter
138        public Boolean toBoolean(NodeList list) {
139            return list.getLength() > 0;
140        }
141    
142        /**
143         * Converts the given byte[] to a Source
144         */
145        @Converter
146        public BytesSource toBytesSource(byte[] data) {
147            return new BytesSource(data);
148        }
149    
150        /**
151         * Converts the given String to a Source
152         */
153        @Converter
154        public StringSource toStringSource(String data) {
155            return new StringSource(data);
156        }
157    
158        /**
159         * Converts the given Document to a Source
160         * @deprecated use toDOMSource instead
161         */
162        @Deprecated
163        public DOMSource toSource(Document document) {
164            return new DOMSource(document);
165        }
166    
167        /**
168         * Converts the given Node to a Source
169         * @throws TransformerException 
170         * @throws ParserConfigurationException 
171         * @deprecated  use toDOMSource instead
172         */
173        @Deprecated
174        public Source toSource(Node node) throws ParserConfigurationException, TransformerException {
175            return toDOMSource(node);
176        }
177    
178        /**
179         * Converts the given Node to a Source
180         * @throws TransformerException 
181         * @throws ParserConfigurationException 
182         */
183        @Converter
184        public DOMSource toDOMSource(Node node) throws ParserConfigurationException, TransformerException {
185            Document document = toDOMDocument(node);
186            return new DOMSource(document);
187        }
188        
189        /**
190         * Converts the given Document to a DOMSource
191         */
192        @Converter
193        public DOMSource toDOMSource(Document document) {
194            return new DOMSource(document);
195        }
196    
197        /**
198         * Converts the given String to a Source
199         */
200        @Converter
201        public Source toSource(String data) {
202            return new StringSource(data);
203        }
204    
205        /**
206         * Converts the given input Source into text.
207         *
208         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
209         */
210        @Deprecated
211        public String toString(Source source) throws TransformerException {
212            return toString(source, null);
213        }
214    
215        /**
216         * Converts the given input Source into text
217         */
218        @Converter
219        public String toString(Source source, Exchange exchange) throws TransformerException {
220            if (source == null) {
221                return null;
222            } else if (source instanceof StringSource) {
223                return ((StringSource) source).getText();
224            } else if (source instanceof BytesSource) {
225                return new String(((BytesSource) source).getData());
226            } else {
227                StringWriter buffer = new StringWriter();           
228                if (exchange != null) {
229                    // check the camelContext properties first
230                    Properties properties = ObjectHelper.getCamelPropertiesWithPrefix(OUTPUT_PROPERTIES_PREFIX, exchange.getContext());
231                    if (properties.size() > 0) {
232                        toResult(source, new StreamResult(buffer), properties);
233                        return buffer.toString();
234                    }            
235                }
236                // using the old way to deal with it
237                toResult(source, new StreamResult(buffer));            
238                return buffer.toString();
239            }
240        }
241    
242        /**
243         * Converts the given input Source into bytes
244         */
245        @Converter
246        public byte[] toByteArray(Source source, Exchange exchange) throws TransformerException {
247            if (source == null) {
248                return null;
249            } else if (source instanceof BytesSource) {
250                return ((BytesSource)source).getData();
251            } else {
252                ByteArrayOutputStream buffer = new ByteArrayOutputStream();
253                if (exchange != null) {
254                    // check the camelContext properties first
255                    Properties properties = ObjectHelper.getCamelPropertiesWithPrefix(OUTPUT_PROPERTIES_PREFIX,
256                                                                                      exchange.getContext());
257                    if (properties.size() > 0) {
258                        toResult(source, new StreamResult(buffer), properties);
259                        return buffer.toByteArray();
260                    }
261                }
262                // using the old way to deal with it
263                toResult(source, new StreamResult(buffer));
264                return buffer.toByteArray();
265            }
266        }
267        
268        /**
269         * Converts the given input Node into text
270         *
271         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
272         */
273        @Deprecated
274        public String toString(Node node) throws TransformerException {
275            return toString(node, null);
276        }
277        
278        /**
279         * Converts the given input Node into text
280         */
281        @Converter
282        public String toString(Node node, Exchange exchange) throws TransformerException {
283            return toString(new DOMSource(node), exchange);
284        }
285    
286        /**
287         * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
288         * supported (making it easy to derive from this class to add new kinds of conversion).
289         */
290        @Converter
291        public DOMSource toDOMSource(Source source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
292            if (source instanceof DOMSource) {
293                return (DOMSource) source;
294            } else if (source instanceof SAXSource) {
295                return toDOMSourceFromSAX((SAXSource) source);
296            } else if (source instanceof StreamSource) {
297                return toDOMSourceFromStream((StreamSource) source);
298            } else if (source instanceof StAXSource) {
299                return toDOMSourceFromStAX((StAXSource)source);
300            } else {
301                return null;
302            }
303        }
304    
305        /**
306         * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
307         * supported (making it easy to derive from this class to add new kinds of conversion).
308         */
309        @Converter
310        public DOMSource toDOMSource(String text) throws ParserConfigurationException, IOException, SAXException, TransformerException {
311            Source source = toSource(text);
312            if (source != null) {
313                return toDOMSourceFromStream((StreamSource) source);
314            } else {
315                return null;
316            }
317        }
318    
319        /**
320         * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
321         * supported (making it easy to derive from this class to add new kinds of conversion).
322         */
323        @Converter
324        public DOMSource toDOMSource(byte[] bytes) throws IOException, SAXException, ParserConfigurationException {
325            InputStream is = new ByteArrayInputStream(bytes);
326            try {
327                return toDOMSource(is);
328            } finally {
329                IOHelper.close(is);
330            }
331        }
332    
333    
334        /**
335         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
336         * supported (making it easy to derive from this class to add new kinds of conversion).
337         *
338         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
339         */
340        @Deprecated
341        public SAXSource toSAXSource(String source) throws IOException, SAXException, TransformerException {
342            return toSAXSource(source, null);
343        }
344        
345        /**
346         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
347         * supported (making it easy to derive from this class to add new kinds of conversion).
348         */
349        @Converter
350        public SAXSource toSAXSource(String source, Exchange exchange) throws IOException, SAXException, TransformerException {
351            return toSAXSource(toSource(source), exchange);
352        }
353    
354        /**
355         * Converts the source instance to a {@link StAXSource} or returns null if the conversion is not
356         * supported (making it easy to derive from this class to add new kinds of conversion).
357         * @throws XMLStreamException 
358         */
359        @Converter
360        public StAXSource toStAXSource(String source, Exchange exchange) throws XMLStreamException {
361            XMLStreamReader r = new StaxConverter().createXMLStreamReader(new StringReader(source));
362            return new StAXSource(r);
363        }    
364        
365        /**
366         * Converts the source instance to a {@link StAXSource} 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         * @throws XMLStreamException
369         */
370        @Converter
371        public StAXSource toStAXSource(byte[] in, Exchange exchange) throws XMLStreamException {
372            XMLStreamReader r = new StaxConverter().createXMLStreamReader(new ByteArrayInputStream(in), exchange);
373            return new StAXSource(r);
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         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
381         */
382        @Deprecated
383        public SAXSource toSAXSource(InputStream source) throws IOException, SAXException, TransformerException {
384            return toSAXSource(source, null);
385        }
386        
387        /**
388         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
389         * supported (making it easy to derive from this class to add new kinds of conversion).
390         */
391        @Converter
392        public SAXSource toSAXSource(InputStream source, Exchange exchange) throws IOException, SAXException, TransformerException {
393            return toSAXSource(toStreamSource(source), exchange);
394        }
395    
396        /**
397         * Converts the source instance to a {@link SAXSource} 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         */
400        @Converter
401        public SAXSource toSAXSource(byte[] in, Exchange exchange) throws IOException, SAXException, TransformerException {
402            return toSAXSource(toStreamSource(in, exchange), exchange);
403        }
404    
405        /**
406         * Converts the source instance to a {@link StAXSource} or returns null if the conversion is not
407         * supported (making it easy to derive from this class to add new kinds of conversion).
408         * @throws XMLStreamException 
409         */
410        @Converter
411        public StAXSource toStAXSource(InputStream source, Exchange exchange) throws XMLStreamException {
412            XMLStreamReader r = new StaxConverter().createXMLStreamReader(source, exchange);
413            return new StAXSource(r);
414        }
415        
416        /**
417         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
418         * supported (making it easy to derive from this class to add new kinds of conversion).
419         */
420        @Converter
421        public SAXSource toSAXSource(File file, Exchange exchange) throws IOException, SAXException, TransformerException {
422            InputStream is = IOHelper.buffered(new FileInputStream(file));
423            return toSAXSource(is, exchange);
424        }
425    
426        /**
427         * Converts the source instance to a {@link StAXSource} or returns null if the conversion is not
428         * supported (making it easy to derive from this class to add new kinds of conversion).
429         * @throws FileNotFoundException 
430         * @throws XMLStreamException 
431         */
432        @Converter
433        public StAXSource toStAXSource(File file, Exchange exchange) throws FileNotFoundException, XMLStreamException {
434            InputStream is = IOHelper.buffered(new FileInputStream(file));
435            XMLStreamReader r = new StaxConverter().createXMLStreamReader(is, exchange);
436            return new StAXSource(r);
437        }
438    
439        /**
440         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
441         * supported (making it easy to derive from this class to add new kinds of conversion).
442         *
443         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
444         */
445        @Deprecated
446        public SAXSource toSAXSource(Source source) throws IOException, SAXException, TransformerException {
447            return toSAXSource(source, null);
448        }
449        
450        /**
451         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
452         * supported (making it easy to derive from this class to add new kinds of conversion).
453         */
454        @Converter
455        public SAXSource toSAXSource(Source source, Exchange exchange) throws IOException, SAXException, TransformerException {
456            if (source instanceof SAXSource) {
457                return (SAXSource) source;
458            } else if (source instanceof DOMSource) {
459                return toSAXSourceFromDOM((DOMSource) source, exchange);
460            } else if (source instanceof StreamSource) {
461                return toSAXSourceFromStream((StreamSource) source);
462            } else if (source instanceof StAXSource) {
463                return toSAXSourceFromStAX((StAXSource) source, exchange);
464            } else {
465                return null;
466            }
467        }
468    
469        /**
470         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
471         */
472        @Deprecated
473        public StreamSource toStreamSource(Source source) throws TransformerException {
474            return toStreamSource(source, null);
475        }
476        
477        @Converter
478        public StreamSource toStreamSource(Source source, Exchange exchange) throws TransformerException {
479            if (source instanceof StreamSource) {
480                return (StreamSource) source;
481            } else if (source instanceof DOMSource) {
482                return toStreamSourceFromDOM((DOMSource) source, exchange);
483            } else if (source instanceof SAXSource) {
484                return toStreamSourceFromSAX((SAXSource) source, exchange);
485            } else if (source instanceof StAXSource) {
486                return toStreamSourceFromStAX((StAXSource) source, exchange);
487            } else {
488                return null;
489            }
490        }
491    
492        @Converter
493        public StreamSource toStreamSource(InputStream in) throws TransformerException {
494            return new StreamSource(in);
495        }
496    
497        @Converter
498        public StreamSource toStreamSource(Reader in) throws TransformerException {
499            return new StreamSource(in);
500        }
501    
502        @Converter
503        public StreamSource toStreamSource(File in) throws TransformerException {
504            return new StreamSource(in);
505        }
506    
507        @Converter
508        public StreamSource toStreamSource(byte[] in, Exchange exchange) throws TransformerException {
509            InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, exchange, in);
510            return new StreamSource(is);
511        }
512    
513        @Converter
514        public StreamSource toStreamSource(ByteBuffer in, Exchange exchange) throws TransformerException {
515            InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, exchange, in);
516            return new StreamSource(is);
517        }
518    
519        /**
520         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
521         */
522        @Deprecated
523        public StreamSource toStreamSourceFromSAX(SAXSource source) throws TransformerException {
524            return toStreamSourceFromSAX(source, null);
525        }
526        
527        @Converter
528        public StreamSource toStreamSourceFromSAX(SAXSource source, Exchange exchange) throws TransformerException {
529            InputSource inputSource = source.getInputSource();
530            if (inputSource != null) {
531                if (inputSource.getCharacterStream() != null) {
532                    return new StreamSource(inputSource.getCharacterStream());
533                }
534                if (inputSource.getByteStream() != null) {
535                    return new StreamSource(inputSource.getByteStream());
536                }
537            }
538            String result = toString(source, exchange);
539            return new StringSource(result);
540        }
541    
542        /**
543         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
544         */
545        @Deprecated
546        public StreamSource toStreamSourceFromDOM(DOMSource source) throws TransformerException {
547            return toStreamSourceFromDOM(source, null);
548        }
549        
550        @Converter
551        public StreamSource toStreamSourceFromDOM(DOMSource source, Exchange exchange) throws TransformerException {
552            String result = toString(source, exchange);
553            return new StringSource(result);
554        }
555        @Converter
556        public StreamSource toStreamSourceFromStAX(StAXSource source, Exchange exchange) throws TransformerException {
557            String result = toString(source, exchange);
558            return new StringSource(result);
559        }
560    
561        @Converter
562        public SAXSource toSAXSourceFromStream(StreamSource source) {
563            InputSource inputSource;
564            if (source.getReader() != null) {
565                inputSource = new InputSource(source.getReader());
566            } else {
567                inputSource = new InputSource(source.getInputStream());
568            }
569            inputSource.setSystemId(source.getSystemId());
570            inputSource.setPublicId(source.getPublicId());
571            return new SAXSource(inputSource);
572        }
573    
574        /**
575         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
576         */
577        @Deprecated
578        public Reader toReaderFromSource(Source src) throws TransformerException {
579            return toReaderFromSource(src, null);
580        }
581        
582        @Converter
583        public Reader toReaderFromSource(Source src, Exchange exchange) throws TransformerException {
584            StreamSource stSrc = toStreamSource(src, exchange);
585            Reader r = stSrc.getReader();
586            if (r == null) {
587                r = new InputStreamReader(stSrc.getInputStream());
588            }
589            return r;
590        }
591    
592        @Converter
593        public DOMSource toDOMSource(InputStream is) throws ParserConfigurationException, IOException, SAXException {
594            InputSource source = new InputSource(is);
595            String systemId = source.getSystemId();
596            DocumentBuilder builder = createDocumentBuilder();
597            Document document = builder.parse(source);
598            return new DOMSource(document, systemId);
599        }
600    
601        @Converter
602        public DOMSource toDOMSource(File file) throws ParserConfigurationException, IOException, SAXException {
603            InputStream is = IOHelper.buffered(new FileInputStream(file));
604            return toDOMSource(is);
605        }
606    
607        @Converter
608        public DOMSource toDOMSourceFromStream(StreamSource source) throws ParserConfigurationException, IOException, SAXException {
609            Document document;
610            String systemId = source.getSystemId();
611    
612            DocumentBuilder builder = createDocumentBuilder();
613            Reader reader = source.getReader();
614            if (reader != null) {
615                document = builder.parse(new InputSource(reader));
616            } else {
617                InputStream inputStream = source.getInputStream();
618                if (inputStream != null) {
619                    InputSource inputsource = new InputSource(inputStream);
620                    inputsource.setSystemId(systemId);
621                    document = builder.parse(inputsource);
622                } else {
623                    throw new IOException("No input stream or reader available on StreamSource: " + source);
624                }
625            }
626            return new DOMSource(document, systemId);
627        }
628        
629        /**
630         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
631         */
632        @Deprecated
633        public SAXSource toSAXSourceFromDOM(DOMSource source) throws TransformerException {
634            return toSAXSourceFromDOM(source, null);
635        }
636        
637        @Converter
638        public SAXSource toSAXSourceFromDOM(DOMSource source, Exchange exchange) throws TransformerException {
639            String str = toString(source, exchange);
640            StringReader reader = new StringReader(str);
641            return new SAXSource(new InputSource(reader));
642        }
643    
644        @Converter
645        public SAXSource toSAXSourceFromStAX(StAXSource source, Exchange exchange) throws TransformerException {
646            String str = toString(source, exchange);
647            StringReader reader = new StringReader(str);
648            return new SAXSource(new InputSource(reader));
649        }
650    
651        @Converter
652        public DOMSource toDOMSourceFromSAX(SAXSource source) throws IOException, SAXException, ParserConfigurationException, TransformerException {
653            return new DOMSource(toDOMNodeFromSAX(source));
654        }
655    
656        @Converter
657        public DOMSource toDOMSourceFromStAX(StAXSource source) throws IOException, SAXException, ParserConfigurationException, TransformerException {
658            return new DOMSource(toDOMNodeFromStAX(source));
659        }
660    
661        @Converter
662        public Node toDOMNodeFromSAX(SAXSource source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
663            DOMResult result = new DOMResult();
664            toResult(source, result);
665            return result.getNode();
666        }
667    
668        @Converter
669        public Node toDOMNodeFromStAX(StAXSource source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
670            DOMResult result = new DOMResult();
671            toResult(source, result);
672            return result.getNode();
673        }
674        
675        /**
676         * Convert a NodeList consisting of just 1 node to a DOM Node.
677         * @param nl the NodeList
678         * @return the DOM Node
679         */
680        @Converter(allowNull = true)
681        public Node toDOMNodeFromSingleNodeList(NodeList nl) {
682            return nl.getLength() == 1 ? nl.item(0) : null;
683        }
684        
685        /**
686         * Convert a NodeList consisting of just 1 node to a DOM Document.
687         * Cannot convert NodeList with length > 1 because they require a root node.
688         * @param nl the NodeList
689         * @return the DOM Document
690         */
691        @Converter(allowNull = true)
692        public Document toDOMDocumentFromSingleNodeList(NodeList nl) throws ParserConfigurationException, TransformerException {
693            if (nl.getLength() == 1) {
694                return toDOMDocument(nl.item(0));
695            } else if (nl instanceof Node) {
696                // as XML parsers may often have nodes that implement both Node and NodeList then the type converter lookup
697                // may lookup either a type converter from NodeList or Node. So let's fallback and try with Node
698                return toDOMDocument((Node) nl);
699            } else {
700                return null;
701            }
702        }
703    
704        /**
705         * Converts the given TRaX Source into a W3C DOM node
706         */
707        @Converter(allowNull = true)
708        public Node toDOMNode(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
709            DOMSource domSrc = toDOMSource(source);
710            return domSrc != null ? domSrc.getNode() : null;
711        }
712    
713        /**
714         * Create a DOM element from the given source.
715         */
716        @Converter
717        public Element toDOMElement(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
718            Node node = toDOMNode(source);
719            return toDOMElement(node);
720        }
721    
722        /**
723         * Create a DOM element from the DOM node.
724         * Simply cast if the node is an Element, or
725         * return the root element if it is a Document.
726         */
727        @Converter
728        public Element toDOMElement(Node node) throws TransformerException {
729            // If the node is an document, return the root element
730            if (node instanceof Document) {
731                return ((Document) node).getDocumentElement();
732                // If the node is an element, just cast it
733            } else if (node instanceof Element) {
734                return (Element) node;
735                // Other node types are not handled
736            } else {
737                throw new TransformerException("Unable to convert DOM node to an Element");
738            }
739        }
740    
741        /**
742         * Converts the given data to a DOM document
743         *
744         * @param data is the data to be parsed
745         * @return the parsed document
746         */
747        @Converter
748        public Document toDOMDocument(byte[] data) throws IOException, SAXException, ParserConfigurationException {
749            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
750            return documentBuilder.parse(new ByteArrayInputStream(data));
751        }
752    
753        /**
754         * Converts the given {@link InputStream} to a DOM document
755         *
756         * @param in is the data to be parsed
757         * @return the parsed document
758         */
759        @Converter
760        public Document toDOMDocument(InputStream in) throws IOException, SAXException, ParserConfigurationException {
761            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
762            return documentBuilder.parse(in);
763        }
764    
765        /**
766         * Converts the given {@link InputStream} to a DOM document
767         *
768         * @param in is the data to be parsed
769         * @return the parsed document
770         */
771        @Converter
772        public Document toDOMDocument(Reader in) throws IOException, SAXException, ParserConfigurationException {
773            return toDOMDocument(new InputSource(in));
774        }
775    
776        /**
777         * Converts the given {@link InputSource} to a DOM document
778         *
779         * @param in is the data to be parsed
780         * @return the parsed document
781         */
782        @Converter
783        public Document toDOMDocument(InputSource in) throws IOException, SAXException, ParserConfigurationException {
784            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
785            return documentBuilder.parse(in);
786        }
787    
788        /**
789         * Converts the given {@link String} to a DOM document
790         *
791         * @param text is the data to be parsed
792         * @return the parsed document
793         */
794        @Converter
795        public Document toDOMDocument(String text) throws IOException, SAXException, ParserConfigurationException {
796            return toDOMDocument(new StringReader(text));
797        }
798    
799        /**
800         * Converts the given {@link File} to a DOM document
801         *
802         * @param file is the data to be parsed
803         * @return the parsed document
804         */
805        @Converter
806        public Document toDOMDocument(File file) throws IOException, SAXException, ParserConfigurationException {
807            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
808            return documentBuilder.parse(file);
809        }
810    
811        /**
812         * Create a DOM document from the given source.
813         */
814        @Converter
815        public Document toDOMDocument(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
816            Node node = toDOMNode(source);
817            return toDOMDocument(node);
818        }
819    
820        /**
821         * Create a DOM document from the given Node.
822         *
823         * If the node is an document, just cast it, if the node is an root element, retrieve its
824         * owner element or create a new document and import the node.
825         */
826        @Converter
827        public Document toDOMDocument(final Node node) throws ParserConfigurationException, TransformerException {
828            ObjectHelper.notNull(node, "node");
829    
830            // If the node is the document, just cast it
831            if (node instanceof Document) {
832                return (Document) node;
833                // If the node is an element
834            } else if (node instanceof Element) {
835                Element elem = (Element) node;
836                // If this is the root element, return its owner document
837                if (elem.getOwnerDocument().getDocumentElement() == elem) {
838                    return elem.getOwnerDocument();
839                    // else, create a new doc and copy the element inside it
840                } else {
841                    Document doc = createDocument();
842                    // import node must not occur concurrent on the same node (must be its owner)
843                    // so we need to synchronize on it
844                    synchronized (node.getOwnerDocument()) {
845                        doc.appendChild(doc.importNode(node, true));
846                    }
847                    return doc;
848                }
849                // other element types are not handled
850            } else {
851                throw new TransformerException("Unable to convert DOM node to a Document: " + node);
852            }
853        }
854    
855        /**
856         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
857         */
858        @Deprecated
859        public InputStream toInputStream(DOMSource source) throws TransformerException, IOException {
860            return toInputStream(source, null);
861        }
862        
863        @Converter
864        public InputStream toInputStream(DOMSource source, Exchange exchange) throws TransformerException, IOException {
865            return new ByteArrayInputStream(toByteArray(source, exchange));
866        }
867    
868        /**
869         * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
870         */
871        @Deprecated
872        public InputStream toInputStream(Document dom) throws TransformerException, IOException {
873            return toInputStream(dom, null);
874        }
875        
876        @Converter
877        public InputStream toInputStream(Document dom, Exchange exchange) throws TransformerException, IOException {
878            return toInputStream(new DOMSource(dom), exchange);
879        }
880    
881        @Converter
882        public InputSource toInputSource(InputStream is, Exchange exchange) {
883            return new InputSource(is);
884        }
885    
886        @Converter
887        public InputSource toInputSource(File file, Exchange exchange) throws FileNotFoundException {
888            InputStream is = IOHelper.buffered(new FileInputStream(file));
889            return new InputSource(is);
890        }
891    
892        // Properties
893        //-------------------------------------------------------------------------
894    
895        public DocumentBuilderFactory getDocumentBuilderFactory() {
896            if (documentBuilderFactory == null) {
897                documentBuilderFactory = createDocumentBuilderFactory();
898            }
899            return documentBuilderFactory;
900        }
901    
902        public void setDocumentBuilderFactory(DocumentBuilderFactory documentBuilderFactory) {
903            this.documentBuilderFactory = documentBuilderFactory;
904        }
905    
906        public TransformerFactory getTransformerFactory() {
907            if (transformerFactory == null) {
908                transformerFactory = createTransformerFactory();
909            }
910            return transformerFactory;
911        }
912    
913        public void setTransformerFactory(TransformerFactory transformerFactory) {
914            this.transformerFactory = transformerFactory;
915        }
916    
917        // Helper methods
918        //-------------------------------------------------------------------------
919        
920        protected void setupFeatures(DocumentBuilderFactory factory) {
921            Properties properties = System.getProperties();
922            List<String> features = new ArrayList<String>();
923            for (Map.Entry<Object, Object> prop : properties.entrySet()) {
924                String key = (String) prop.getKey();
925                if (key.startsWith(XmlConverter.DOCUMENT_BUILDER_FACTORY_FEATURE)) {
926                    String uri = ObjectHelper.after(key, ":");
927                    Boolean value = Boolean.valueOf((String)prop.getValue());
928                    try {
929                        factory.setFeature(uri, value);
930                        features.add("feature " + uri + " value " + value);
931                    } catch (ParserConfigurationException e) {
932                        LOG.warn("DocumentBuilderFactory doesn't support the feature {} with value {}, due to {}.", new Object[]{uri, value, e});
933                    }
934                }
935            }
936            if (features.size() > 0) {
937                StringBuffer featureString = new StringBuffer();
938                // just log the configured feature
939                for (String feature : features) {
940                    if (featureString.length() != 0) {
941                        featureString.append(", ");
942                    }
943                    featureString.append(feature);
944                }
945                LOG.info("DocumentBuilderFactory has been set with features {{}}.", featureString.toString());
946            }
947            
948        }
949    
950        public DocumentBuilderFactory createDocumentBuilderFactory() {
951            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
952            factory.setNamespaceAware(true);
953            factory.setIgnoringElementContentWhitespace(true);
954            factory.setIgnoringComments(true);
955            try {
956                // Disable the external-general-entities by default
957                factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
958            } catch (ParserConfigurationException e) {
959                LOG.warn("DocumentBuilderFactory doesn't support the feature {} with value {}, due to {}."
960                         , new Object[]{"http://xml.org/sax/features/external-general-entities", true, e});
961            }
962            // setup the feature from the system property
963            setupFeatures(factory);
964            return factory;
965        }
966    
967        public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
968            DocumentBuilderFactory factory = getDocumentBuilderFactory();
969            return factory.newDocumentBuilder();
970        }
971    
972        public Document createDocument() throws ParserConfigurationException {
973            DocumentBuilder builder = createDocumentBuilder();
974            return builder.newDocument();
975        }
976    
977        /**
978         * @deprecated use {@link #createTransformer}, will be removed in Camel 3.0
979         */
980        @Deprecated
981        public Transformer createTransfomer() throws TransformerConfigurationException {
982            return createTransformer();
983        }
984    
985        public Transformer createTransformer() throws TransformerConfigurationException {
986            TransformerFactory factory = getTransformerFactory();
987            return factory.newTransformer();
988        }
989    
990        public TransformerFactory createTransformerFactory() {
991            TransformerFactory factory = TransformerFactory.newInstance();
992            // Enable the Security feature by default
993            try {
994                factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
995            } catch (TransformerConfigurationException e) {
996                LOG.warn("TransformerFactory doesn't support the feature {} with value {}, due to {}.", new Object[]{javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, "true", e});
997            }
998            factory.setErrorListener(new XmlErrorListener());
999            return factory;
1000        }
1001    
1002    }