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