001 /* $Id: Digester.java 729350 2008-12-24 18:22:28Z rahul $ 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one or more 004 * contributor license agreements. See the NOTICE file distributed with 005 * this work for additional information regarding copyright ownership. 006 * The ASF licenses this file to You under the Apache License, Version 2.0 007 * (the "License"); you may not use this file except in compliance with 008 * the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.commons.digester; 020 021 022 import java.io.File; 023 import java.io.FileInputStream; 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.io.Reader; 027 import java.lang.reflect.InvocationTargetException; 028 import java.net.MalformedURLException; 029 import java.net.URL; 030 import java.net.URLConnection; 031 import java.util.ArrayList; 032 import java.util.EmptyStackException; 033 import java.util.HashMap; 034 import java.util.List; 035 import java.util.Map; 036 import java.util.Properties; 037 import java.util.Stack; 038 039 import javax.xml.parsers.ParserConfigurationException; 040 import javax.xml.parsers.SAXParser; 041 import javax.xml.parsers.SAXParserFactory; 042 import javax.xml.validation.Schema; 043 044 import org.apache.commons.logging.Log; 045 import org.apache.commons.logging.LogFactory; 046 import org.xml.sax.Attributes; 047 import org.xml.sax.ContentHandler; 048 import org.xml.sax.EntityResolver; 049 import org.xml.sax.ErrorHandler; 050 import org.xml.sax.InputSource; 051 import org.xml.sax.Locator; 052 import org.xml.sax.SAXException; 053 import org.xml.sax.SAXNotRecognizedException; 054 import org.xml.sax.SAXNotSupportedException; 055 import org.xml.sax.SAXParseException; 056 import org.xml.sax.XMLReader; 057 import org.xml.sax.helpers.DefaultHandler; 058 059 060 061 062 /** 063 * <p>A <strong>Digester</strong> processes an XML input stream by matching a 064 * series of element nesting patterns to execute Rules that have been added 065 * prior to the start of parsing.</p> 066 * 067 * <p>See the <a href="package-summary.html#package_description">Digester 068 * Developer Guide</a> for more information.</p> 069 * 070 * <p><strong>IMPLEMENTATION NOTE</strong> - A single Digester instance may 071 * only be used within the context of a single thread at a time, and a call 072 * to <code>parse()</code> must be completed before another can be initiated 073 * even from the same thread.</p> 074 * 075 * <p>A Digester instance should not be used for parsing more than one input 076 * document. The problem is that the Digester class has quite a few member 077 * variables whose values "evolve" as SAX events are received during a parse. 078 * When reusing the Digester instance, all these members must be reset back 079 * to their initial states before the second parse begins. The "clear()" 080 * method makes a stab at resetting these, but it is actually rather a 081 * difficult problem. If you are determined to reuse Digester instances, then 082 * at the least you should call the clear() method before each parse, and must 083 * call it if the Digester parse terminates due to an exception during a parse. 084 * </p> 085 * 086 * <p><strong>LEGACY IMPLEMENTATION NOTE</strong> - When using the legacy XML 087 * schema support (instead of using the {@link Schema} class), a bug in 088 * Xerces 2.0.2 prevents the support of XML schema. You need Xerces 2.1/2.3 089 * and up to make this class work with the legacy XML schema support.</p> 090 * 091 * <p>This package was inspired by the <code>XmlMapper</code> class that was 092 * part of Tomcat 3.0 and 3.1, but is organized somewhat differently.</p> 093 */ 094 095 public class Digester extends DefaultHandler { 096 097 098 // --------------------------------------------------------- Constructors 099 100 101 /** 102 * Construct a new Digester with default properties. 103 */ 104 public Digester() { 105 106 super(); 107 108 } 109 110 111 /** 112 * Construct a new Digester, allowing a SAXParser to be passed in. This 113 * allows Digester to be used in environments which are unfriendly to 114 * JAXP1.1 (such as WebLogic 6.0). This may help in places where 115 * you are able to load JAXP 1.1 classes yourself. 116 */ 117 public Digester(SAXParser parser) { 118 119 super(); 120 121 this.parser = parser; 122 123 } 124 125 126 /** 127 * Construct a new Digester, allowing an XMLReader to be passed in. This 128 * allows Digester to be used in environments which are unfriendly to 129 * JAXP1.1 (such as WebLogic 6.0). Note that if you use this option you 130 * have to configure namespace and validation support yourself, as these 131 * properties only affect the SAXParser and emtpy constructor. 132 */ 133 public Digester(XMLReader reader) { 134 135 super(); 136 137 this.reader = reader; 138 139 } 140 141 142 // --------------------------------------------------- Instance Variables 143 144 145 /** 146 * The body text of the current element. 147 */ 148 protected StringBuffer bodyText = new StringBuffer(); 149 150 151 /** 152 * The stack of body text string buffers for surrounding elements. 153 */ 154 protected Stack<StringBuffer> bodyTexts = new Stack<StringBuffer>(); 155 156 157 /** 158 * Stack whose elements are List objects, each containing a list of 159 * Rule objects as returned from Rules.getMatch(). As each xml element 160 * in the input is entered, the matching rules are pushed onto this 161 * stack. After the end tag is reached, the matches are popped again. 162 * The depth of is stack is therefore exactly the same as the current 163 * "nesting" level of the input xml. 164 * 165 * @since 1.6 166 */ 167 protected Stack<List<Rule>> matches = new Stack<List<Rule>>(); 168 169 /** 170 * The class loader to use for instantiating application objects. 171 * If not specified, the context class loader, or the class loader 172 * used to load Digester itself, is used, based on the value of the 173 * <code>useContextClassLoader</code> variable. 174 */ 175 protected ClassLoader classLoader = null; 176 177 178 /** 179 * Has this Digester been configured yet. 180 */ 181 protected boolean configured = false; 182 183 184 /** 185 * The EntityResolver used by the SAX parser. By default it use this class 186 */ 187 protected EntityResolver entityResolver; 188 189 /** 190 * The URLs of entityValidator that have been registered, keyed by the public 191 * identifier that corresponds. 192 */ 193 protected HashMap<String, URL> entityValidator = new HashMap<String, URL>(); 194 195 196 /** 197 * The application-supplied error handler that is notified when parsing 198 * warnings, errors, or fatal errors occur. 199 */ 200 protected ErrorHandler errorHandler = null; 201 202 203 /** 204 * The SAXParserFactory that is created the first time we need it. 205 */ 206 protected SAXParserFactory factory = null; 207 208 /** 209 * @deprecated This is now managed by {@link ParserFeatureSetterFactory} 210 */ 211 protected String JAXP_SCHEMA_LANGUAGE = 212 "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; 213 214 215 /** 216 * The Locator associated with our parser. 217 */ 218 protected Locator locator = null; 219 220 221 /** 222 * The current match pattern for nested element processing. 223 */ 224 protected String match = ""; 225 226 227 /** 228 * Do we want a "namespace aware" parser. 229 */ 230 protected boolean namespaceAware = false; 231 232 233 /** 234 * Registered namespaces we are currently processing. The key is the 235 * namespace prefix that was declared in the document. The value is an 236 * Stack of the namespace URIs this prefix has been mapped to -- 237 * the top Stack element is the most current one. (This architecture 238 * is required because documents can declare nested uses of the same 239 * prefix for different Namespace URIs). 240 */ 241 protected HashMap<String, Stack<String>> namespaces = new HashMap<String, Stack<String>>(); 242 243 244 /** 245 * Do we want a "XInclude aware" parser. 246 */ 247 protected boolean xincludeAware = false; 248 249 250 /** 251 * The parameters stack being utilized by CallMethodRule and 252 * CallParamRule rules. 253 * 254 * @since 2.0 255 */ 256 protected Stack<Object> params = new Stack<Object>(); 257 258 259 /** 260 * The SAXParser we will use to parse the input stream. 261 */ 262 protected SAXParser parser = null; 263 264 265 /** 266 * The public identifier of the DTD we are currently parsing under 267 * (if any). 268 */ 269 protected String publicId = null; 270 271 272 /** 273 * The XMLReader used to parse digester rules. 274 */ 275 protected XMLReader reader = null; 276 277 278 /** 279 * The "root" element of the stack (in other words, the last object 280 * that was popped. 281 */ 282 protected Object root = null; 283 284 285 /** 286 * The <code>Rules</code> implementation containing our collection of 287 * <code>Rule</code> instances and associated matching policy. If not 288 * established before the first rule is added, a default implementation 289 * will be provided. 290 */ 291 protected Rules rules = null; 292 293 /** 294 * The XML schema language to use for validating an XML instance. By 295 * default this value is set to <code>W3C_XML_SCHEMA</code> 296 * 297 * @deprecated Use {@link Schema} support instead. 298 */ 299 protected String schemaLanguage = W3C_XML_SCHEMA; 300 301 302 /** 303 * The XML schema to use for validating an XML instance. 304 * 305 * @deprecated Use {@link Schema} support instead. 306 */ 307 protected String schemaLocation = null; 308 309 310 /** 311 * The XML schema to use for validating an XML instance. 312 * 313 * @since 2.0 314 */ 315 protected Schema schema = null; 316 317 318 /** 319 * The object stack being constructed. 320 */ 321 protected Stack<Object> stack = new Stack<Object>(); 322 323 324 /** 325 * Do we want to use the Context ClassLoader when loading classes 326 * for instantiating new objects. Default is <code>false</code>. 327 */ 328 protected boolean useContextClassLoader = false; 329 330 331 /** 332 * Do we want to use a validating parser. 333 */ 334 protected boolean validating = false; 335 336 337 /** 338 * The Log to which most logging calls will be made. 339 */ 340 protected Log log = 341 LogFactory.getLog("org.apache.commons.digester.Digester"); 342 343 344 /** 345 * The Log to which all SAX event related logging calls will be made. 346 */ 347 protected Log saxLog = 348 LogFactory.getLog("org.apache.commons.digester.Digester.sax"); 349 350 351 /** 352 * The schema language supported. By default, we use this one. 353 */ 354 protected static final String W3C_XML_SCHEMA = 355 "http://www.w3.org/2001/XMLSchema"; 356 357 /** 358 * An optional class that substitutes values in attributes and body text. 359 * This may be null and so a null check is always required before use. 360 */ 361 protected Substitutor substitutor; 362 363 /** Stacks used for interrule communication, indexed by name String */ 364 private HashMap<String, Stack<Object>> stacksByName = new HashMap<String, Stack<Object>>(); 365 366 /** 367 * If not null, then calls by the parser to this object's characters, 368 * startElement, endElement and processingInstruction methods are 369 * forwarded to the specified object. This is intended to allow rules 370 * to temporarily "take control" of the sax events. In particular, 371 * this is used by NodeCreateRule. 372 * <p> 373 * See setCustomContentHandler. 374 */ 375 private ContentHandler customContentHandler = null; 376 377 /** 378 * Object which will receive callbacks for every pop/push action 379 * on the default stack or named stacks. 380 */ 381 private StackAction stackAction = null; 382 383 // ------------------------------------------------------------- Properties 384 385 /** 386 * Return the currently mapped namespace URI for the specified prefix, 387 * if any; otherwise return <code>null</code>. These mappings come and 388 * go dynamically as the document is parsed. 389 * 390 * @param prefix Prefix to look up 391 */ 392 public String findNamespaceURI(String prefix) { 393 394 Stack<String> nsStack = namespaces.get(prefix); 395 if (nsStack == null) { 396 return null; 397 } 398 try { 399 return (nsStack.peek()); 400 } catch (EmptyStackException e) { 401 return null; 402 } 403 404 } 405 406 407 /** 408 * Return the class loader to be used for instantiating application objects 409 * when required. This is determined based upon the following rules: 410 * <ul> 411 * <li>The class loader set by <code>setClassLoader()</code>, if any</li> 412 * <li>The thread context class loader, if it exists and the 413 * <code>useContextClassLoader</code> property is set to true</li> 414 * <li>The class loader used to load the Digester class itself. 415 * </ul> 416 */ 417 public ClassLoader getClassLoader() { 418 419 if (this.classLoader != null) { 420 return (this.classLoader); 421 } 422 if (this.useContextClassLoader) { 423 ClassLoader classLoader = 424 Thread.currentThread().getContextClassLoader(); 425 if (classLoader != null) { 426 return (classLoader); 427 } 428 } 429 return (this.getClass().getClassLoader()); 430 431 } 432 433 434 /** 435 * Set the class loader to be used for instantiating application objects 436 * when required. 437 * 438 * @param classLoader The new class loader to use, or <code>null</code> 439 * to revert to the standard rules 440 */ 441 public void setClassLoader(ClassLoader classLoader) { 442 443 this.classLoader = classLoader; 444 445 } 446 447 448 /** 449 * Return the current depth of the element stack. 450 */ 451 public int getCount() { 452 453 return (stack.size()); 454 455 } 456 457 458 /** 459 * Return the name of the XML element that is currently being processed. 460 */ 461 public String getCurrentElementName() { 462 463 String elementName = match; 464 int lastSlash = elementName.lastIndexOf('/'); 465 if (lastSlash >= 0) { 466 elementName = elementName.substring(lastSlash + 1); 467 } 468 return (elementName); 469 470 } 471 472 473 /** 474 * Return the debugging detail level of our currently enabled logger. 475 * 476 * @deprecated This method now always returns 0. Digester uses the apache 477 * jakarta commons-logging library; see the documentation for that library 478 * for more information. 479 */ 480 public int getDebug() { 481 482 return (0); 483 484 } 485 486 487 /** 488 * Set the debugging detail level of our currently enabled logger. 489 * 490 * @param debug New debugging detail level (0=off, increasing integers 491 * for more detail) 492 * 493 * @deprecated This method now has no effect at all. Digester uses 494 * the apache jakarta comons-logging library; see the documentation 495 * for that library for more information. 496 */ 497 public void setDebug(int debug) { 498 499 ; // No action is taken 500 501 } 502 503 504 /** 505 * Return the error handler for this Digester. 506 */ 507 public ErrorHandler getErrorHandler() { 508 509 return (this.errorHandler); 510 511 } 512 513 514 /** 515 * Set the error handler for this Digester. 516 * 517 * @param errorHandler The new error handler 518 */ 519 public void setErrorHandler(ErrorHandler errorHandler) { 520 521 this.errorHandler = errorHandler; 522 523 } 524 525 526 /** 527 * Return the SAXParserFactory we will use, creating one if necessary. 528 */ 529 public SAXParserFactory getFactory() { 530 531 if (factory == null) { 532 factory = SAXParserFactory.newInstance(); 533 factory.setNamespaceAware(namespaceAware); 534 factory.setXIncludeAware(xincludeAware); 535 factory.setValidating(validating); 536 factory.setSchema(schema); 537 } 538 return (factory); 539 540 } 541 542 543 /** 544 * Returns a flag indicating whether the requested feature is supported 545 * by the underlying implementation of <code>org.xml.sax.XMLReader</code>. 546 * See <a href="http://www.saxproject.org">the saxproject website</a> 547 * for information about the standard SAX2 feature flags. 548 * 549 * @param feature Name of the feature to inquire about 550 * 551 * @exception ParserConfigurationException if a parser configuration error 552 * occurs 553 * @exception SAXNotRecognizedException if the property name is 554 * not recognized 555 * @exception SAXNotSupportedException if the property name is 556 * recognized but not supported 557 */ 558 public boolean getFeature(String feature) 559 throws ParserConfigurationException, SAXNotRecognizedException, 560 SAXNotSupportedException { 561 562 return (getFactory().getFeature(feature)); 563 564 } 565 566 567 /** 568 * Sets a flag indicating whether the requested feature is supported 569 * by the underlying implementation of <code>org.xml.sax.XMLReader</code>. 570 * See <a href="http://www.saxproject.org">the saxproject website</a> 571 * for information about the standard SAX2 feature flags. In order to be 572 * effective, this method must be called <strong>before</strong> the 573 * <code>getParser()</code> method is called for the first time, either 574 * directly or indirectly. 575 * 576 * @param feature Name of the feature to set the status for 577 * @param value The new value for this feature 578 * 579 * @exception ParserConfigurationException if a parser configuration error 580 * occurs 581 * @exception SAXNotRecognizedException if the property name is 582 * not recognized 583 * @exception SAXNotSupportedException if the property name is 584 * recognized but not supported 585 */ 586 public void setFeature(String feature, boolean value) 587 throws ParserConfigurationException, SAXNotRecognizedException, 588 SAXNotSupportedException { 589 590 getFactory().setFeature(feature, value); 591 592 } 593 594 595 /** 596 * Return the current Logger associated with this instance of the Digester 597 */ 598 public Log getLogger() { 599 600 return log; 601 602 } 603 604 605 /** 606 * Set the current logger for this Digester. 607 */ 608 public void setLogger(Log log) { 609 610 this.log = log; 611 612 } 613 614 /** 615 * Gets the logger used for logging SAX-related information. 616 * <strong>Note</strong> the output is finely grained. 617 * 618 * @since 1.6 619 */ 620 public Log getSAXLogger() { 621 622 return saxLog; 623 } 624 625 626 /** 627 * Sets the logger used for logging SAX-related information. 628 * <strong>Note</strong> the output is finely grained. 629 * @param saxLog Log, not null 630 * 631 * @since 1.6 632 */ 633 public void setSAXLogger(Log saxLog) { 634 635 this.saxLog = saxLog; 636 } 637 638 /** 639 * Return the current rule match path 640 */ 641 public String getMatch() { 642 643 return match; 644 645 } 646 647 648 /** 649 * Return the "namespace aware" flag for parsers we create. 650 */ 651 public boolean getNamespaceAware() { 652 653 return (this.namespaceAware); 654 655 } 656 657 658 /** 659 * Set the "namespace aware" flag for parsers we create. 660 * 661 * @param namespaceAware The new "namespace aware" flag 662 */ 663 public void setNamespaceAware(boolean namespaceAware) { 664 665 this.namespaceAware = namespaceAware; 666 667 } 668 669 670 /** 671 * Return the XInclude-aware flag for parsers we create. XInclude 672 * functionality additionally requires namespace-awareness. 673 * 674 * @return The XInclude-aware flag 675 * 676 * @see #getNamespaceAware() 677 * 678 * @since 2.0 679 */ 680 public boolean getXIncludeAware() { 681 682 return (this.xincludeAware); 683 684 } 685 686 687 /** 688 * Set the XInclude-aware flag for parsers we create. This additionally 689 * requires namespace-awareness. 690 * 691 * @param xincludeAware The new XInclude-aware flag 692 * 693 * @see #setNamespaceAware(boolean) 694 * 695 * @since 2.0 696 */ 697 public void setXIncludeAware(boolean xincludeAware) { 698 699 this.xincludeAware = xincludeAware; 700 701 } 702 703 704 /** 705 * Set the publid id of the current file being parse. 706 * @param publicId the DTD/Schema public's id. 707 */ 708 public void setPublicId(String publicId){ 709 this.publicId = publicId; 710 } 711 712 713 /** 714 * Return the public identifier of the DTD we are currently 715 * parsing under, if any. 716 */ 717 public String getPublicId() { 718 719 return (this.publicId); 720 721 } 722 723 724 /** 725 * Return the namespace URI that will be applied to all subsequently 726 * added <code>Rule</code> objects. 727 */ 728 public String getRuleNamespaceURI() { 729 730 return (getRules().getNamespaceURI()); 731 732 } 733 734 735 /** 736 * Set the namespace URI that will be applied to all subsequently 737 * added <code>Rule</code> objects. 738 * 739 * @param ruleNamespaceURI Namespace URI that must match on all 740 * subsequently added rules, or <code>null</code> for matching 741 * regardless of the current namespace URI 742 */ 743 public void setRuleNamespaceURI(String ruleNamespaceURI) { 744 745 getRules().setNamespaceURI(ruleNamespaceURI); 746 747 } 748 749 750 /** 751 * Return the SAXParser we will use to parse the input stream. If there 752 * is a problem creating the parser, return <code>null</code>. 753 */ 754 public SAXParser getParser() { 755 756 // Return the parser we already created (if any) 757 if (parser != null) { 758 return (parser); 759 } 760 761 // Create a new parser 762 try { 763 if (validating && (schemaLocation != null)) { 764 // There is no portable way to specify the location of 765 // an xml schema to be applied to the input document, so 766 // we have to use parser-specific code for this. That code 767 // is hidden behind the ParserFeatureSetterFactory class. 768 769 // The above has changed in JDK 1.5 and no longer true. The 770 // functionality used in this block has now been deprecated. 771 // We now use javax.xml.validation.Schema instead. 772 773 Properties properties = new Properties(); 774 properties.put("SAXParserFactory", getFactory()); 775 if (schemaLocation != null) { 776 properties.put("schemaLocation", schemaLocation); 777 properties.put("schemaLanguage", schemaLanguage); 778 } 779 parser = ParserFeatureSetterFactory.newSAXParser(properties); 780 } else { 781 // The user doesn't want to use any non-portable parsing features, 782 // so we can just use the portable API here. Note that method 783 // getFactory returns a factory already configured with the 784 // appropriate namespaceAware and validating properties. 785 786 parser = getFactory().newSAXParser(); 787 } 788 } catch (Exception e) { 789 log.error("Digester.getParser: ", e); 790 return (null); 791 } 792 793 return (parser); 794 795 } 796 797 798 /** 799 * Return the current value of the specified property for the underlying 800 * <code>XMLReader</code> implementation. 801 * See <a href="http://www.saxproject.org">the saxproject website</a> 802 * for information about the standard SAX2 properties. 803 * 804 * @param property Property name to be retrieved 805 * 806 * @exception SAXNotRecognizedException if the property name is 807 * not recognized 808 * @exception SAXNotSupportedException if the property name is 809 * recognized but not supported 810 */ 811 public Object getProperty(String property) 812 throws SAXNotRecognizedException, SAXNotSupportedException { 813 814 return (getParser().getProperty(property)); 815 816 } 817 818 819 /** 820 * Set the current value of the specified property for the underlying 821 * <code>XMLReader</code> implementation. 822 * See <a href="http://www.saxproject.org">the saxproject website</a> 823 * for information about the standard SAX2 properties. 824 * 825 * @param property Property name to be set 826 * @param value Property value to be set 827 * 828 * @exception SAXNotRecognizedException if the property name is 829 * not recognized 830 * @exception SAXNotSupportedException if the property name is 831 * recognized but not supported 832 */ 833 public void setProperty(String property, Object value) 834 throws SAXNotRecognizedException, SAXNotSupportedException { 835 836 getParser().setProperty(property, value); 837 838 } 839 840 841 /** 842 * By setting the reader in the constructor, you can bypass JAXP and 843 * be able to use digester in Weblogic 6.0. 844 * 845 * @deprecated Use getXMLReader() instead, which can throw a 846 * SAXException if the reader cannot be instantiated 847 */ 848 public XMLReader getReader() { 849 850 try { 851 return (getXMLReader()); 852 } catch (SAXException e) { 853 log.error("Cannot get XMLReader", e); 854 return (null); 855 } 856 857 } 858 859 860 /** 861 * Return the <code>Rules</code> implementation object containing our 862 * rules collection and associated matching policy. If none has been 863 * established, a default implementation will be created and returned. 864 */ 865 public Rules getRules() { 866 867 if (this.rules == null) { 868 this.rules = new RulesBase(); 869 this.rules.setDigester(this); 870 } 871 return (this.rules); 872 873 } 874 875 876 /** 877 * Set the <code>Rules</code> implementation object containing our 878 * rules collection and associated matching policy. 879 * 880 * @param rules New Rules implementation 881 */ 882 public void setRules(Rules rules) { 883 884 this.rules = rules; 885 this.rules.setDigester(this); 886 887 } 888 889 890 /** 891 * Return the XML Schema URI used for validating an XML instance. 892 * 893 * @deprecated Use {@link Schema} for validation instead. 894 * @see #getXMLSchema() 895 * @see #setXMLSchema(Schema) 896 */ 897 public String getSchema() { 898 899 return (this.schemaLocation); 900 901 } 902 903 904 /** 905 * Set the XML Schema URI used for validating the input XML. 906 * <p> 907 * It is often desirable to <i>force</i> the input document to be 908 * validated against a particular schema regardless of what type 909 * the input document declares itself to be. This method allows that 910 * to be done. 911 * <p> 912 * Note, however, that there is no standard API for enabling this 913 * feature on the underlying SAX parser; this method therefore only works 914 * for those parsers explicitly supported by Digester's 915 * ParserFeatureSetterFactory class. If the underlying parser does not 916 * support the feature, or is not one of the supported parsers, then 917 * an exception will be thrown when getParser is called (explicitly, 918 * or implicitly via the parse method). 919 * <p> 920 * See also method setSchemaLanguage which allows the type of the schema 921 * specified here to be defined. By default, the schema is expected to 922 * be a W3C xml schema definition. 923 * <p> 924 * IMPORTANT NOTE: This functionality was never very reliable, and has 925 * been horribly broken since the 1.6 release of Digester. There are 926 * currently no plans to fix it, so you are strongly recommended to 927 * avoid using this method. Instead, create an XMLParser instance 928 * yourself, configure validation appropriately, and pass it as a 929 * parameter to the Digester constructor. 930 * 931 * @param schemaLocation a URI to the schema. 932 * @deprecated Use {@link Schema} for validation instead. 933 * @see #getXMLSchema() 934 * @see #setXMLSchema(Schema) 935 */ 936 public void setSchema(String schemaLocation){ 937 938 this.schemaLocation = schemaLocation; 939 940 } 941 942 943 /** 944 * Return the XML Schema language used when parsing. 945 * 946 * @deprecated Use {@link Schema} for validation instead. 947 * @see #getXMLSchema() 948 * @see #setXMLSchema(Schema) 949 */ 950 public String getSchemaLanguage() { 951 952 return (this.schemaLanguage); 953 954 } 955 956 957 /** 958 * Set the XML Schema language used when parsing. By default, we use W3C. 959 * 960 * @param schemaLanguage a URI to the schema language. 961 * @deprecated Use {@link Schema} for validation instead. 962 * @see #getXMLSchema() 963 * @see #setXMLSchema(Schema) 964 */ 965 public void setSchemaLanguage(String schemaLanguage){ 966 967 this.schemaLanguage = schemaLanguage; 968 969 } 970 971 972 /** 973 * Return the XML Schema used when parsing. 974 * 975 * @return The {@link Schema} instance in use. 976 * 977 * @since 2.0 978 */ 979 public Schema getXMLSchema() { 980 981 return (this.schema); 982 983 } 984 985 986 /** 987 * Set the XML Schema to be used when parsing. 988 * 989 * @param schema The {@link Schema} instance to use. 990 * 991 * @since 2.0 992 */ 993 public void setXMLSchema(Schema schema){ 994 995 this.schema = schema; 996 997 } 998 999 1000 /** 1001 * Return the boolean as to whether the context classloader should be used. 1002 */ 1003 public boolean getUseContextClassLoader() { 1004 1005 return useContextClassLoader; 1006 1007 } 1008 1009 1010 /** 1011 * Determine whether to use the Context ClassLoader (the one found by 1012 * calling <code>Thread.currentThread().getContextClassLoader()</code>) 1013 * to resolve/load classes that are defined in various rules. If not 1014 * using Context ClassLoader, then the class-loading defaults to 1015 * using the calling-class' ClassLoader. 1016 * 1017 * @param use determines whether to use Context ClassLoader. 1018 */ 1019 public void setUseContextClassLoader(boolean use) { 1020 1021 useContextClassLoader = use; 1022 1023 } 1024 1025 1026 /** 1027 * Return the validating parser flag. 1028 */ 1029 public boolean getValidating() { 1030 1031 return (this.validating); 1032 1033 } 1034 1035 1036 /** 1037 * Set the validating parser flag. This must be called before 1038 * <code>parse()</code> is called the first time. 1039 * 1040 * @param validating The new validating parser flag. 1041 */ 1042 public void setValidating(boolean validating) { 1043 1044 this.validating = validating; 1045 1046 } 1047 1048 1049 /** 1050 * Return the XMLReader to be used for parsing the input document. 1051 * 1052 * FIX ME: there is a bug in JAXP/XERCES that prevent the use of a 1053 * parser that contains a schema with a DTD. 1054 * @exception SAXException if no XMLReader can be instantiated 1055 */ 1056 public XMLReader getXMLReader() throws SAXException { 1057 if (reader == null){ 1058 reader = getParser().getXMLReader(); 1059 } 1060 1061 reader.setDTDHandler(this); 1062 reader.setContentHandler(this); 1063 1064 if (entityResolver == null){ 1065 reader.setEntityResolver(this); 1066 } else { 1067 reader.setEntityResolver(entityResolver); 1068 } 1069 1070 reader.setErrorHandler(this); 1071 return reader; 1072 } 1073 1074 /** 1075 * Gets the <code>Substitutor</code> used to convert attributes and body text. 1076 * @return Substitutor, null if not substitutions are to be performed. 1077 */ 1078 public Substitutor getSubstitutor() { 1079 return substitutor; 1080 } 1081 1082 /** 1083 * Sets the <code>Substitutor</code> to be used to convert attributes and body text. 1084 * @param substitutor the Substitutor to be used to convert attributes and body text 1085 * or null if not substitution of these values is to be performed. 1086 */ 1087 public void setSubstitutor(Substitutor substitutor) { 1088 this.substitutor = substitutor; 1089 } 1090 1091 /* 1092 * See setCustomContentHandler. 1093 * 1094 * @since 1.7 1095 */ 1096 public ContentHandler getCustomContentHandler() { 1097 return customContentHandler; 1098 } 1099 1100 /** 1101 * Redirects (or cancels redirecting) of SAX ContentHandler events to an 1102 * external object. 1103 * <p> 1104 * When this object's customContentHandler is non-null, any SAX events 1105 * received from the parser will simply be passed on to the specified 1106 * object instead of this object handling them. This allows Rule classes 1107 * to take control of the SAX event stream for a while in order to do 1108 * custom processing. Such a rule should save the old value before setting 1109 * a new one, and restore the old value in order to resume normal digester 1110 * processing. 1111 * <p> 1112 * An example of a Rule which needs this feature is NodeCreateRule. 1113 * <p> 1114 * Note that saving the old value is probably not needed as it should always 1115 * be null; a custom rule that wants to take control could only have been 1116 * called when there was no custom content handler. But it seems cleaner 1117 * to properly save/restore the value and maybe some day this will come in 1118 * useful. 1119 * <p> 1120 * Note also that this is not quite equivalent to 1121 * <pre> 1122 * digester.getXMLReader().setContentHandler(handler) 1123 * </pre> 1124 * for these reasons: 1125 * <ul> 1126 * <li>Some xml parsers don't like having setContentHandler called after 1127 * parsing has started. The Aelfred parser is one example.</li> 1128 * <li>Directing the events via the Digester object potentially allows 1129 * us to log information about those SAX events at the digester level.</li> 1130 * </ul> 1131 * 1132 * @since 1.7 1133 */ 1134 public void setCustomContentHandler(ContentHandler handler) { 1135 customContentHandler = handler; 1136 } 1137 1138 /** 1139 * Define a callback object which is invoked whever an object is pushed onto 1140 * a digester object stack, or popped off one. 1141 * 1142 * @since 1.8 1143 */ 1144 public void setStackAction(StackAction stackAction) { 1145 this.stackAction = stackAction; 1146 } 1147 1148 /** 1149 * See setStackAction. 1150 * 1151 * @since 1.8 1152 */ 1153 public StackAction getStackAction() { 1154 return stackAction; 1155 } 1156 1157 /** 1158 * Get the most current namespaces for all prefixes. 1159 * 1160 * @return Map A map with namespace prefixes as keys and most current 1161 * namespace URIs for the corresponding prefixes as values 1162 * 1163 * @since 1.8 1164 */ 1165 public Map<String, String> getCurrentNamespaces() { 1166 if (!namespaceAware) { 1167 log.warn("Digester is not namespace aware"); 1168 } 1169 Map<String, String> currentNamespaces = new HashMap<String, String>(); 1170 for (Map.Entry<String, Stack<String>> nsEntry : namespaces.entrySet()) { 1171 try { 1172 currentNamespaces.put(nsEntry.getKey(), 1173 nsEntry.getValue().peek()); 1174 } catch (RuntimeException e) { 1175 // rethrow, after logging 1176 log.error(e.getMessage(), e); 1177 throw e; 1178 } 1179 } 1180 return currentNamespaces; 1181 } 1182 1183 // ------------------------------------------------- ContentHandler Methods 1184 1185 1186 /** 1187 * Process notification of character data received from the body of 1188 * an XML element. 1189 * 1190 * @param buffer The characters from the XML document 1191 * @param start Starting offset into the buffer 1192 * @param length Number of characters from the buffer 1193 * 1194 * @exception SAXException if a parsing error is to be reported 1195 */ 1196 public void characters(char buffer[], int start, int length) 1197 throws SAXException { 1198 1199 if (customContentHandler != null) { 1200 // forward calls instead of handling them here 1201 customContentHandler.characters(buffer, start, length); 1202 return; 1203 } 1204 1205 if (saxLog.isDebugEnabled()) { 1206 saxLog.debug("characters(" + new String(buffer, start, length) + ")"); 1207 } 1208 1209 bodyText.append(buffer, start, length); 1210 1211 } 1212 1213 1214 /** 1215 * Process notification of the end of the document being reached. 1216 * 1217 * @exception SAXException if a parsing error is to be reported 1218 */ 1219 public void endDocument() throws SAXException { 1220 1221 if (saxLog.isDebugEnabled()) { 1222 if (getCount() > 1) { 1223 saxLog.debug("endDocument(): " + getCount() + 1224 " elements left"); 1225 } else { 1226 saxLog.debug("endDocument()"); 1227 } 1228 } 1229 1230 // Fire "finish" events for all defined rules 1231 for (Rule rule : getRules().rules()) { 1232 try { 1233 rule.finish(); 1234 } catch (Exception e) { 1235 log.error("Finish event threw exception", e); 1236 throw createSAXException(e); 1237 } catch (Error e) { 1238 log.error("Finish event threw error", e); 1239 throw e; 1240 } 1241 } 1242 1243 // Perform final cleanup 1244 clear(); 1245 1246 } 1247 1248 1249 /** 1250 * Process notification of the end of an XML element being reached. 1251 * 1252 * @param namespaceURI - The Namespace URI, or the empty string if the 1253 * element has no Namespace URI or if Namespace processing is not 1254 * being performed. 1255 * @param localName - The local name (without prefix), or the empty 1256 * string if Namespace processing is not being performed. 1257 * @param qName - The qualified XML 1.0 name (with prefix), or the 1258 * empty string if qualified names are not available. 1259 * @exception SAXException if a parsing error is to be reported 1260 */ 1261 public void endElement(String namespaceURI, String localName, 1262 String qName) throws SAXException { 1263 1264 if (customContentHandler != null) { 1265 // forward calls instead of handling them here 1266 customContentHandler.endElement(namespaceURI, localName, qName); 1267 return; 1268 } 1269 1270 boolean debug = log.isDebugEnabled(); 1271 1272 if (debug) { 1273 if (saxLog.isDebugEnabled()) { 1274 saxLog.debug("endElement(" + namespaceURI + "," + localName + 1275 "," + qName + ")"); 1276 } 1277 log.debug(" match='" + match + "'"); 1278 log.debug(" bodyText='" + bodyText + "'"); 1279 } 1280 1281 // the actual element name is either in localName or qName, depending 1282 // on whether the parser is namespace aware 1283 String name = localName; 1284 if ((name == null) || (name.length() < 1)) { 1285 name = qName; 1286 } 1287 1288 // Fire "body" events for all relevant rules 1289 List<Rule> rules = matches.pop(); 1290 if ((rules != null) && (rules.size() > 0)) { 1291 String bodyText = this.bodyText.toString(); 1292 Substitutor substitutor = getSubstitutor(); 1293 if (substitutor!= null) { 1294 bodyText = substitutor.substitute(bodyText); 1295 } 1296 for (int i = 0; i < rules.size(); i++) { 1297 try { 1298 Rule rule = rules.get(i); 1299 if (debug) { 1300 log.debug(" Fire body() for " + rule); 1301 } 1302 rule.body(namespaceURI, name, bodyText); 1303 } catch (Exception e) { 1304 log.error("Body event threw exception", e); 1305 throw createSAXException(e); 1306 } catch (Error e) { 1307 log.error("Body event threw error", e); 1308 throw e; 1309 } 1310 } 1311 } else { 1312 if (debug) { 1313 log.debug(" No rules found matching '" + match + "'."); 1314 } 1315 } 1316 1317 // Recover the body text from the surrounding element 1318 bodyText = bodyTexts.pop(); 1319 if (debug) { 1320 log.debug(" Popping body text '" + bodyText.toString() + "'"); 1321 } 1322 1323 // Fire "end" events for all relevant rules in reverse order 1324 if (rules != null) { 1325 for (int i = 0; i < rules.size(); i++) { 1326 int j = (rules.size() - i) - 1; 1327 try { 1328 Rule rule = rules.get(j); 1329 if (debug) { 1330 log.debug(" Fire end() for " + rule); 1331 } 1332 rule.end(namespaceURI, name); 1333 } catch (Exception e) { 1334 log.error("End event threw exception", e); 1335 throw createSAXException(e); 1336 } catch (Error e) { 1337 log.error("End event threw error", e); 1338 throw e; 1339 } 1340 } 1341 } 1342 1343 // Recover the previous match expression 1344 int slash = match.lastIndexOf('/'); 1345 if (slash >= 0) { 1346 match = match.substring(0, slash); 1347 } else { 1348 match = ""; 1349 } 1350 1351 } 1352 1353 1354 /** 1355 * Process notification that a namespace prefix is going out of scope. 1356 * 1357 * @param prefix Prefix that is going out of scope 1358 * 1359 * @exception SAXException if a parsing error is to be reported 1360 */ 1361 public void endPrefixMapping(String prefix) throws SAXException { 1362 1363 if (saxLog.isDebugEnabled()) { 1364 saxLog.debug("endPrefixMapping(" + prefix + ")"); 1365 } 1366 1367 // Deregister this prefix mapping 1368 Stack<String> stack = namespaces.get(prefix); 1369 if (stack == null) { 1370 return; 1371 } 1372 try { 1373 stack.pop(); 1374 if (stack.empty()) 1375 namespaces.remove(prefix); 1376 } catch (EmptyStackException e) { 1377 throw createSAXException("endPrefixMapping popped too many times"); 1378 } 1379 1380 } 1381 1382 1383 /** 1384 * Process notification of ignorable whitespace received from the body of 1385 * an XML element. 1386 * 1387 * @param buffer The characters from the XML document 1388 * @param start Starting offset into the buffer 1389 * @param len Number of characters from the buffer 1390 * 1391 * @exception SAXException if a parsing error is to be reported 1392 */ 1393 public void ignorableWhitespace(char buffer[], int start, int len) 1394 throws SAXException { 1395 1396 if (saxLog.isDebugEnabled()) { 1397 saxLog.debug("ignorableWhitespace(" + 1398 new String(buffer, start, len) + ")"); 1399 } 1400 1401 ; // No processing required 1402 1403 } 1404 1405 1406 /** 1407 * Process notification of a processing instruction that was encountered. 1408 * 1409 * @param target The processing instruction target 1410 * @param data The processing instruction data (if any) 1411 * 1412 * @exception SAXException if a parsing error is to be reported 1413 */ 1414 public void processingInstruction(String target, String data) 1415 throws SAXException { 1416 1417 if (customContentHandler != null) { 1418 // forward calls instead of handling them here 1419 customContentHandler.processingInstruction(target, data); 1420 return; 1421 } 1422 1423 if (saxLog.isDebugEnabled()) { 1424 saxLog.debug("processingInstruction('" + target + "','" + data + "')"); 1425 } 1426 1427 ; // No processing is required 1428 1429 } 1430 1431 1432 /** 1433 * Gets the document locator associated with our parser. 1434 * 1435 * @return the Locator supplied by the document parser 1436 */ 1437 public Locator getDocumentLocator() { 1438 1439 return locator; 1440 1441 } 1442 1443 /** 1444 * Sets the document locator associated with our parser. 1445 * 1446 * @param locator The new locator 1447 */ 1448 public void setDocumentLocator(Locator locator) { 1449 1450 if (saxLog.isDebugEnabled()) { 1451 saxLog.debug("setDocumentLocator(" + locator + ")"); 1452 } 1453 1454 this.locator = locator; 1455 1456 } 1457 1458 1459 /** 1460 * Process notification of a skipped entity. 1461 * 1462 * @param name Name of the skipped entity 1463 * 1464 * @exception SAXException if a parsing error is to be reported 1465 */ 1466 public void skippedEntity(String name) throws SAXException { 1467 1468 if (saxLog.isDebugEnabled()) { 1469 saxLog.debug("skippedEntity(" + name + ")"); 1470 } 1471 1472 ; // No processing required 1473 1474 } 1475 1476 1477 /** 1478 * Process notification of the beginning of the document being reached. 1479 * 1480 * @exception SAXException if a parsing error is to be reported 1481 */ 1482 public void startDocument() throws SAXException { 1483 1484 if (saxLog.isDebugEnabled()) { 1485 saxLog.debug("startDocument()"); 1486 } 1487 1488 // ensure that the digester is properly configured, as 1489 // the digester could be used as a SAX ContentHandler 1490 // rather than via the parse() methods. 1491 configure(); 1492 } 1493 1494 1495 /** 1496 * Process notification of the start of an XML element being reached. 1497 * 1498 * @param namespaceURI The Namespace URI, or the empty string if the element 1499 * has no Namespace URI or if Namespace processing is not being performed. 1500 * @param localName The local name (without prefix), or the empty 1501 * string if Namespace processing is not being performed. 1502 * @param qName The qualified name (with prefix), or the empty 1503 * string if qualified names are not available.\ 1504 * @param list The attributes attached to the element. If there are 1505 * no attributes, it shall be an empty Attributes object. 1506 * @exception SAXException if a parsing error is to be reported 1507 */ 1508 public void startElement(String namespaceURI, String localName, 1509 String qName, Attributes list) 1510 throws SAXException { 1511 boolean debug = log.isDebugEnabled(); 1512 1513 if (customContentHandler != null) { 1514 // forward calls instead of handling them here 1515 customContentHandler.startElement(namespaceURI, localName, qName, list); 1516 return; 1517 } 1518 1519 if (saxLog.isDebugEnabled()) { 1520 saxLog.debug("startElement(" + namespaceURI + "," + localName + "," + 1521 qName + ")"); 1522 } 1523 1524 // Save the body text accumulated for our surrounding element 1525 bodyTexts.push(bodyText); 1526 if (debug) { 1527 log.debug(" Pushing body text '" + bodyText.toString() + "'"); 1528 } 1529 bodyText = new StringBuffer(); 1530 1531 // the actual element name is either in localName or qName, depending 1532 // on whether the parser is namespace aware 1533 String name = localName; 1534 if ((name == null) || (name.length() < 1)) { 1535 name = qName; 1536 } 1537 1538 // Compute the current matching rule 1539 StringBuffer sb = new StringBuffer(match); 1540 if (match.length() > 0) { 1541 sb.append('/'); 1542 } 1543 sb.append(name); 1544 match = sb.toString(); 1545 if (debug) { 1546 log.debug(" New match='" + match + "'"); 1547 } 1548 1549 // Fire "begin" events for all relevant rules 1550 List<Rule> rules = getRules().match(namespaceURI, match); 1551 matches.push(rules); 1552 if ((rules != null) && (rules.size() > 0)) { 1553 Substitutor substitutor = getSubstitutor(); 1554 if (substitutor!= null) { 1555 list = substitutor.substitute(list); 1556 } 1557 for (int i = 0; i < rules.size(); i++) { 1558 try { 1559 Rule rule = rules.get(i); 1560 if (debug) { 1561 log.debug(" Fire begin() for " + rule); 1562 } 1563 rule.begin(namespaceURI, name, list); 1564 } catch (Exception e) { 1565 log.error("Begin event threw exception", e); 1566 throw createSAXException(e); 1567 } catch (Error e) { 1568 log.error("Begin event threw error", e); 1569 throw e; 1570 } 1571 } 1572 } else { 1573 if (debug) { 1574 log.debug(" No rules found matching '" + match + "'."); 1575 } 1576 } 1577 1578 } 1579 1580 1581 /** 1582 * Process notification that a namespace prefix is coming in to scope. 1583 * 1584 * @param prefix Prefix that is being declared 1585 * @param namespaceURI Corresponding namespace URI being mapped to 1586 * 1587 * @exception SAXException if a parsing error is to be reported 1588 */ 1589 public void startPrefixMapping(String prefix, String namespaceURI) 1590 throws SAXException { 1591 1592 if (saxLog.isDebugEnabled()) { 1593 saxLog.debug("startPrefixMapping(" + prefix + "," + namespaceURI + ")"); 1594 } 1595 1596 // Register this prefix mapping 1597 Stack<String> stack = namespaces.get(prefix); 1598 if (stack == null) { 1599 stack = new Stack<String>(); 1600 namespaces.put(prefix, stack); 1601 } 1602 stack.push(namespaceURI); 1603 1604 } 1605 1606 1607 // ----------------------------------------------------- DTDHandler Methods 1608 1609 1610 /** 1611 * Receive notification of a notation declaration event. 1612 * 1613 * @param name The notation name 1614 * @param publicId The public identifier (if any) 1615 * @param systemId The system identifier (if any) 1616 */ 1617 public void notationDecl(String name, String publicId, String systemId) { 1618 1619 if (saxLog.isDebugEnabled()) { 1620 saxLog.debug("notationDecl(" + name + "," + publicId + "," + 1621 systemId + ")"); 1622 } 1623 1624 } 1625 1626 1627 /** 1628 * Receive notification of an unparsed entity declaration event. 1629 * 1630 * @param name The unparsed entity name 1631 * @param publicId The public identifier (if any) 1632 * @param systemId The system identifier (if any) 1633 * @param notation The name of the associated notation 1634 */ 1635 public void unparsedEntityDecl(String name, String publicId, 1636 String systemId, String notation) { 1637 1638 if (saxLog.isDebugEnabled()) { 1639 saxLog.debug("unparsedEntityDecl(" + name + "," + publicId + "," + 1640 systemId + "," + notation + ")"); 1641 } 1642 1643 } 1644 1645 1646 // ----------------------------------------------- EntityResolver Methods 1647 1648 /** 1649 * Set the <code>EntityResolver</code> used by SAX when resolving 1650 * public id and system id. 1651 * This must be called before the first call to <code>parse()</code>. 1652 * @param entityResolver a class that implement the <code>EntityResolver</code> interface. 1653 */ 1654 public void setEntityResolver(EntityResolver entityResolver){ 1655 this.entityResolver = entityResolver; 1656 } 1657 1658 1659 /** 1660 * Return the Entity Resolver used by the SAX parser. 1661 * @return Return the Entity Resolver used by the SAX parser. 1662 */ 1663 public EntityResolver getEntityResolver(){ 1664 return entityResolver; 1665 } 1666 1667 /** 1668 * Resolve the requested external entity. 1669 * 1670 * @param publicId The public identifier of the entity being referenced 1671 * @param systemId The system identifier of the entity being referenced 1672 * 1673 * @exception SAXException if a parsing exception occurs 1674 * 1675 */ 1676 public InputSource resolveEntity(String publicId, String systemId) 1677 throws SAXException { 1678 1679 if (saxLog.isDebugEnabled()) { 1680 saxLog.debug("resolveEntity('" + publicId + "', '" + systemId + "')"); 1681 } 1682 1683 if (publicId != null) 1684 this.publicId = publicId; 1685 1686 // Has this system identifier been registered? 1687 URL entityURL = null; 1688 if (publicId != null) { 1689 entityURL = entityValidator.get(publicId); 1690 } 1691 1692 // Redirect the schema location to a local destination 1693 if (schemaLocation != null && entityURL == null && systemId != null){ 1694 entityURL = entityValidator.get(systemId); 1695 } 1696 1697 if (entityURL == null) { 1698 if (systemId == null) { 1699 // cannot resolve 1700 if (log.isDebugEnabled()) { 1701 log.debug(" Cannot resolve null entity, returning null InputSource"); 1702 } 1703 return (null); 1704 1705 } else { 1706 // try to resolve using system ID 1707 if (log.isDebugEnabled()) { 1708 log.debug(" Trying to resolve using system ID '" + systemId + "'"); 1709 } 1710 try { 1711 entityURL = new URL(systemId); 1712 } catch (MalformedURLException e) { 1713 throw new IllegalArgumentException("Malformed URL '" + systemId 1714 + "' : " + e.getMessage()); 1715 } 1716 } 1717 } 1718 1719 // Return an input source to our alternative URL 1720 if (log.isDebugEnabled()) { 1721 log.debug(" Resolving to alternate DTD '" + entityURL + "'"); 1722 } 1723 1724 try { 1725 return createInputSourceFromURL(entityURL); 1726 } catch (Exception e) { 1727 throw createSAXException(e); 1728 } 1729 } 1730 1731 1732 // ------------------------------------------------- ErrorHandler Methods 1733 1734 1735 /** 1736 * Forward notification of a parsing error to the application supplied 1737 * error handler (if any). 1738 * 1739 * @param exception The error information 1740 * 1741 * @exception SAXException if a parsing exception occurs 1742 */ 1743 public void error(SAXParseException exception) throws SAXException { 1744 1745 log.error("Parse Error at line " + exception.getLineNumber() + 1746 " column " + exception.getColumnNumber() + ": " + 1747 exception.getMessage(), exception); 1748 if (errorHandler != null) { 1749 errorHandler.error(exception); 1750 } 1751 1752 } 1753 1754 1755 /** 1756 * Forward notification of a fatal parsing error to the application 1757 * supplied error handler (if any). 1758 * 1759 * @param exception The fatal error information 1760 * 1761 * @exception SAXException if a parsing exception occurs 1762 */ 1763 public void fatalError(SAXParseException exception) throws SAXException { 1764 1765 log.error("Parse Fatal Error at line " + exception.getLineNumber() + 1766 " column " + exception.getColumnNumber() + ": " + 1767 exception.getMessage(), exception); 1768 if (errorHandler != null) { 1769 errorHandler.fatalError(exception); 1770 } 1771 1772 } 1773 1774 1775 /** 1776 * Forward notification of a parse warning to the application supplied 1777 * error handler (if any). 1778 * 1779 * @param exception The warning information 1780 * 1781 * @exception SAXException if a parsing exception occurs 1782 */ 1783 public void warning(SAXParseException exception) throws SAXException { 1784 if (errorHandler != null) { 1785 log.warn("Parse Warning Error at line " + exception.getLineNumber() + 1786 " column " + exception.getColumnNumber() + ": " + 1787 exception.getMessage(), exception); 1788 1789 errorHandler.warning(exception); 1790 } 1791 1792 } 1793 1794 1795 // ------------------------------------------------------- Public Methods 1796 1797 1798 /** 1799 * Log a message to our associated logger. 1800 * 1801 * @param message The message to be logged 1802 * @deprecated Call getLogger() and use it's logging methods 1803 */ 1804 public void log(String message) { 1805 1806 log.info(message); 1807 1808 } 1809 1810 1811 /** 1812 * Log a message and exception to our associated logger. 1813 * 1814 * @param message The message to be logged 1815 * @deprecated Call getLogger() and use it's logging methods 1816 */ 1817 public void log(String message, Throwable exception) { 1818 1819 log.error(message, exception); 1820 1821 } 1822 1823 1824 /** 1825 * Parse the content of the specified file using this Digester. Returns 1826 * the root element from the object stack (if any). 1827 * 1828 * @param file File containing the XML data to be parsed 1829 * 1830 * @exception IOException if an input/output error occurs 1831 * @exception SAXException if a parsing exception occurs 1832 */ 1833 public Object parse(File file) throws IOException, SAXException { 1834 1835 if (file == null) { 1836 throw new IllegalArgumentException("File to parse is null"); 1837 } 1838 1839 configure(); 1840 InputSource input = new InputSource(new FileInputStream(file)); 1841 input.setSystemId(file.toURI().toURL().toString()); 1842 getXMLReader().parse(input); 1843 cleanup(); 1844 return (root); 1845 1846 } 1847 /** 1848 * Parse the content of the specified input source using this Digester. 1849 * Returns the root element from the object stack (if any). 1850 * 1851 * @param input Input source containing the XML data to be parsed 1852 * 1853 * @exception IOException if an input/output error occurs 1854 * @exception SAXException if a parsing exception occurs 1855 */ 1856 public Object parse(InputSource input) throws IOException, SAXException { 1857 1858 if (input == null) { 1859 throw new IllegalArgumentException("InputSource to parse is null"); 1860 } 1861 1862 configure(); 1863 getXMLReader().parse(input); 1864 cleanup(); 1865 return (root); 1866 1867 } 1868 1869 1870 /** 1871 * Parse the content of the specified input stream using this Digester. 1872 * Returns the root element from the object stack (if any). 1873 * 1874 * @param input Input stream containing the XML data to be parsed 1875 * 1876 * @exception IOException if an input/output error occurs 1877 * @exception SAXException if a parsing exception occurs 1878 */ 1879 public Object parse(InputStream input) throws IOException, SAXException { 1880 1881 if (input == null) { 1882 throw new IllegalArgumentException("InputStream to parse is null"); 1883 } 1884 1885 configure(); 1886 InputSource is = new InputSource(input); 1887 getXMLReader().parse(is); 1888 cleanup(); 1889 return (root); 1890 1891 } 1892 1893 1894 /** 1895 * Parse the content of the specified reader using this Digester. 1896 * Returns the root element from the object stack (if any). 1897 * 1898 * @param reader Reader containing the XML data to be parsed 1899 * 1900 * @exception IOException if an input/output error occurs 1901 * @exception SAXException if a parsing exception occurs 1902 */ 1903 public Object parse(Reader reader) throws IOException, SAXException { 1904 1905 if (reader == null) { 1906 throw new IllegalArgumentException("Reader to parse is null"); 1907 } 1908 1909 configure(); 1910 InputSource is = new InputSource(reader); 1911 getXMLReader().parse(is); 1912 cleanup(); 1913 return (root); 1914 1915 } 1916 1917 1918 /** 1919 * Parse the content of the specified URI using this Digester. 1920 * Returns the root element from the object stack (if any). 1921 * 1922 * @param uri URI containing the XML data to be parsed 1923 * 1924 * @exception IOException if an input/output error occurs 1925 * @exception SAXException if a parsing exception occurs 1926 */ 1927 public Object parse(String uri) throws IOException, SAXException { 1928 1929 if (uri == null) { 1930 throw new IllegalArgumentException("String URI to parse is null"); 1931 } 1932 1933 configure(); 1934 InputSource is = createInputSourceFromURL(uri); 1935 getXMLReader().parse(is); 1936 cleanup(); 1937 return (root); 1938 1939 } 1940 1941 1942 /** 1943 * Parse the content of the specified URL using this Digester. 1944 * Returns the root element from the object stack (if any). 1945 * 1946 * @param url URL containing the XML data to be parsed 1947 * 1948 * @exception IOException if an input/output error occurs 1949 * @exception SAXException if a parsing exception occurs 1950 * 1951 * @since 1.8 1952 */ 1953 public Object parse(URL url) throws IOException, SAXException { 1954 1955 if (url == null) { 1956 throw new IllegalArgumentException("URL to parse is null"); 1957 } 1958 1959 configure(); 1960 InputSource is = createInputSourceFromURL(url); 1961 getXMLReader().parse(is); 1962 cleanup(); 1963 return (root); 1964 1965 } 1966 1967 1968 /** 1969 * <p>Register the specified DTD URL for the specified public identifier. 1970 * This must be called before the first call to <code>parse()</code>. 1971 * </p><p> 1972 * <code>Digester</code> contains an internal <code>EntityResolver</code> 1973 * implementation. This maps <code>PUBLICID</code>'s to URLs 1974 * (from which the resource will be loaded). A common use case for this 1975 * method is to register local URLs (possibly computed at runtime by a 1976 * classloader) for DTDs. This allows the performance advantage of using 1977 * a local version without having to ensure every <code>SYSTEM</code> 1978 * URI on every processed xml document is local. This implementation provides 1979 * only basic functionality. If more sophisticated features are required, 1980 * using {@link #setEntityResolver} to set a custom resolver is recommended. 1981 * </p><p> 1982 * <strong>Note:</strong> This method will have no effect when a custom 1983 * <code>EntityResolver</code> has been set. (Setting a custom 1984 * <code>EntityResolver</code> overrides the internal implementation.) 1985 * </p> 1986 * @param publicId Public identifier of the DTD to be resolved 1987 * @param entityURL The URL to use for reading this DTD 1988 * 1989 * @since 1.8 1990 */ 1991 public void register(String publicId, URL entityURL) { 1992 1993 if (log.isDebugEnabled()) { 1994 log.debug("register('" + publicId + "', '" + entityURL + "'"); 1995 } 1996 entityValidator.put(publicId, entityURL); 1997 1998 } 1999 2000 2001 /** 2002 * <p>Convenience method that registers the string version of an entity URL 2003 * instead of a URL version.</p> 2004 * 2005 * @param publicId Public identifier of the entity to be resolved 2006 * @param entityURL The URL to use for reading this entity 2007 */ 2008 public void register(String publicId, String entityURL) { 2009 2010 if (log.isDebugEnabled()) { 2011 log.debug("register('" + publicId + "', '" + entityURL + "'"); 2012 } 2013 try { 2014 entityValidator.put(publicId, new URL(entityURL)); 2015 } catch (MalformedURLException e) { 2016 throw new IllegalArgumentException("Malformed URL '" + entityURL 2017 + "' : " + e.getMessage()); 2018 } 2019 2020 } 2021 2022 2023 /** 2024 * <p><code>List</code> of <code>InputSource</code> instances 2025 * created by a <code>createInputSourceFromURL()</code> method 2026 * call. These represent open input streams that need to be 2027 * closed to avoid resource leaks, as well as potentially locked 2028 * JAR files on Windows.</p> 2029 */ 2030 protected List<InputSource> inputSources = new ArrayList<InputSource>(5); 2031 2032 2033 /** 2034 * Given a URL, return an InputSource that reads from that URL. 2035 * <p> 2036 * Ideally this function would not be needed and code could just use 2037 * <code>new InputSource(entityURL)</code>. Unfortunately it appears 2038 * that when the entityURL points to a file within a jar archive a 2039 * caching mechanism inside the InputSource implementation causes a 2040 * file-handle to the jar file to remain open. On Windows systems 2041 * this then causes the jar archive file to be locked on disk 2042 * ("in use") which makes it impossible to delete the jar file - 2043 * and that really stuffs up "undeploy" in webapps in particular. 2044 * <p> 2045 * In JDK1.4 and later, Apache XercesJ is used as the xml parser. 2046 * The InputSource object provided is converted into an XMLInputSource, 2047 * and eventually passed to an instance of XMLDocumentScannerImpl to 2048 * specify the source data to be converted into tokens for the rest 2049 * of the XMLReader code to handle. XMLDocumentScannerImpl calls 2050 * fEntityManager.startDocumentEntity(source), where fEntityManager 2051 * is declared in ancestor class XMLScanner to be an XMLEntityManager. In 2052 * that class, if the input source stream is null, then: 2053 * <pre> 2054 * URL location = new URL(expandedSystemId); 2055 * URLConnection connect = location.openConnection(); 2056 * if (connect instanceof HttpURLConnection) { 2057 * setHttpProperties(connect,xmlInputSource); 2058 * } 2059 * stream = connect.getInputStream(); 2060 * </pre> 2061 * This method pretty much duplicates the standard behaviour, except 2062 * that it calls URLConnection.setUseCaches(false) before opening 2063 * the connection. 2064 * 2065 * @since 1.8 2066 */ 2067 public InputSource createInputSourceFromURL(URL url) 2068 throws MalformedURLException, IOException { 2069 2070 URLConnection connection = url.openConnection(); 2071 connection.setUseCaches(false); 2072 InputStream stream = connection.getInputStream(); 2073 InputSource source = new InputSource(stream); 2074 source.setSystemId(url.toExternalForm()); 2075 inputSources.add(source); 2076 return source; 2077 2078 } 2079 2080 2081 /** 2082 * <p>Convenience method that creates an <code>InputSource</code> 2083 * from the string version of a URL.</p> 2084 * 2085 * @param url URL for which to create an <code>InputSource</code> 2086 * 2087 * @since 1.8 2088 */ 2089 public InputSource createInputSourceFromURL(String url) 2090 throws MalformedURLException, IOException { 2091 2092 return createInputSourceFromURL(new URL(url)); 2093 2094 } 2095 2096 2097 // --------------------------------------------------------- Rule Methods 2098 2099 2100 /** 2101 * <p>Register a new Rule matching the specified pattern. 2102 * This method sets the <code>Digester</code> property on the rule.</p> 2103 * 2104 * @param pattern Element matching pattern 2105 * @param rule Rule to be registered 2106 */ 2107 public void addRule(String pattern, Rule rule) { 2108 2109 rule.setDigester(this); 2110 getRules().add(pattern, rule); 2111 2112 } 2113 2114 2115 /** 2116 * Register a set of Rule instances defined in a RuleSet. 2117 * 2118 * @param ruleSet The RuleSet instance to configure from 2119 */ 2120 public void addRuleSet(RuleSet ruleSet) { 2121 2122 String oldNamespaceURI = getRuleNamespaceURI(); 2123 String newNamespaceURI = ruleSet.getNamespaceURI(); 2124 if (log.isDebugEnabled()) { 2125 if (newNamespaceURI == null) { 2126 log.debug("addRuleSet() with no namespace URI"); 2127 } else { 2128 log.debug("addRuleSet() with namespace URI " + newNamespaceURI); 2129 } 2130 } 2131 setRuleNamespaceURI(newNamespaceURI); 2132 ruleSet.addRuleInstances(this); 2133 setRuleNamespaceURI(oldNamespaceURI); 2134 2135 } 2136 2137 2138 /** 2139 * Add a "bean property setter" rule for the specified parameters. 2140 * 2141 * @param pattern Element matching pattern 2142 * @see BeanPropertySetterRule 2143 */ 2144 public void addBeanPropertySetter(String pattern) { 2145 2146 addRule(pattern, 2147 new BeanPropertySetterRule()); 2148 2149 } 2150 2151 2152 /** 2153 * Add a "bean property setter" rule for the specified parameters. 2154 * 2155 * @param pattern Element matching pattern 2156 * @param propertyName Name of property to set 2157 * @see BeanPropertySetterRule 2158 */ 2159 public void addBeanPropertySetter(String pattern, 2160 String propertyName) { 2161 2162 addRule(pattern, 2163 new BeanPropertySetterRule(propertyName)); 2164 2165 } 2166 2167 /** 2168 * Add an "call method" rule for a method which accepts no arguments. 2169 * 2170 * @param pattern Element matching pattern 2171 * @param methodName Method name to be called 2172 * @see CallMethodRule 2173 */ 2174 public void addCallMethod(String pattern, String methodName) { 2175 2176 addRule( 2177 pattern, 2178 new CallMethodRule(methodName)); 2179 2180 } 2181 2182 /** 2183 * Add an "call method" rule for the specified parameters. 2184 * 2185 * @param pattern Element matching pattern 2186 * @param methodName Method name to be called 2187 * @param paramCount Number of expected parameters (or zero 2188 * for a single parameter from the body of this element) 2189 * @see CallMethodRule 2190 */ 2191 public void addCallMethod(String pattern, String methodName, 2192 int paramCount) { 2193 2194 addRule(pattern, 2195 new CallMethodRule(methodName, paramCount)); 2196 2197 } 2198 2199 2200 /** 2201 * Add an "call method" rule for the specified parameters. 2202 * If <code>paramCount</code> is set to zero the rule will use 2203 * the body of the matched element as the single argument of the 2204 * method, unless <code>paramTypes</code> is null or empty, in this 2205 * case the rule will call the specified method with no arguments. 2206 * 2207 * @param pattern Element matching pattern 2208 * @param methodName Method name to be called 2209 * @param paramCount Number of expected parameters (or zero 2210 * for a single parameter from the body of this element) 2211 * @param paramTypes Set of Java class names for the types 2212 * of the expected parameters 2213 * (if you wish to use a primitive type, specify the corresonding 2214 * Java wrapper class instead, such as <code>java.lang.Boolean</code> 2215 * for a <code>boolean</code> parameter) 2216 * @see CallMethodRule 2217 */ 2218 public void addCallMethod(String pattern, String methodName, 2219 int paramCount, String paramTypes[]) { 2220 2221 addRule(pattern, 2222 new CallMethodRule( 2223 methodName, 2224 paramCount, 2225 paramTypes)); 2226 2227 } 2228 2229 2230 /** 2231 * Add an "call method" rule for the specified parameters. 2232 * If <code>paramCount</code> is set to zero the rule will use 2233 * the body of the matched element as the single argument of the 2234 * method, unless <code>paramTypes</code> is null or empty, in this 2235 * case the rule will call the specified method with no arguments. 2236 * 2237 * @param pattern Element matching pattern 2238 * @param methodName Method name to be called 2239 * @param paramCount Number of expected parameters (or zero 2240 * for a single parameter from the body of this element) 2241 * @param paramTypes The Java class names of the arguments 2242 * (if you wish to use a primitive type, specify the corresonding 2243 * Java wrapper class instead, such as <code>java.lang.Boolean</code> 2244 * for a <code>boolean</code> parameter) 2245 * @see CallMethodRule 2246 */ 2247 public void addCallMethod(String pattern, String methodName, 2248 int paramCount, Class<?> paramTypes[]) { 2249 2250 addRule(pattern, 2251 new CallMethodRule( 2252 methodName, 2253 paramCount, 2254 paramTypes)); 2255 2256 } 2257 2258 2259 /** 2260 * Add a "call parameter" rule for the specified parameters. 2261 * 2262 * @param pattern Element matching pattern 2263 * @param paramIndex Zero-relative parameter index to set 2264 * (from the body of this element) 2265 * @see CallParamRule 2266 */ 2267 public void addCallParam(String pattern, int paramIndex) { 2268 2269 addRule(pattern, 2270 new CallParamRule(paramIndex)); 2271 2272 } 2273 2274 2275 /** 2276 * Add a "call parameter" rule for the specified parameters. 2277 * 2278 * @param pattern Element matching pattern 2279 * @param paramIndex Zero-relative parameter index to set 2280 * (from the specified attribute) 2281 * @param attributeName Attribute whose value is used as the 2282 * parameter value 2283 * @see CallParamRule 2284 */ 2285 public void addCallParam(String pattern, int paramIndex, 2286 String attributeName) { 2287 2288 addRule(pattern, 2289 new CallParamRule(paramIndex, attributeName)); 2290 2291 } 2292 2293 2294 /** 2295 * Add a "call parameter" rule. 2296 * This will either take a parameter from the stack 2297 * or from the current element body text. 2298 * 2299 * @param paramIndex The zero-relative parameter number 2300 * @param fromStack Should the call parameter be taken from the top of the stack? 2301 * @see CallParamRule 2302 */ 2303 public void addCallParam(String pattern, int paramIndex, boolean fromStack) { 2304 2305 addRule(pattern, 2306 new CallParamRule(paramIndex, fromStack)); 2307 2308 } 2309 2310 /** 2311 * Add a "call parameter" rule that sets a parameter from the stack. 2312 * This takes a parameter from the given position on the stack. 2313 * 2314 * @param paramIndex The zero-relative parameter number 2315 * @param stackIndex set the call parameter to the stackIndex'th object down the stack, 2316 * where 0 is the top of the stack, 1 the next element down and so on 2317 * @see CallMethodRule 2318 */ 2319 public void addCallParam(String pattern, int paramIndex, int stackIndex) { 2320 2321 addRule(pattern, 2322 new CallParamRule(paramIndex, stackIndex)); 2323 2324 } 2325 2326 /** 2327 * Add a "call parameter" rule that sets a parameter from the current 2328 * <code>Digester</code> matching path. 2329 * This is sometimes useful when using rules that support wildcards. 2330 * 2331 * @param pattern the pattern that this rule should match 2332 * @param paramIndex The zero-relative parameter number 2333 * @see CallMethodRule 2334 */ 2335 public void addCallParamPath(String pattern,int paramIndex) { 2336 addRule(pattern, new PathCallParamRule(paramIndex)); 2337 } 2338 2339 /** 2340 * Add a "call parameter" rule that sets a parameter from a 2341 * caller-provided object. This can be used to pass constants such as 2342 * strings to methods; it can also be used to pass mutable objects, 2343 * providing ways for objects to do things like "register" themselves 2344 * with some shared object. 2345 * <p> 2346 * Note that when attempting to locate a matching method to invoke, 2347 * the true type of the paramObj is used, so that despite the paramObj 2348 * being passed in here as type Object, the target method can declare 2349 * its parameters as being the true type of the object (or some ancestor 2350 * type, according to the usual type-conversion rules). 2351 * 2352 * @param paramIndex The zero-relative parameter number 2353 * @param paramObj Any arbitrary object to be passed to the target 2354 * method. 2355 * @see CallMethodRule 2356 * 2357 * @since 1.6 2358 */ 2359 public void addObjectParam(String pattern, int paramIndex, 2360 Object paramObj) { 2361 2362 addRule(pattern, 2363 new ObjectParamRule(paramIndex, paramObj)); 2364 2365 } 2366 2367 /** 2368 * Add a "factory create" rule for the specified parameters. 2369 * Exceptions thrown during the object creation process will be propagated. 2370 * 2371 * @param pattern Element matching pattern 2372 * @param className Java class name of the object creation factory class 2373 * @see FactoryCreateRule 2374 */ 2375 public void addFactoryCreate(String pattern, String className) { 2376 2377 addFactoryCreate(pattern, className, false); 2378 2379 } 2380 2381 2382 /** 2383 * Add a "factory create" rule for the specified parameters. 2384 * Exceptions thrown during the object creation process will be propagated. 2385 * 2386 * @param pattern Element matching pattern 2387 * @param clazz Java class of the object creation factory class 2388 * @see FactoryCreateRule 2389 */ 2390 public void addFactoryCreate(String pattern, Class<?> clazz) { 2391 2392 addFactoryCreate(pattern, clazz, false); 2393 2394 } 2395 2396 2397 /** 2398 * Add a "factory create" rule for the specified parameters. 2399 * Exceptions thrown during the object creation process will be propagated. 2400 * 2401 * @param pattern Element matching pattern 2402 * @param className Java class name of the object creation factory class 2403 * @param attributeName Attribute name which, if present, overrides the 2404 * value specified by <code>className</code> 2405 * @see FactoryCreateRule 2406 */ 2407 public void addFactoryCreate(String pattern, String className, 2408 String attributeName) { 2409 2410 addFactoryCreate(pattern, className, attributeName, false); 2411 2412 } 2413 2414 2415 /** 2416 * Add a "factory create" rule for the specified parameters. 2417 * Exceptions thrown during the object creation process will be propagated. 2418 * 2419 * @param pattern Element matching pattern 2420 * @param clazz Java class of the object creation factory class 2421 * @param attributeName Attribute name which, if present, overrides the 2422 * value specified by <code>className</code> 2423 * @see FactoryCreateRule 2424 */ 2425 public void addFactoryCreate(String pattern, Class<?> clazz, 2426 String attributeName) { 2427 2428 addFactoryCreate(pattern, clazz, attributeName, false); 2429 2430 } 2431 2432 2433 /** 2434 * Add a "factory create" rule for the specified parameters. 2435 * Exceptions thrown during the object creation process will be propagated. 2436 * 2437 * @param pattern Element matching pattern 2438 * @param creationFactory Previously instantiated ObjectCreationFactory 2439 * to be utilized 2440 * @see FactoryCreateRule 2441 */ 2442 public void addFactoryCreate(String pattern, 2443 ObjectCreationFactory creationFactory) { 2444 2445 addFactoryCreate(pattern, creationFactory, false); 2446 2447 } 2448 2449 /** 2450 * Add a "factory create" rule for the specified parameters. 2451 * 2452 * @param pattern Element matching pattern 2453 * @param className Java class name of the object creation factory class 2454 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during 2455 * object creation will be ignored. 2456 * @see FactoryCreateRule 2457 */ 2458 public void addFactoryCreate( 2459 String pattern, 2460 String className, 2461 boolean ignoreCreateExceptions) { 2462 2463 addRule( 2464 pattern, 2465 new FactoryCreateRule(className, ignoreCreateExceptions)); 2466 2467 } 2468 2469 2470 /** 2471 * Add a "factory create" rule for the specified parameters. 2472 * 2473 * @param pattern Element matching pattern 2474 * @param clazz Java class of the object creation factory class 2475 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during 2476 * object creation will be ignored. 2477 * @see FactoryCreateRule 2478 */ 2479 public void addFactoryCreate( 2480 String pattern, 2481 Class<?> clazz, 2482 boolean ignoreCreateExceptions) { 2483 2484 addRule( 2485 pattern, 2486 new FactoryCreateRule(clazz, ignoreCreateExceptions)); 2487 2488 } 2489 2490 2491 /** 2492 * Add a "factory create" rule for the specified parameters. 2493 * 2494 * @param pattern Element matching pattern 2495 * @param className Java class name of the object creation factory class 2496 * @param attributeName Attribute name which, if present, overrides the 2497 * value specified by <code>className</code> 2498 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during 2499 * object creation will be ignored. 2500 * @see FactoryCreateRule 2501 */ 2502 public void addFactoryCreate( 2503 String pattern, 2504 String className, 2505 String attributeName, 2506 boolean ignoreCreateExceptions) { 2507 2508 addRule( 2509 pattern, 2510 new FactoryCreateRule(className, attributeName, ignoreCreateExceptions)); 2511 2512 } 2513 2514 2515 /** 2516 * Add a "factory create" rule for the specified parameters. 2517 * 2518 * @param pattern Element matching pattern 2519 * @param clazz Java class of the object creation factory class 2520 * @param attributeName Attribute name which, if present, overrides the 2521 * value specified by <code>className</code> 2522 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during 2523 * object creation will be ignored. 2524 * @see FactoryCreateRule 2525 */ 2526 public void addFactoryCreate( 2527 String pattern, 2528 Class<?> clazz, 2529 String attributeName, 2530 boolean ignoreCreateExceptions) { 2531 2532 addRule( 2533 pattern, 2534 new FactoryCreateRule(clazz, attributeName, ignoreCreateExceptions)); 2535 2536 } 2537 2538 2539 /** 2540 * Add a "factory create" rule for the specified parameters. 2541 * 2542 * @param pattern Element matching pattern 2543 * @param creationFactory Previously instantiated ObjectCreationFactory 2544 * to be utilized 2545 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during 2546 * object creation will be ignored. 2547 * @see FactoryCreateRule 2548 */ 2549 public void addFactoryCreate(String pattern, 2550 ObjectCreationFactory creationFactory, 2551 boolean ignoreCreateExceptions) { 2552 2553 creationFactory.setDigester(this); 2554 addRule(pattern, 2555 new FactoryCreateRule(creationFactory, ignoreCreateExceptions)); 2556 2557 } 2558 2559 /** 2560 * Add an "object create" rule for the specified parameters. 2561 * 2562 * @param pattern Element matching pattern 2563 * @param className Java class name to be created 2564 * @see ObjectCreateRule 2565 */ 2566 public void addObjectCreate(String pattern, String className) { 2567 2568 addRule(pattern, 2569 new ObjectCreateRule(className)); 2570 2571 } 2572 2573 2574 /** 2575 * Add an "object create" rule for the specified parameters. 2576 * 2577 * @param pattern Element matching pattern 2578 * @param clazz Java class to be created 2579 * @see ObjectCreateRule 2580 */ 2581 public void addObjectCreate(String pattern, Class<?> clazz) { 2582 2583 addRule(pattern, 2584 new ObjectCreateRule(clazz)); 2585 2586 } 2587 2588 2589 /** 2590 * Add an "object create" rule for the specified parameters. 2591 * 2592 * @param pattern Element matching pattern 2593 * @param className Default Java class name to be created 2594 * @param attributeName Attribute name that optionally overrides 2595 * the default Java class name to be created 2596 * @see ObjectCreateRule 2597 */ 2598 public void addObjectCreate(String pattern, String className, 2599 String attributeName) { 2600 2601 addRule(pattern, 2602 new ObjectCreateRule(className, attributeName)); 2603 2604 } 2605 2606 2607 /** 2608 * Add an "object create" rule for the specified parameters. 2609 * 2610 * @param pattern Element matching pattern 2611 * @param attributeName Attribute name that optionally overrides 2612 * @param clazz Default Java class to be created 2613 * the default Java class name to be created 2614 * @see ObjectCreateRule 2615 */ 2616 public void addObjectCreate(String pattern, 2617 String attributeName, 2618 Class<?> clazz) { 2619 2620 addRule(pattern, 2621 new ObjectCreateRule(attributeName, clazz)); 2622 2623 } 2624 2625 /** 2626 * Adds an {@link SetNestedPropertiesRule}. 2627 * 2628 * @param pattern register the rule with this pattern 2629 * 2630 * @since 1.6 2631 */ 2632 public void addSetNestedProperties(String pattern) { 2633 2634 addRule(pattern, new SetNestedPropertiesRule()); 2635 } 2636 2637 /** 2638 * Adds an {@link SetNestedPropertiesRule}. 2639 * 2640 * @param pattern register the rule with this pattern 2641 * @param elementName elment name that a property maps to 2642 * @param propertyName property name of the element mapped from 2643 * 2644 * @since 1.6 2645 */ 2646 public void addSetNestedProperties(String pattern, String elementName, String propertyName) { 2647 2648 addRule(pattern, new SetNestedPropertiesRule(elementName, propertyName)); 2649 } 2650 2651 /** 2652 * Adds an {@link SetNestedPropertiesRule}. 2653 * 2654 * @param pattern register the rule with this pattern 2655 * @param elementNames elment names that (in order) map to properties 2656 * @param propertyNames property names that (in order) elements are mapped to 2657 * 2658 * @since 1.6 2659 */ 2660 public void addSetNestedProperties(String pattern, String[] elementNames, String[] propertyNames) { 2661 2662 addRule(pattern, new SetNestedPropertiesRule(elementNames, propertyNames)); 2663 } 2664 2665 2666 /** 2667 * Add a "set next" rule for the specified parameters. 2668 * 2669 * @param pattern Element matching pattern 2670 * @param methodName Method name to call on the parent element 2671 * @see SetNextRule 2672 */ 2673 public void addSetNext(String pattern, String methodName) { 2674 2675 addRule(pattern, 2676 new SetNextRule(methodName)); 2677 2678 } 2679 2680 2681 /** 2682 * Add a "set next" rule for the specified parameters. 2683 * 2684 * @param pattern Element matching pattern 2685 * @param methodName Method name to call on the parent element 2686 * @param paramType Java class name of the expected parameter type 2687 * (if you wish to use a primitive type, specify the corresonding 2688 * Java wrapper class instead, such as <code>java.lang.Boolean</code> 2689 * for a <code>boolean</code> parameter) 2690 * @see SetNextRule 2691 */ 2692 public void addSetNext(String pattern, String methodName, 2693 String paramType) { 2694 2695 addRule(pattern, 2696 new SetNextRule(methodName, paramType)); 2697 2698 } 2699 2700 2701 /** 2702 * Add {@link SetRootRule} with the specified parameters. 2703 * 2704 * @param pattern Element matching pattern 2705 * @param methodName Method name to call on the root object 2706 * @see SetRootRule 2707 */ 2708 public void addSetRoot(String pattern, String methodName) { 2709 2710 addRule(pattern, 2711 new SetRootRule(methodName)); 2712 2713 } 2714 2715 2716 /** 2717 * Add {@link SetRootRule} with the specified parameters. 2718 * 2719 * @param pattern Element matching pattern 2720 * @param methodName Method name to call on the root object 2721 * @param paramType Java class name of the expected parameter type 2722 * @see SetRootRule 2723 */ 2724 public void addSetRoot(String pattern, String methodName, 2725 String paramType) { 2726 2727 addRule(pattern, 2728 new SetRootRule(methodName, paramType)); 2729 2730 } 2731 2732 /** 2733 * Add a "set properties" rule for the specified parameters. 2734 * 2735 * @param pattern Element matching pattern 2736 * @see SetPropertiesRule 2737 */ 2738 public void addSetProperties(String pattern) { 2739 2740 addRule(pattern, 2741 new SetPropertiesRule()); 2742 2743 } 2744 2745 /** 2746 * Add a "set properties" rule with a single overridden parameter. 2747 * See {@link SetPropertiesRule#SetPropertiesRule(String attributeName, String propertyName)} 2748 * 2749 * @param pattern Element matching pattern 2750 * @param attributeName map this attribute 2751 * @param propertyName to this property 2752 * @see SetPropertiesRule 2753 */ 2754 public void addSetProperties( 2755 String pattern, 2756 String attributeName, 2757 String propertyName) { 2758 2759 addRule(pattern, 2760 new SetPropertiesRule(attributeName, propertyName)); 2761 2762 } 2763 2764 /** 2765 * Add a "set properties" rule with overridden parameters. 2766 * See {@link SetPropertiesRule#SetPropertiesRule(String [] attributeNames, String [] propertyNames)} 2767 * 2768 * @param pattern Element matching pattern 2769 * @param attributeNames names of attributes with custom mappings 2770 * @param propertyNames property names these attributes map to 2771 * @see SetPropertiesRule 2772 */ 2773 public void addSetProperties( 2774 String pattern, 2775 String [] attributeNames, 2776 String [] propertyNames) { 2777 2778 addRule(pattern, 2779 new SetPropertiesRule(attributeNames, propertyNames)); 2780 2781 } 2782 2783 2784 /** 2785 * Add a "set property" rule for the specified parameters. 2786 * 2787 * @param pattern Element matching pattern 2788 * @param name Attribute name containing the property name to be set 2789 * @param value Attribute name containing the property value to set 2790 * @see SetPropertyRule 2791 */ 2792 public void addSetProperty(String pattern, String name, String value) { 2793 2794 addRule(pattern, 2795 new SetPropertyRule(name, value)); 2796 2797 } 2798 2799 2800 /** 2801 * Add a "set top" rule for the specified parameters. 2802 * 2803 * @param pattern Element matching pattern 2804 * @param methodName Method name to call on the parent element 2805 * @see SetTopRule 2806 */ 2807 public void addSetTop(String pattern, String methodName) { 2808 2809 addRule(pattern, 2810 new SetTopRule(methodName)); 2811 2812 } 2813 2814 2815 /** 2816 * Add a "set top" rule for the specified parameters. 2817 * 2818 * @param pattern Element matching pattern 2819 * @param methodName Method name to call on the parent element 2820 * @param paramType Java class name of the expected parameter type 2821 * (if you wish to use a primitive type, specify the corresonding 2822 * Java wrapper class instead, such as <code>java.lang.Boolean</code> 2823 * for a <code>boolean</code> parameter) 2824 * @see SetTopRule 2825 */ 2826 public void addSetTop(String pattern, String methodName, 2827 String paramType) { 2828 2829 addRule(pattern, 2830 new SetTopRule(methodName, paramType)); 2831 2832 } 2833 2834 2835 // --------------------------------------------------- Object Stack Methods 2836 2837 2838 /** 2839 * Clear the current contents of the default object stack, the param stack, 2840 * all named stacks, and other internal variables. 2841 * <p> 2842 * Calling this method <i>might</i> allow another document of the same type 2843 * to be correctly parsed. However this method was not intended for this 2844 * purpose (just to tidy up memory usage). In general, a separate Digester 2845 * object should be created for each document to be parsed. 2846 * <p> 2847 * Note that this method is called automatically after a document has been 2848 * successfully parsed by a Digester instance. However it is not invoked 2849 * automatically when a parse fails, so when reusing a Digester instance 2850 * (which is not recommended) this method <i>must</i> be called manually 2851 * after a parse failure. 2852 */ 2853 public void clear() { 2854 2855 match = ""; 2856 bodyTexts.clear(); 2857 params.clear(); 2858 publicId = null; 2859 stack.clear(); 2860 stacksByName.clear(); 2861 customContentHandler = null; 2862 } 2863 2864 2865 /** 2866 * Return the top object on the stack without removing it. If there are 2867 * no objects on the stack, return <code>null</code>. 2868 */ 2869 public Object peek() { 2870 2871 try { 2872 return (stack.peek()); 2873 } catch (EmptyStackException e) { 2874 log.warn("Empty stack (returning null)"); 2875 return (null); 2876 } 2877 2878 } 2879 2880 2881 /** 2882 * Return the n'th object down the stack, where 0 is the top element 2883 * and [getCount()-1] is the bottom element. If the specified index 2884 * is out of range, return <code>null</code>. 2885 * 2886 * @param n Index of the desired element, where 0 is the top of the stack, 2887 * 1 is the next element down, and so on. 2888 */ 2889 public Object peek(int n) { 2890 2891 int index = (stack.size() - 1) - n; 2892 if (index < 0) { 2893 log.warn("Empty stack (returning null)"); 2894 return (null); 2895 } 2896 try { 2897 return (stack.get(index)); 2898 } catch (EmptyStackException e) { 2899 log.warn("Empty stack (returning null)"); 2900 return (null); 2901 } 2902 2903 } 2904 2905 2906 /** 2907 * Pop the top object off of the stack, and return it. If there are 2908 * no objects on the stack, return <code>null</code>. 2909 */ 2910 public Object pop() { 2911 2912 try { 2913 Object popped = stack.pop(); 2914 if (stackAction != null) { 2915 popped = stackAction.onPop(this, null, popped); 2916 } 2917 return popped; 2918 } catch (EmptyStackException e) { 2919 log.warn("Empty stack (returning null)"); 2920 return (null); 2921 } 2922 2923 } 2924 2925 2926 /** 2927 * Push a new object onto the top of the object stack. 2928 * 2929 * @param object The new object 2930 */ 2931 public void push(Object object) { 2932 2933 if (stackAction != null) { 2934 object = stackAction.onPush(this, null, object); 2935 } 2936 2937 if (stack.size() == 0) { 2938 root = object; 2939 } 2940 stack.push(object); 2941 } 2942 2943 /** 2944 * Pushes the given object onto the stack with the given name. 2945 * If no stack already exists with the given name then one will be created. 2946 * 2947 * @param stackName the name of the stack onto which the object should be pushed 2948 * @param value the Object to be pushed onto the named stack. 2949 * 2950 * @since 1.6 2951 */ 2952 public void push(String stackName, Object value) { 2953 if (stackAction != null) { 2954 value = stackAction.onPush(this, stackName, value); 2955 } 2956 2957 Stack<Object> namedStack = stacksByName.get(stackName); 2958 if (namedStack == null) { 2959 namedStack = new Stack<Object>(); 2960 stacksByName.put(stackName, namedStack); 2961 } 2962 namedStack.push(value); 2963 } 2964 2965 /** 2966 * <p>Pops (gets and removes) the top object from the stack with the given name.</p> 2967 * 2968 * <p><strong>Note:</strong> a stack is considered empty 2969 * if no objects have been pushed onto it yet.</p> 2970 * 2971 * @param stackName the name of the stack from which the top value is to be popped. 2972 * @return the top <code>Object</code> on the stack or or null if the stack is either 2973 * empty or has not been created yet 2974 * @throws EmptyStackException if the named stack is empty 2975 * 2976 * @since 1.6 2977 */ 2978 public Object pop(String stackName) { 2979 Object result = null; 2980 Stack<Object> namedStack = stacksByName.get(stackName); 2981 if (namedStack == null) { 2982 if (log.isDebugEnabled()) { 2983 log.debug("Stack '" + stackName + "' is empty"); 2984 } 2985 throw new EmptyStackException(); 2986 } 2987 2988 result = namedStack.pop(); 2989 2990 if (stackAction != null) { 2991 result = stackAction.onPop(this, stackName, result); 2992 } 2993 2994 return result; 2995 } 2996 2997 /** 2998 * <p>Gets the top object from the stack with the given name. 2999 * This method does not remove the object from the stack. 3000 * </p> 3001 * <p><strong>Note:</strong> a stack is considered empty 3002 * if no objects have been pushed onto it yet.</p> 3003 * 3004 * @param stackName the name of the stack to be peeked 3005 * @return the top <code>Object</code> on the stack or null if the stack is either 3006 * empty or has not been created yet 3007 * @throws EmptyStackException if the named stack is empty 3008 * 3009 * @since 1.6 3010 */ 3011 public Object peek(String stackName) { 3012 return peek(stackName, 0); 3013 } 3014 3015 /** 3016 * <p>Gets the top object from the stack with the given name. 3017 * This method does not remove the object from the stack. 3018 * </p> 3019 * <p><strong>Note:</strong> a stack is considered empty 3020 * if no objects have been pushed onto it yet.</p> 3021 * 3022 * @param stackName the name of the stack to be peeked 3023 * @param n Index of the desired element, where 0 is the top of the stack, 3024 * 1 is the next element down, and so on. 3025 * @return the specified <code>Object</code> on the stack. 3026 * @throws EmptyStackException if the named stack is empty 3027 * 3028 * @since 1.6 3029 */ 3030 public Object peek(String stackName, int n) { 3031 Object result = null; 3032 Stack<Object> namedStack = stacksByName.get(stackName); 3033 if (namedStack == null ) { 3034 if (log.isDebugEnabled()) { 3035 log.debug("Stack '" + stackName + "' is empty"); 3036 } 3037 throw new EmptyStackException(); 3038 3039 } else { 3040 int index = (namedStack.size() - 1) - n; 3041 if (index < 0) { 3042 throw new EmptyStackException(); 3043 } 3044 result = namedStack.get(index); 3045 } 3046 return result; 3047 } 3048 3049 /** 3050 * <p>Is the stack with the given name empty?</p> 3051 * <p><strong>Note:</strong> a stack is considered empty 3052 * if no objects have been pushed onto it yet.</p> 3053 * @param stackName the name of the stack whose emptiness 3054 * should be evaluated 3055 * @return true if the given stack if empty 3056 * 3057 * @since 1.6 3058 */ 3059 public boolean isEmpty(String stackName) { 3060 boolean result = true; 3061 Stack<Object> namedStack = stacksByName.get(stackName); 3062 if (namedStack != null ) { 3063 result = namedStack.isEmpty(); 3064 } 3065 return result; 3066 } 3067 3068 /** 3069 * Returns the root element of the tree of objects created as a result 3070 * of applying the rule objects to the input XML. 3071 * <p> 3072 * If the digester stack was "primed" by explicitly pushing a root 3073 * object onto the stack before parsing started, then that root object 3074 * is returned here. 3075 * <p> 3076 * Alternatively, if a Rule which creates an object (eg ObjectCreateRule) 3077 * matched the root element of the xml, then the object created will be 3078 * returned here. 3079 * <p> 3080 * In other cases, the object most recently pushed onto an empty digester 3081 * stack is returned. This would be a most unusual use of digester, however; 3082 * one of the previous configurations is much more likely. 3083 * <p> 3084 * Note that when using one of the Digester.parse methods, the return 3085 * value from the parse method is exactly the same as the return value 3086 * from this method. However when the Digester is being used as a 3087 * SAXContentHandler, no such return value is available; in this case, this 3088 * method allows you to access the root object that has been created 3089 * after parsing has completed. 3090 * 3091 * @return the root object that has been created after parsing 3092 * or null if the digester has not parsed any XML yet. 3093 */ 3094 public Object getRoot() { 3095 return root; 3096 } 3097 3098 /** 3099 * This method allows the "root" variable to be reset to null. 3100 * <p> 3101 * It is not considered safe for a digester instance to be reused 3102 * to parse multiple xml documents. However if you are determined to 3103 * do so, then you should call both clear() and resetRoot() before 3104 * each parse. 3105 * 3106 * @since 1.7 3107 */ 3108 public void resetRoot() { 3109 root = null; 3110 } 3111 3112 // ------------------------------------------------ Parameter Stack Methods 3113 3114 3115 // ------------------------------------------------------ Protected Methods 3116 3117 3118 /** 3119 * <p>Clean up allocated resources after parsing is complete. The 3120 * default method closes input streams that have been created by 3121 * Digester itself. If you override this method in a subclass, be 3122 * sure to call <code>super.cleanup()</code> to invoke this logic.</p> 3123 * 3124 * @since 1.8 3125 */ 3126 protected void cleanup() { 3127 3128 // If we created any InputSource objects in this instance, 3129 // they each have an input stream that should be closed 3130 for (InputSource source : inputSources) { 3131 try { 3132 source.getByteStream().close(); 3133 } catch (IOException e) { 3134 ; // Fall through so we get them all 3135 } 3136 } 3137 inputSources.clear(); 3138 3139 } 3140 3141 3142 /** 3143 * <p> 3144 * Provide a hook for lazy configuration of this <code>Digester</code> 3145 * instance. The default implementation does nothing, but subclasses 3146 * can override as needed. 3147 * </p> 3148 * 3149 * <p> 3150 * <strong>Note</strong> This method may be called more than once. 3151 * Once only initialization code should be placed in {@link #initialize} 3152 * or the code should take responsibility by checking and setting the 3153 * {@link #configured} flag. 3154 * </p> 3155 */ 3156 protected void configure() { 3157 3158 // Do not configure more than once 3159 if (configured) { 3160 return; 3161 } 3162 3163 // Perform lazy configuration as needed 3164 initialize(); // call hook method for subclasses that want to be initialized once only 3165 // Nothing else required by default 3166 3167 // Set the configuration flag to avoid repeating 3168 configured = true; 3169 3170 } 3171 3172 /** 3173 * <p> 3174 * Provides a hook for lazy initialization of this <code>Digester</code> 3175 * instance. 3176 * The default implementation does nothing, but subclasses 3177 * can override as needed. 3178 * Digester (by default) only calls this method once. 3179 * </p> 3180 * 3181 * <p> 3182 * <strong>Note</strong> This method will be called by {@link #configure} 3183 * only when the {@link #configured} flag is false. 3184 * Subclasses that override <code>configure</code> or who set <code>configured</code> 3185 * may find that this method may be called more than once. 3186 * </p> 3187 * 3188 * @since 1.6 3189 */ 3190 protected void initialize() { 3191 3192 // Perform lazy initialization as needed 3193 ; // Nothing required by default 3194 3195 } 3196 3197 // -------------------------------------------------------- Package Methods 3198 3199 3200 /** 3201 * Return the set of DTD URL registrations, keyed by public identifier. 3202 */ 3203 Map<String, URL> getRegistrations() { 3204 3205 return (entityValidator); 3206 3207 } 3208 3209 3210 /** 3211 * Return the set of rules that apply to the specified match position. 3212 * The selected rules are those that match exactly, or those rules 3213 * that specify a suffix match and the tail of the rule matches the 3214 * current match position. Exact matches have precedence over 3215 * suffix matches, then (among suffix matches) the longest match 3216 * is preferred. 3217 * 3218 * @param match The current match position 3219 * 3220 * @deprecated Call <code>match()</code> on the <code>Rules</code> 3221 * implementation returned by <code>getRules()</code> 3222 */ 3223 List<Rule> getRules(String match) { 3224 3225 return (getRules().match(match)); 3226 3227 } 3228 3229 3230 /** 3231 * <p>Return the top object on the parameters stack without removing it. If there are 3232 * no objects on the stack, return <code>null</code>.</p> 3233 * 3234 * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters. 3235 * See {@link #params}.</p> 3236 */ 3237 public Object peekParams() { 3238 3239 try { 3240 return (params.peek()); 3241 } catch (EmptyStackException e) { 3242 log.warn("Empty stack (returning null)"); 3243 return (null); 3244 } 3245 3246 } 3247 3248 3249 /** 3250 * <p>Return the n'th object down the parameters stack, where 0 is the top element 3251 * and [getCount()-1] is the bottom element. If the specified index 3252 * is out of range, return <code>null</code>.</p> 3253 * 3254 * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters. 3255 * See {@link #params}.</p> 3256 * 3257 * @param n Index of the desired element, where 0 is the top of the stack, 3258 * 1 is the next element down, and so on. 3259 */ 3260 public Object peekParams(int n) { 3261 3262 int index = (params.size() - 1) - n; 3263 if (index < 0) { 3264 log.warn("Empty stack (returning null)"); 3265 return (null); 3266 } 3267 try { 3268 return (params.get(index)); 3269 } catch (EmptyStackException e) { 3270 log.warn("Empty stack (returning null)"); 3271 return (null); 3272 } 3273 3274 } 3275 3276 3277 /** 3278 * <p>Pop the top object off of the parameters stack, and return it. If there are 3279 * no objects on the stack, return <code>null</code>.</p> 3280 * 3281 * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters. 3282 * See {@link #params}.</p> 3283 */ 3284 public Object popParams() { 3285 3286 try { 3287 if (log.isTraceEnabled()) { 3288 log.trace("Popping params"); 3289 } 3290 return (params.pop()); 3291 } catch (EmptyStackException e) { 3292 log.warn("Empty stack (returning null)"); 3293 return (null); 3294 } 3295 3296 } 3297 3298 3299 /** 3300 * <p>Push a new object onto the top of the parameters stack.</p> 3301 * 3302 * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters. 3303 * See {@link #params}.</p> 3304 * 3305 * @param object The new object 3306 */ 3307 public void pushParams(Object object) { 3308 if (log.isTraceEnabled()) { 3309 log.trace("Pushing params"); 3310 } 3311 params.push(object); 3312 3313 } 3314 3315 /** 3316 * Create a SAX exception which also understands about the location in 3317 * the digester file where the exception occurs 3318 * 3319 * @return the new exception 3320 */ 3321 public SAXException createSAXException(String message, Exception e) { 3322 if ((e != null) && 3323 (e instanceof InvocationTargetException)) { 3324 Throwable t = ((InvocationTargetException) e).getTargetException(); 3325 if ((t != null) && (t instanceof Exception)) { 3326 e = (Exception) t; 3327 } 3328 } 3329 if (locator != null) { 3330 String error = "Error at line " + locator.getLineNumber() + " char " + 3331 locator.getColumnNumber() + ": " + message; 3332 if (e != null) { 3333 return new SAXParseException(error, locator, e); 3334 } else { 3335 return new SAXParseException(error, locator); 3336 } 3337 } 3338 log.error("No Locator!"); 3339 if (e != null) { 3340 return new SAXException(message, e); 3341 } else { 3342 return new SAXException(message); 3343 } 3344 } 3345 3346 /** 3347 * Create a SAX exception which also understands about the location in 3348 * the digester file where the exception occurs 3349 * 3350 * @return the new exception 3351 */ 3352 public SAXException createSAXException(Exception e) { 3353 if (e instanceof InvocationTargetException) { 3354 Throwable t = ((InvocationTargetException) e).getTargetException(); 3355 if ((t != null) && (t instanceof Exception)) { 3356 e = (Exception) t; 3357 } 3358 } 3359 return createSAXException(e.getMessage(), e); 3360 } 3361 3362 /** 3363 * Create a SAX exception which also understands about the location in 3364 * the digester file where the exception occurs 3365 * 3366 * @return the new exception 3367 */ 3368 public SAXException createSAXException(String message) { 3369 return createSAXException(message, null); 3370 } 3371 3372 }