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