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.File;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.io.InputStreamReader;
024    import java.io.Reader;
025    import java.io.StringReader;
026    import java.io.StringWriter;
027    import java.lang.reflect.Constructor;
028    import java.nio.ByteBuffer;
029    import java.util.Properties;
030    
031    import javax.xml.parsers.DocumentBuilder;
032    import javax.xml.parsers.DocumentBuilderFactory;
033    import javax.xml.parsers.ParserConfigurationException;
034    import javax.xml.transform.OutputKeys;
035    import javax.xml.transform.Result;
036    import javax.xml.transform.Source;
037    import javax.xml.transform.Transformer;
038    import javax.xml.transform.TransformerConfigurationException;
039    import javax.xml.transform.TransformerException;
040    import javax.xml.transform.TransformerFactory;
041    import javax.xml.transform.dom.DOMResult;
042    import javax.xml.transform.dom.DOMSource;
043    import javax.xml.transform.sax.SAXSource;
044    import javax.xml.transform.stream.StreamResult;
045    import javax.xml.transform.stream.StreamSource;
046    
047    import org.w3c.dom.Document;
048    import org.w3c.dom.Element;
049    import org.w3c.dom.Node;
050    import org.xml.sax.InputSource;
051    import org.xml.sax.SAXException;
052    import org.xml.sax.XMLReader;
053    
054    import org.apache.camel.Converter;
055    import org.apache.camel.Exchange;
056    import org.apache.camel.util.ObjectHelper;
057    
058    /**
059     * A helper class to transform to and from various JAXB types such as {@link Source} and {@link Document}
060     *
061     * @version $Revision: 787735 $
062     */
063    @Converter
064    public class XmlConverter {
065        public static final String DEFAULT_CHARSET_PROPERTY = "org.apache.camel.default.charset";
066    
067        public static String defaultCharset = ObjectHelper.getSystemProperty(DEFAULT_CHARSET_PROPERTY, "UTF-8");
068    
069        /*
070         * When converting a DOM tree to a SAXSource, we try to use Xalan internal DOM parser if
071         * available. Else, transform the DOM tree to a String and build a SAXSource on top of it.
072         */
073        private static final Class DOM_TO_SAX_CLASS;
074    
075        private DocumentBuilderFactory documentBuilderFactory;
076        private TransformerFactory transformerFactory;
077    
078        static {
079            Class cl = null;
080            try {
081                cl = ObjectHelper.loadClass("org.apache.xalan.xsltc.trax.DOM2SAX");
082            } catch (Exception e) {
083                // ignore
084            }
085            DOM_TO_SAX_CLASS = cl;
086        }
087    
088    
089        public XmlConverter() {
090        }
091    
092        public XmlConverter(DocumentBuilderFactory documentBuilderFactory) {
093            this.documentBuilderFactory = documentBuilderFactory;
094        }
095    
096        /**
097         * Returns the default set of output properties for conversions.
098         */
099        public Properties defaultOutputProperties() {
100            Properties properties = new Properties();
101            properties.put(OutputKeys.ENCODING, defaultCharset);
102            properties.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
103            return properties;
104        }
105    
106        /**
107         * Converts the given input Source into the required result
108         */
109        public void toResult(Source source, Result result) throws TransformerException {
110            toResult(source, result, defaultOutputProperties());
111        }
112    
113        /**
114         * Converts the given input Source into the required result
115         */
116        public void toResult(Source source, Result result, Properties outputProperties) throws TransformerException {
117            if (source == null) {
118                return;
119            }
120    
121            Transformer transformer = createTransfomer();
122            if (transformer == null) {
123                throw new TransformerException("Could not create a transformer - JAXP is misconfigured!");
124            }
125            transformer.setOutputProperties(outputProperties);
126            transformer.transform(source, result);
127        }
128    
129        /**
130         * Converts the given byte[] to a Source
131         */
132        @Converter
133        public BytesSource toSource(byte[] data) {
134            return new BytesSource(data);
135        }
136    
137    
138        /**
139         * Converts the given String to a Source
140         */
141        @Converter
142        public StringSource toSource(String data) {
143            return new StringSource(data);
144        }
145    
146        /**
147         * Converts the given Document to a Source
148         */
149        @Converter
150        public DOMSource toSource(Document document) {
151            return new DOMSource(document);
152        }
153    
154        /**
155         * Converts the given Node to a Source
156         */
157        @Converter
158        public Source toSource(Node node) {
159            return new DOMSource(node);
160        }
161    
162        /**
163         * Converts the given input Source into text
164         */
165        @Converter
166        public String toString(Source source) throws TransformerException {
167            if (source == null) {
168                return null;
169            } else if (source instanceof StringSource) {
170                return ((StringSource) source).getText();
171            } else if (source instanceof BytesSource) {
172                return new String(((BytesSource) source).getData());
173            } else {
174                StringWriter buffer = new StringWriter();
175                toResult(source, new StreamResult(buffer));
176                return buffer.toString();
177            }
178        }
179    
180        /**
181         * Converts the given input Source into bytes
182         */
183        @Converter
184        public byte[] toByteArray(Source source, Exchange exchange) throws TransformerException {
185            String answer = toString(source);
186            if (exchange != null) {
187                return exchange.getContext().getTypeConverter().convertTo(byte[].class, exchange, answer);
188            } else {
189                return answer.getBytes();
190            }
191        }
192    
193        /**
194         * Converts the given input Node into text
195         */
196        @Converter
197        public String toString(Node node) throws TransformerException {
198            return toString(new DOMSource(node));
199        }
200    
201        /**
202         * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
203         * supported (making it easy to derive from this class to add new kinds of conversion).
204         */
205        @Converter
206        public DOMSource toDOMSource(Source source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
207            if (source instanceof DOMSource) {
208                return (DOMSource) source;
209            } else if (source instanceof SAXSource) {
210                return toDOMSourceFromSAX((SAXSource) source);
211            } else if (source instanceof StreamSource) {
212                return toDOMSourceFromStream((StreamSource) source);
213            } else {
214                return null;
215            }
216        }
217    
218        /**
219         * Converts the source instance to a {@link DOMSource} or returns null if the conversion is not
220         * supported (making it easy to derive from this class to add new kinds of conversion).
221         */
222        @Converter
223        public DOMSource toDOMSource(String text) throws ParserConfigurationException, IOException, SAXException, TransformerException {
224            Source source = toSource(text);
225            if (source != null) {
226                return toDOMSourceFromStream((StreamSource) source);
227            } else {
228                return null;
229            }
230        }
231    
232        /**
233         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
234         * supported (making it easy to derive from this class to add new kinds of conversion).
235         */
236        @Converter
237        public SAXSource toSAXSource(String source) throws IOException, SAXException, TransformerException {
238            return toSAXSource(toSource(source));
239        }
240    
241        /**
242         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
243         * supported (making it easy to derive from this class to add new kinds of conversion).
244         */
245        @Converter
246        public SAXSource toSAXSource(InputStream source) throws IOException, SAXException, TransformerException {
247            return toSAXSource(toStreamSource(source));
248        }
249    
250        /**
251         * Converts the source instance to a {@link SAXSource} or returns null if the conversion is not
252         * supported (making it easy to derive from this class to add new kinds of conversion).
253         */
254        @Converter
255        public SAXSource toSAXSource(Source source) throws IOException, SAXException, TransformerException {
256            if (source instanceof SAXSource) {
257                return (SAXSource) source;
258            } else if (source instanceof DOMSource) {
259                return toSAXSourceFromDOM((DOMSource) source);
260            } else if (source instanceof StreamSource) {
261                return toSAXSourceFromStream((StreamSource) source);
262            } else {
263                return null;
264            }
265        }
266    
267        @Converter
268        public StreamSource toStreamSource(Source source) throws TransformerException {
269            if (source instanceof StreamSource) {
270                return (StreamSource) source;
271            } else if (source instanceof DOMSource) {
272                return toStreamSourceFromDOM((DOMSource) source);
273            } else if (source instanceof SAXSource) {
274                return toStreamSourceFromSAX((SAXSource) source);
275            } else {
276                return null;
277            }
278        }
279    
280        @Converter
281        public StreamSource toStreamSource(InputStream in) throws TransformerException {
282            if (in != null) {
283                return new StreamSource(in);
284            }
285            return null;
286        }
287    
288        @Converter
289        public StreamSource toStreamSource(Reader in) throws TransformerException {
290            if (in != null) {
291                return new StreamSource(in);
292            }
293            return null;
294        }
295    
296        @Converter
297        public StreamSource toStreamSource(File in) throws TransformerException {
298            if (in != null) {
299                return new StreamSource(in);
300            }
301            return null;
302        }
303    
304        @Converter
305        public StreamSource toStreamSource(byte[] in, Exchange exchange) throws TransformerException {
306            if (in != null) {
307                InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, in);
308                return new StreamSource(is);
309            }
310            return null;
311        }
312    
313        @Converter
314        public StreamSource toStreamSource(ByteBuffer in, Exchange exchange) throws TransformerException {
315            if (in != null) {
316                InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, in);
317                return new StreamSource(is);
318            }
319            return null;
320        }
321    
322        @Converter
323        public StreamSource toStreamSourceFromSAX(SAXSource source) throws TransformerException {
324            InputSource inputSource = source.getInputSource();
325            if (inputSource != null) {
326                if (inputSource.getCharacterStream() != null) {
327                    return new StreamSource(inputSource.getCharacterStream());
328                }
329                if (inputSource.getByteStream() != null) {
330                    return new StreamSource(inputSource.getByteStream());
331                }
332            }
333            String result = toString(source);
334            return new StringSource(result);
335        }
336    
337        @Converter
338        public StreamSource toStreamSourceFromDOM(DOMSource source) throws TransformerException {
339            String result = toString(source);
340            return new StringSource(result);
341        }
342    
343        @Converter
344        public SAXSource toSAXSourceFromStream(StreamSource source) {
345            InputSource inputSource;
346            if (source.getReader() != null) {
347                inputSource = new InputSource(source.getReader());
348            } else {
349                inputSource = new InputSource(source.getInputStream());
350            }
351            inputSource.setSystemId(source.getSystemId());
352            inputSource.setPublicId(source.getPublicId());
353            return new SAXSource(inputSource);
354        }
355    
356        @Converter
357        public Reader toReaderFromSource(Source src) throws TransformerException {
358            StreamSource stSrc = toStreamSource(src);
359            Reader r = stSrc.getReader();
360            if (r == null) {
361                r = new InputStreamReader(stSrc.getInputStream());
362            }
363            return r;
364        }
365    
366        @Converter
367        public DOMSource toDOMSource(InputStream is) throws ParserConfigurationException, IOException, SAXException {
368            InputSource source = new InputSource(is);
369            String systemId = source.getSystemId();
370            DocumentBuilder builder = createDocumentBuilder();
371            Document document = builder.parse(source);
372            return new DOMSource(document, systemId);
373        }
374    
375        @Converter
376        public DOMSource toDOMSourceFromStream(StreamSource source) throws ParserConfigurationException, IOException, SAXException {
377            DocumentBuilder builder = createDocumentBuilder();
378            String systemId = source.getSystemId();
379            Document document = null;
380            Reader reader = source.getReader();
381            if (reader != null) {
382                document = builder.parse(new InputSource(reader));
383            } else {
384                InputStream inputStream = source.getInputStream();
385                if (inputStream != null) {
386                    InputSource inputsource = new InputSource(inputStream);
387                    inputsource.setSystemId(systemId);
388                    document = builder.parse(inputsource);
389                } else {
390                    throw new IOException("No input stream or reader available");
391                }
392            }
393            return new DOMSource(document, systemId);
394        }
395    
396        @Converter
397        public SAXSource toSAXSourceFromDOM(DOMSource source) throws TransformerException {
398            if (DOM_TO_SAX_CLASS != null) {
399                try {
400                    Constructor cns = DOM_TO_SAX_CLASS.getConstructor(Node.class);
401                    XMLReader converter = (XMLReader) cns.newInstance(source.getNode());
402                    return new SAXSource(converter, new InputSource());
403                } catch (Exception e) {
404                    throw new TransformerException(e);
405                }
406            } else {
407                String str = toString(source);
408                StringReader reader = new StringReader(str);
409                return new SAXSource(new InputSource(reader));
410            }
411        }
412    
413        @Converter
414        public DOMSource toDOMSourceFromSAX(SAXSource source) throws IOException, SAXException, ParserConfigurationException, TransformerException {
415            return new DOMSource(toDOMNodeFromSAX(source));
416        }
417    
418        @Converter
419        public Node toDOMNodeFromSAX(SAXSource source) throws ParserConfigurationException, IOException, SAXException, TransformerException {
420            DOMResult result = new DOMResult();
421            toResult(source, result);
422            return result.getNode();
423        }
424    
425        /**
426         * Converts the given TRaX Source into a W3C DOM node
427         */
428        @Converter
429        public Node toDOMNode(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
430            DOMSource domSrc = toDOMSource(source);
431            return domSrc != null ? domSrc.getNode() : null;
432        }
433    
434        /**
435         * Create a DOM element from the given source.
436         */
437        @Converter
438        public Element toDOMElement(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
439            Node node = toDOMNode(source);
440            return toDOMElement(node);
441        }
442    
443        /**
444         * Create a DOM element from the DOM node.
445         * Simply cast if the node is an Element, or
446         * return the root element if it is a Document.
447         */
448        @Converter
449        public Element toDOMElement(Node node) throws TransformerException {
450            // If the node is an document, return the root element
451            if (node instanceof Document) {
452                return ((Document) node).getDocumentElement();
453                // If the node is an element, just cast it
454            } else if (node instanceof Element) {
455                return (Element) node;
456                // Other node types are not handled
457            } else {
458                throw new TransformerException("Unable to convert DOM node to an Element");
459            }
460        }
461    
462        /**
463         * Converts the given data to a DOM document
464         *
465         * @param data is the data to be parsed
466         * @return the parsed document
467         */
468        @Converter
469        public Document toDOMDocument(byte[] data) throws IOException, SAXException, ParserConfigurationException {
470            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
471            return documentBuilder.parse(new ByteArrayInputStream(data));
472        }
473    
474        /**
475         * Converts the given {@link InputStream} to a DOM document
476         *
477         * @param in is the data to be parsed
478         * @return the parsed document
479         */
480        @Converter
481        public Document toDOMDocument(InputStream in) throws IOException, SAXException, ParserConfigurationException {
482            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
483            return documentBuilder.parse(in);
484        }
485    
486        /**
487         * Converts the given {@link InputStream} to a DOM document
488         *
489         * @param in is the data to be parsed
490         * @return the parsed document
491         */
492        @Converter
493        public Document toDOMDocument(Reader in) throws IOException, SAXException, ParserConfigurationException {
494            return toDOMDocument(new InputSource(in));
495        }
496    
497        /**
498         * Converts the given {@link InputSource} to a DOM document
499         *
500         * @param in is the data to be parsed
501         * @return the parsed document
502         */
503        @Converter
504        public Document toDOMDocument(InputSource in) throws IOException, SAXException, ParserConfigurationException {
505            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
506            return documentBuilder.parse(in);
507        }
508    
509        /**
510         * Converts the given {@link String} to a DOM document
511         *
512         * @param text is the data to be parsed
513         * @return the parsed document
514         */
515        @Converter
516        public Document toDOMDocument(String text) throws IOException, SAXException, ParserConfigurationException {
517            return toDOMDocument(new StringReader(text));
518        }
519    
520        /**
521         * Converts the given {@link File} to a DOM document
522         *
523         * @param file is the data to be parsed
524         * @return the parsed document
525         */
526        @Converter
527        public Document toDOMDocument(File file) throws IOException, SAXException, ParserConfigurationException {
528            DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
529            return documentBuilder.parse(file);
530        }
531    
532        /**
533         * Create a DOM document from the given source.
534         */
535        @Converter
536        public Document toDOMDocument(Source source) throws TransformerException, ParserConfigurationException, IOException, SAXException {
537            Node node = toDOMNode(source);
538            return toDOMDocument(node);
539        }
540    
541        /**
542         * Create a DOM document from the given Node.
543         * If the node is an document, just cast it,
544         * if the node is an root element, retrieve its
545         * owner element or create a new document and import
546         * the node.
547         */
548        @Converter
549        public Document toDOMDocument(Node node) throws ParserConfigurationException, TransformerException {
550            // If the node is the document, just cast it
551            if (node instanceof Document) {
552                return (Document) node;
553                // If the node is an element
554            } else if (node instanceof Element) {
555                Element elem = (Element) node;
556                // If this is the root element, return its owner document
557                if (elem.getOwnerDocument().getDocumentElement() == elem) {
558                    return elem.getOwnerDocument();
559                    // else, create a new doc and copy the element inside it
560                } else {
561                    Document doc = createDocument();
562                    doc.appendChild(doc.importNode(node, true));
563                    return doc;
564                }
565                // other element types are not handled
566            } else {
567                throw new TransformerException("Unable to convert DOM node to a Document");
568            }
569        }
570    
571        // Properties
572        //-------------------------------------------------------------------------
573        public DocumentBuilderFactory getDocumentBuilderFactory() {
574            if (documentBuilderFactory == null) {
575                documentBuilderFactory = createDocumentBuilderFactory();
576            }
577            return documentBuilderFactory;
578        }
579    
580        public void setDocumentBuilderFactory(DocumentBuilderFactory documentBuilderFactory) {
581            this.documentBuilderFactory = documentBuilderFactory;
582        }
583    
584    
585        // Helper methods
586        //-------------------------------------------------------------------------
587        public DocumentBuilderFactory createDocumentBuilderFactory() {
588            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
589            factory.setNamespaceAware(true);
590            factory.setIgnoringElementContentWhitespace(true);
591            factory.setIgnoringComments(true);
592            return factory;
593        }
594    
595    
596        public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
597            DocumentBuilderFactory factory = getDocumentBuilderFactory();
598            return factory.newDocumentBuilder();
599        }
600    
601        public Document createDocument() throws ParserConfigurationException {
602            DocumentBuilder builder = createDocumentBuilder();
603            return builder.newDocument();
604        }
605    
606        public TransformerFactory getTransformerFactory() {
607            if (transformerFactory == null) {
608                transformerFactory = createTransformerFactory();
609            }
610            return transformerFactory;
611        }
612    
613        public void setTransformerFactory(TransformerFactory transformerFactory) {
614            this.transformerFactory = transformerFactory;
615        }
616    
617        public Transformer createTransfomer() throws TransformerConfigurationException {
618            TransformerFactory factory = getTransformerFactory();
619            return factory.newTransformer();
620        }
621    
622        public TransformerFactory createTransformerFactory() {
623            return TransformerFactory.newInstance();
624        }
625    
626    }