1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.tomcat.util.digester;
18
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.UnsupportedEncodingException;
24 import java.lang.reflect.InvocationTargetException;
25 import java.net.URI;
26 import java.net.URISyntaxException;
27 import java.security.Permission;
28 import java.util.EmptyStackException;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.PropertyPermission;
34 import java.util.Set;
35
36 import javax.xml.parsers.ParserConfigurationException;
37 import javax.xml.parsers.SAXParser;
38 import javax.xml.parsers.SAXParserFactory;
39
40 import org.apache.juli.logging.Log;
41 import org.apache.juli.logging.LogFactory;
42 import org.apache.tomcat.util.ExceptionUtils;
43 import org.apache.tomcat.util.IntrospectionUtils;
44 import org.apache.tomcat.util.IntrospectionUtils.PropertySource;
45 import org.apache.tomcat.util.buf.B2CConverter;
46 import org.apache.tomcat.util.res.StringManager;
47 import org.apache.tomcat.util.security.PermissionCheck;
48 import org.xml.sax.Attributes;
49 import org.xml.sax.EntityResolver;
50 import org.xml.sax.ErrorHandler;
51 import org.xml.sax.InputSource;
52 import org.xml.sax.Locator;
53 import org.xml.sax.SAXException;
54 import org.xml.sax.SAXNotRecognizedException;
55 import org.xml.sax.SAXNotSupportedException;
56 import org.xml.sax.SAXParseException;
57 import org.xml.sax.XMLReader;
58 import org.xml.sax.ext.DefaultHandler2;
59 import org.xml.sax.ext.EntityResolver2;
60 import org.xml.sax.ext.Locator2;
61 import org.xml.sax.helpers.AttributesImpl;
62
63
64 /**
65  * <p>A <strong>Digester</strong> processes an XML input stream by matching a
66  * series of element nesting patterns to execute Rules that have been added
67  * prior to the start of parsing.  This package was inspired by the
68  * <code>XmlMapper</code> class that was part of Tomcat 3.0 and 3.1,
69  * but is organized somewhat differently.</p>
70  *
71  * <p>See the <a href="package-summary.html#package_description">Digester
72  * Developer Guide</a> for more information.</p>
73  *
74  * <p><strong>IMPLEMENTATION NOTE</strong> - A single Digester instance may
75  * only be used within the context of a single thread at a time, and a call
76  * to <code>parse()</code> must be completed before another can be initiated
77  * even from the same thread.</p>
78  *
79  * <p><strong>IMPLEMENTATION NOTE</strong> - A bug in Xerces 2.0.2 prevents
80  * the support of XML schema. You need Xerces 2.1/2.3 and up to make
81  * this class working with XML schema</p>
82  */

83 public class Digester extends DefaultHandler2 {
84
85     // ---------------------------------------------------------- Static Fields
86
87     protected static IntrospectionUtils.PropertySource propertySource;
88     private static boolean propertySourceSet = false;
89     protected static final StringManager sm = StringManager.getManager(Digester.class);
90
91     static {
92         String className = System.getProperty("org.apache.tomcat.util.digester.PROPERTY_SOURCE");
93         IntrospectionUtils.PropertySource source = null;
94         if (className != null) {
95             ClassLoader[] cls = new ClassLoader[] { Digester.class.getClassLoader(),
96                     Thread.currentThread().getContextClassLoader() };
97             for (int i = 0; i < cls.length; i++) {
98                 try {
99                     Class<?> clazz = Class.forName(className, true, cls[i]);
100                     source = (IntrospectionUtils.PropertySource)
101                             clazz.getConstructor().newInstance();
102                     break;
103                 } catch (Throwable t) {
104                     ExceptionUtils.handleThrowable(t);
105                     LogFactory.getLog(Digester.class).error(sm.getString("digester.propertySourceLoadError", className), t);
106                 }
107             }
108         }
109         if (source != null) {
110             propertySource = source;
111             propertySourceSet = true;
112         }
113         if (Boolean.getBoolean("org.apache.tomcat.util.digester.REPLACE_SYSTEM_PROPERTIES")) {
114             replaceSystemProperties();
115         }
116     }
117
118     public static void setPropertySource(IntrospectionUtils.PropertySource propertySource) {
119         if (!propertySourceSet) {
120             Digester.propertySource = propertySource;
121             propertySourceSet = true;
122         }
123     }
124
125     // --------------------------------------------------- Instance Variables
126
127
128     private static class SystemPropertySource implements IntrospectionUtils.SecurePropertySource {
129
130         @Override
131         public String getProperty(String key) {
132             // For backward compatibility
133             return getProperty(key, null);
134         }
135
136         @Override
137         public String getProperty(String key, ClassLoader classLoader) {
138             if (classLoader instanceof PermissionCheck) {
139                 Permission p = new PropertyPermission(key, "read");
140                 if (!((PermissionCheck) classLoader).check(p)) {
141                     return null;
142                 }
143             }
144             return System.getProperty(key);
145         }
146     }
147
148     /**
149      * A {@link org.apache.tomcat.util.IntrospectionUtils.SecurePropertySource}
150      * that uses environment variables to resolve expressions. Still available
151      * for backwards compatibility.
152      *
153      * @deprecated Use {@link org.apache.tomcat.util.digester.EnvironmentPropertySource}
154      *             This will be removed in Tomcat 10 onwards.
155      */

156     @Deprecated
157     public static class EnvironmentPropertySource extends org.apache.tomcat.util.digester.EnvironmentPropertySource {
158     }
159
160
161     protected IntrospectionUtils.PropertySource source[] = new IntrospectionUtils.PropertySource[] {
162             new SystemPropertySource() };
163
164
165     /**
166      * The body text of the current element.
167      */

168     protected StringBuilder bodyText = new StringBuilder();
169
170
171     /**
172      * The stack of body text string buffers for surrounding elements.
173      */

174     protected ArrayStack<StringBuilder> bodyTexts = new ArrayStack<>();
175
176
177     /**
178      * Stack whose elements are List objects, each containing a list of
179      * Rule objects as returned from Rules.getMatch(). As each xml element
180      * in the input is entered, the matching rules are pushed onto this
181      * stack. After the end tag is reached, the matches are popped again.
182      * The depth of is stack is therefore exactly the same as the current
183      * "nesting" level of the input xml.
184      *
185      * @since 1.6
186      */

187     protected ArrayStack<List<Rule>> matches = new ArrayStack<>(10);
188
189     /**
190      * The class loader to use for instantiating application objects.
191      * If not specified, the context class loader, or the class loader
192      * used to load Digester itself, is used, based on the value of the
193      * <code>useContextClassLoader</code> variable.
194      */

195     protected ClassLoader classLoader = null;
196
197
198     /**
199      * Has this Digester been configured yet.
200      */

201     protected boolean configured = false;
202
203
204     /**
205      * The EntityResolver used by the SAX parser. By default it use this class
206      */

207     protected EntityResolver entityResolver;
208
209     /**
210      * The URLs of entityValidator that have been registered, keyed by the public
211      * identifier that corresponds.
212      */

213     protected HashMap<String, String> entityValidator = new HashMap<>();
214
215
216     /**
217      * The application-supplied error handler that is notified when parsing
218      * warnings, errors, or fatal errors occur.
219      */

220     protected ErrorHandler errorHandler = null;
221
222
223     /**
224      * The SAXParserFactory that is created the first time we need it.
225      */

226     protected SAXParserFactory factory = null;
227
228     /**
229      * The Locator associated with our parser.
230      */

231     protected Locator locator = null;
232
233
234     /**
235      * The current match pattern for nested element processing.
236      */

237     protected String match = "";
238
239
240     /**
241      * Do we want a "namespace aware" parser.
242      */

243     protected boolean namespaceAware = false;
244
245
246     /**
247      * Registered namespaces we are currently processing.  The key is the
248      * namespace prefix that was declared in the document.  The value is an
249      * ArrayStack of the namespace URIs this prefix has been mapped to --
250      * the top Stack element is the most current one.  (This architecture
251      * is required because documents can declare nested uses of the same
252      * prefix for different Namespace URIs).
253      */

254     protected HashMap<String, ArrayStack<String>> namespaces = new HashMap<>();
255
256
257     /**
258      * The parameters stack being utilized by CallMethodRule and
259      * CallParamRule rules.
260      */

261     protected ArrayStack<Object> params = new ArrayStack<>();
262
263
264     /**
265      * The SAXParser we will use to parse the input stream.
266      */

267     protected SAXParser parser = null;
268
269
270     /**
271      * The public identifier of the DTD we are currently parsing under
272      * (if any).
273      */

274     protected String publicId = null;
275
276
277     /**
278      * The XMLReader used to parse digester rules.
279      */

280     protected XMLReader reader = null;
281
282
283     /**
284      * The "root" element of the stack (in other words, the last object
285      * that was popped.
286      */

287     protected Object root = null;
288
289
290     /**
291      * The <code>Rules</code> implementation containing our collection of
292      * <code>Rule</code> instances and associated matching policy.  If not
293      * established before the first rule is added, a default implementation
294      * will be provided.
295      */

296     protected Rules rules = null;
297
298     /**
299      * The object stack being constructed.
300      */

301     protected ArrayStack<Object> stack = new ArrayStack<>();
302
303
304     /**
305      * Do we want to use the Context ClassLoader when loading classes
306      * for instantiating new objects.  Default is <code>false</code>.
307      */

308     protected boolean useContextClassLoader = false;
309
310
311     /**
312      * Do we want to use a validating parser.
313      */

314     protected boolean validating = false;
315
316
317     /**
318      * Warn on missing attributes and elements.
319      */

320     protected boolean rulesValidation = false;
321
322
323     /**
324      * Fake attributes map (attributes are often used for object creation).
325      */

326     protected Map<Class<?>, List<String>> fakeAttributes = null;
327
328
329     /**
330      * The Log to which most logging calls will be made.
331      */

332     protected Log log = LogFactory.getLog(Digester.class);
333
334     /**
335      * The Log to which all SAX event related logging calls will be made.
336      */

337     protected Log saxLog = LogFactory.getLog("org.apache.tomcat.util.digester.Digester.sax");
338
339
340     public Digester() {
341         propertySourceSet = true;
342         if (propertySource != null) {
343             source = new IntrospectionUtils.PropertySource[] { propertySource, source[0] };
344         }
345     }
346
347
348     public static void replaceSystemProperties() {
349         Log log = LogFactory.getLog(Digester.class);
350         if (propertySource != null) {
351             IntrospectionUtils.PropertySource[] propertySources =
352                     new IntrospectionUtils.PropertySource[] { propertySource };
353             Properties properties = System.getProperties();
354             Set<String> names = properties.stringPropertyNames();
355             for (String name : names) {
356                 String value = System.getProperty(name);
357                 if (value != null) {
358                     try {
359                         String newValue = IntrospectionUtils.replaceProperties(value, null, propertySources, null);
360                         if (!value.equals(newValue)) {
361                             System.setProperty(name, newValue);
362                         }
363                     } catch (Exception e) {
364                         log.warn(sm.getString("digester.failedToUpdateSystemProperty", name, value), e);
365                     }
366                 }
367             }
368         }
369     }
370
371
372     // ------------------------------------------------------------- Properties
373
374     /**
375      * Return the currently mapped namespace URI for the specified prefix,
376      * if any; otherwise return <code>null</code>.  These mappings come and
377      * go dynamically as the document is parsed.
378      *
379      * @param prefix Prefix to look up
380      * @return the namespace URI
381      */

382     public String findNamespaceURI(String prefix) {
383         ArrayStack<String> stack = namespaces.get(prefix);
384         if (stack == null) {
385             return null;
386         }
387         try {
388             return stack.peek();
389         } catch (EmptyStackException e) {
390             return null;
391         }
392     }
393
394
395     /**
396      * Return the class loader to be used for instantiating application objects
397      * when required.  This is determined based upon the following rules:
398      * <ul>
399      * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
400      * <li>The thread context class loader, if it exists and the
401      *     <code>useContextClassLoader</code> property is set to true</li>
402      * <li>The class loader used to load the Digester class itself.
403      * </ul>
404      * @return the classloader
405      */

406     public ClassLoader getClassLoader() {
407         if (this.classLoader != null) {
408             return this.classLoader;
409         }
410         if (this.useContextClassLoader) {
411             ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
412             if (classLoader != null) {
413                 return classLoader;
414             }
415         }
416         return this.getClass().getClassLoader();
417     }
418
419
420     /**
421      * Set the class loader to be used for instantiating application objects
422      * when required.
423      *
424      * @param classLoader The new class loader to use, or <code>null</code>
425      *  to revert to the standard rules
426      */

427     public void setClassLoader(ClassLoader classLoader) {
428         this.classLoader = classLoader;
429     }
430
431
432     /**
433      * @return the current depth of the element stack.
434      */

435     public int getCount() {
436         return stack.size();
437     }
438
439
440     /**
441      * @return the name of the XML element that is currently being processed.
442      */

443     public String getCurrentElementName() {
444         String elementName = match;
445         int lastSlash = elementName.lastIndexOf('/');
446         if (lastSlash >= 0) {
447             elementName = elementName.substring(lastSlash + 1);
448         }
449         return elementName;
450     }
451
452
453     /**
454      * @return the error handler for this Digester.
455      */

456     public ErrorHandler getErrorHandler() {
457         return this.errorHandler;
458     }
459
460
461     /**
462      * Set the error handler for this Digester.
463      *
464      * @param errorHandler The new error handler
465      */

466     public void setErrorHandler(ErrorHandler errorHandler) {
467         this.errorHandler = errorHandler;
468     }
469
470
471     /**
472      * SAX parser factory method.
473      * @return the SAXParserFactory we will use, creating one if necessary.
474      * @throws ParserConfigurationException Error creating parser
475      * @throws SAXNotSupportedException Error creating parser
476      * @throws SAXNotRecognizedException Error creating parser
477      */

478     public SAXParserFactory getFactory() throws SAXNotRecognizedException, SAXNotSupportedException,
479             ParserConfigurationException {
480
481         if (factory == null) {
482             factory = SAXParserFactory.newInstance();
483
484             factory.setNamespaceAware(namespaceAware);
485             // Preserve xmlns attributes
486             if (namespaceAware) {
487                 factory.setFeature("http://xml.org/sax/features/namespace-prefixes"true);
488             }
489
490             factory.setValidating(validating);
491             if (validating) {
492                 // Enable DTD validation
493                 factory.setFeature("http://xml.org/sax/features/validation"true);
494                 // Enable schema validation
495                 factory.setFeature("http://apache.org/xml/features/validation/schema"true);
496             }
497         }
498         return factory;
499     }
500
501
502     /**
503      * Sets a flag indicating whether the requested feature is supported
504      * by the underlying implementation of <code>org.xml.sax.XMLReader</code>.
505      * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description">
506      * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
507      * for information about the standard SAX2 feature flags.  In order to be
508      * effective, this method must be called <strong>before</strong> the
509      * <code>getParser()</code> method is called for the first time, either
510      * directly or indirectly.
511      *
512      * @param feature Name of the feature to set the status for
513      * @param value The new value for this feature
514      *
515      * @exception ParserConfigurationException if a parser configuration error
516      *  occurs
517      * @exception SAXNotRecognizedException if the property name is
518      *  not recognized
519      * @exception SAXNotSupportedException if the property name is
520      *  recognized but not supported
521      */

522     public void setFeature(String feature, boolean value) throws ParserConfigurationException,
523             SAXNotRecognizedException, SAXNotSupportedException {
524
525         getFactory().setFeature(feature, value);
526
527     }
528
529
530     /**
531      * @return the current Logger associated with this instance of the Digester
532      */

533     public Log getLogger() {
534
535         return log;
536
537     }
538
539
540     /**
541      * Set the current logger for this Digester.
542      * @param log The logger that will be used
543      */

544     public void setLogger(Log log) {
545
546         this.log = log;
547
548     }
549
550     /**
551      * Gets the logger used for logging SAX-related information.
552      * <strong>Note</strong> the output is finely grained.
553      *
554      * @since 1.6
555      * @return the SAX logger
556      */

557     public Log getSAXLogger() {
558
559         return saxLog;
560     }
561
562
563     /**
564      * Sets the logger used for logging SAX-related information.
565      * <strong>Note</strong> the output is finely grained.
566      * @param saxLog Log, not null
567      *
568      * @since 1.6
569      */

570     public void setSAXLogger(Log saxLog) {
571
572         this.saxLog = saxLog;
573     }
574
575     /**
576      * @return the current rule match path
577      */

578     public String getMatch() {
579
580         return match;
581
582     }
583
584
585     /**
586      * @return the "namespace aware" flag for parsers we create.
587      */

588     public boolean getNamespaceAware() {
589         return this.namespaceAware;
590     }
591
592
593     /**
594      * Set the "namespace aware" flag for parsers we create.
595      *
596      * @param namespaceAware The new "namespace aware" flag
597      */

598     public void setNamespaceAware(boolean namespaceAware) {
599         this.namespaceAware = namespaceAware;
600     }
601
602
603     /**
604      * Set the public id of the current file being parse.
605      * @param publicId the DTD/Schema public's id.
606      */

607     public void setPublicId(String publicId) {
608         this.publicId = publicId;
609     }
610
611
612     /**
613      * @return the public identifier of the DTD we are currently
614      * parsing under, if any.
615      */

616     public String getPublicId() {
617         return this.publicId;
618     }
619
620
621     /**
622      * @return the SAXParser we will use to parse the input stream.  If there
623      * is a problem creating the parser, return <code>null</code>.
624      */

625     public SAXParser getParser() {
626
627         // Return the parser we already created (if any)
628         if (parser != null) {
629             return parser;
630         }
631
632         // Create a new parser
633         try {
634             parser = getFactory().newSAXParser();
635         } catch (Exception e) {
636             log.error(sm.getString("digester.createParserError"), e);
637             return null;
638         }
639
640         return parser;
641     }
642
643
644     /**
645      * Return the current value of the specified property for the underlying
646      * <code>XMLReader</code> implementation.
647      * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description">
648      * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
649      * for information about the standard SAX2 properties.
650      *
651      * @param property Property name to be retrieved
652      * @return the property value
653      * @exception SAXNotRecognizedException if the property name is
654      *  not recognized
655      * @exception SAXNotSupportedException if the property name is
656      *  recognized but not supported
657      */

658     public Object getProperty(String property)
659             throws SAXNotRecognizedException, SAXNotSupportedException {
660
661         return getParser().getProperty(property);
662     }
663
664
665     /**
666      * Return the <code>Rules</code> implementation object containing our
667      * rules collection and associated matching policy.  If none has been
668      * established, a default implementation will be created and returned.
669      * @return the rules
670      */

671     public Rules getRules() {
672         if (this.rules == null) {
673             this.rules = new RulesBase();
674             this.rules.setDigester(this);
675         }
676         return this.rules;
677     }
678
679
680     /**
681      * Set the <code>Rules</code> implementation object containing our
682      * rules collection and associated matching policy.
683      *
684      * @param rules New Rules implementation
685      */

686     public void setRules(Rules rules) {
687         this.rules = rules;
688         this.rules.setDigester(this);
689     }
690
691
692     /**
693      * @return the boolean as to whether the context classloader should be used.
694      */

695     public boolean getUseContextClassLoader() {
696         return useContextClassLoader;
697     }
698
699
700     /**
701      * Determine whether to use the Context ClassLoader (the one found by
702      * calling <code>Thread.currentThread().getContextClassLoader()</code>)
703      * to resolve/load classes that are defined in various rules.  If not
704      * using Context ClassLoader, then the class-loading defaults to
705      * using the calling-class' ClassLoader.
706      *
707      * @param use determines whether to use Context ClassLoader.
708      */

709     public void setUseContextClassLoader(boolean use) {
710
711         useContextClassLoader = use;
712
713     }
714
715
716     /**
717      * @return the validating parser flag.
718      */

719     public boolean getValidating() {
720         return this.validating;
721     }
722
723
724     /**
725      * Set the validating parser flag.  This must be called before
726      * <code>parse()</code> is called the first time.
727      *
728      * @param validating The new validating parser flag.
729      */

730     public void setValidating(boolean validating) {
731         this.validating = validating;
732     }
733
734
735     /**
736      * @return the rules validation flag.
737      */

738     public boolean getRulesValidation() {
739         return this.rulesValidation;
740     }
741
742
743     /**
744      * Set the rules validation flag.  This must be called before
745      * <code>parse()</code> is called the first time.
746      *
747      * @param rulesValidation The new rules validation flag.
748      */

749     public void setRulesValidation(boolean rulesValidation) {
750         this.rulesValidation = rulesValidation;
751     }
752
753
754     /**
755      * @return the fake attributes list.
756      */

757     public Map<Class<?>, List<String>> getFakeAttributes() {
758         return this.fakeAttributes;
759     }
760
761
762     /**
763      * Determine if an attribute is a fake attribute.
764      * @param object The object
765      * @param name The attribute name
766      * @return <code>true</code> if this is a fake attribute
767      */

768     public boolean isFakeAttribute(Object object, String name) {
769         if (fakeAttributes == null) {
770             return false;
771         }
772         List<String> result = fakeAttributes.get(object.getClass());
773         if (result == null) {
774             result = fakeAttributes.get(Object.class);
775         }
776         if (result == null) {
777             return false;
778         } else {
779             return result.contains(name);
780         }
781     }
782
783
784     /**
785      * Set the fake attributes.
786      *
787      * @param fakeAttributes The new fake attributes.
788      */

789     public void setFakeAttributes(Map<Class<?>, List<String>> fakeAttributes) {
790
791         this.fakeAttributes = fakeAttributes;
792
793     }
794
795
796     /**
797      * Return the XMLReader to be used for parsing the input document.
798      *
799      * FIX ME: there is a bug in JAXP/XERCES that prevent the use of a
800      * parser that contains a schema with a DTD.
801      * @return the XML reader
802      * @exception SAXException if no XMLReader can be instantiated
803      */

804     public XMLReader getXMLReader() throws SAXException {
805         if (reader == null) {
806             reader = getParser().getXMLReader();
807         }
808
809         reader.setDTDHandler(this);
810         reader.setContentHandler(this);
811
812         EntityResolver entityResolver = getEntityResolver();
813         if (entityResolver == null) {
814             entityResolver = this;
815         }
816
817         // Wrap the resolver so we can perform ${...} property replacement
818         if (entityResolver instanceof EntityResolver2) {
819             entityResolver = new EntityResolver2Wrapper((EntityResolver2) entityResolver, source, classLoader);
820         } else {
821             entityResolver = new EntityResolverWrapper(entityResolver, source, classLoader);
822         }
823
824         reader.setEntityResolver(entityResolver);
825
826         reader.setProperty("http://xml.org/sax/properties/lexical-handler"this);
827
828         reader.setErrorHandler(this);
829         return reader;
830     }
831
832     // ------------------------------------------------- ContentHandler Methods
833
834
835     /**
836      * Process notification of character data received from the body of
837      * an XML element.
838      *
839      * @param buffer The characters from the XML document
840      * @param start Starting offset into the buffer
841      * @param length Number of characters from the buffer
842      *
843      * @exception SAXException if a parsing error is to be reported
844      */

845     @Override
846     public void characters(char buffer[], int start, int length) throws SAXException {
847
848         if (saxLog.isDebugEnabled()) {
849             saxLog.debug("characters(" + new String(buffer, start, length) + ")");
850         }
851
852         bodyText.append(buffer, start, length);
853
854     }
855
856
857     /**
858      * Process notification of the end of the document being reached.
859      *
860      * @exception SAXException if a parsing error is to be reported
861      */

862     @Override
863     public void endDocument() throws SAXException {
864
865         if (saxLog.isDebugEnabled()) {
866             if (getCount() > 1) {
867                 saxLog.debug("endDocument():  " + getCount() + " elements left");
868             } else {
869                 saxLog.debug("endDocument()");
870             }
871         }
872
873         while (getCount() > 1) {
874             pop();
875         }
876
877         // Fire "finish" events for all defined rules
878         for (Rule rule : getRules().rules()) {
879             try {
880                 rule.finish();
881             } catch (Exception e) {
882                 log.error(sm.getString("digester.error.finish"), e);
883                 throw createSAXException(e);
884             } catch (Error e) {
885                 log.error(sm.getString("digester.error.finish"), e);
886                 throw e;
887             }
888         }
889
890         // Perform final cleanup
891         clear();
892
893     }
894
895
896     /**
897      * Process notification of the end of an XML element being reached.
898      *
899      * @param namespaceURI - The Namespace URI, or the empty string if the
900      *   element has no Namespace URI or if Namespace processing is not
901      *   being performed.
902      * @param localName - The local name (without prefix), or the empty
903      *   string if Namespace processing is not being performed.
904      * @param qName - The qualified XML 1.0 name (with prefix), or the
905      *   empty string if qualified names are not available.
906      * @exception SAXException if a parsing error is to be reported
907      */

908     @Override
909     public void endElement(String namespaceURI, String localName, String qName)
910             throws SAXException {
911
912         boolean debug = log.isDebugEnabled();
913
914         if (debug) {
915             if (saxLog.isDebugEnabled()) {
916                 saxLog.debug("endElement(" + namespaceURI + "," + localName + "," + qName + ")");
917             }
918             log.debug("  match='" + match + "'");
919             log.debug("  bodyText='" + bodyText + "'");
920         }
921
922         // Parse system properties
923         bodyText = updateBodyText(bodyText);
924
925         // the actual element name is either in localName or qName, depending
926         // on whether the parser is namespace aware
927         String name = localName;
928         if ((name == null) || (name.length() < 1)) {
929             name = qName;
930         }
931
932         // Fire "body" events for all relevant rules
933         List<Rule> rules = matches.pop();
934         if ((rules != null) && (rules.size() > 0)) {
935             String bodyText = this.bodyText.toString().intern();
936             for (int i = 0; i < rules.size(); i++) {
937                 try {
938                     Rule rule = rules.get(i);
939                     if (debug) {
940                         log.debug("  Fire body() for " + rule);
941                     }
942                     rule.body(namespaceURI, name, bodyText);
943                 } catch (Exception e) {
944                     log.error(sm.getString("digester.error.body"), e);
945                     throw createSAXException(e);
946                 } catch (Error e) {
947                     log.error(sm.getString("digester.error.body"), e);
948                     throw e;
949                 }
950             }
951         } else {
952             if (debug) {
953                 log.debug(sm.getString("digester.noRulesFound", match));
954             }
955             if (rulesValidation) {
956                 log.warn(sm.getString("digester.noRulesFound", match));
957             }
958         }
959
960         // Recover the body text from the surrounding element
961         bodyText = bodyTexts.pop();
962
963         // Fire "end" events for all relevant rules in reverse order
964         if (rules != null) {
965             for (int i = 0; i < rules.size(); i++) {
966                 int j = (rules.size() - i) - 1;
967                 try {
968                     Rule rule = rules.get(j);
969                     if (debug) {
970                         log.debug("  Fire end() for " + rule);
971                     }
972                     rule.end(namespaceURI, name);
973                 } catch (Exception e) {
974                     log.error(sm.getString("digester.error.end"), e);
975                     throw createSAXException(e);
976                 } catch (Error e) {
977                     log.error(sm.getString("digester.error.end"), e);
978                     throw e;
979                 }
980             }
981         }
982
983         // Recover the previous match expression
984         int slash = match.lastIndexOf('/');
985         if (slash >= 0) {
986             match = match.substring(0, slash);
987         } else {
988             match = "";
989         }
990
991     }
992
993
994     /**
995      * Process notification that a namespace prefix is going out of scope.
996      *
997      * @param prefix Prefix that is going out of scope
998      *
999      * @exception SAXException if a parsing error is to be reported
1000      */

1001     @Override
1002     public void endPrefixMapping(String prefix) throws SAXException {
1003
1004         if (saxLog.isDebugEnabled()) {
1005             saxLog.debug("endPrefixMapping(" + prefix + ")");
1006         }
1007
1008         // Deregister this prefix mapping
1009         ArrayStack<String> stack = namespaces.get(prefix);
1010         if (stack == null) {
1011             return;
1012         }
1013         try {
1014             stack.pop();
1015             if (stack.empty())
1016                 namespaces.remove(prefix);
1017         } catch (EmptyStackException e) {
1018             throw createSAXException(sm.getString("digester.emptyStackError"));
1019         }
1020
1021     }
1022
1023
1024     /**
1025      * Process notification of ignorable whitespace received from the body of
1026      * an XML element.
1027      *
1028      * @param buffer The characters from the XML document
1029      * @param start Starting offset into the buffer
1030      * @param len Number of characters from the buffer
1031      *
1032      * @exception SAXException if a parsing error is to be reported
1033      */

1034     @Override
1035     public void ignorableWhitespace(char buffer[], int start, int len) throws SAXException {
1036
1037         if (saxLog.isDebugEnabled()) {
1038             saxLog.debug("ignorableWhitespace(" + new String(buffer, start, len) + ")");
1039         }
1040
1041         // No processing required
1042
1043     }
1044
1045
1046     /**
1047      * Process notification of a processing instruction that was encountered.
1048      *
1049      * @param target The processing instruction target
1050      * @param data The processing instruction data (if any)
1051      *
1052      * @exception SAXException if a parsing error is to be reported
1053      */

1054     @Override
1055     public void processingInstruction(String target, String data) throws SAXException {
1056
1057         if (saxLog.isDebugEnabled()) {
1058             saxLog.debug("processingInstruction('" + target + "','" + data + "')");
1059         }
1060
1061         // No processing is required
1062
1063     }
1064
1065
1066     /**
1067      * Gets the document locator associated with our parser.
1068      *
1069      * @return the Locator supplied by the document parser
1070      */

1071     public Locator getDocumentLocator() {
1072
1073         return locator;
1074
1075     }
1076
1077     /**
1078      * Sets the document locator associated with our parser.
1079      *
1080      * @param locator The new locator
1081      */

1082     @Override
1083     public void setDocumentLocator(Locator locator) {
1084
1085         if (saxLog.isDebugEnabled()) {
1086             saxLog.debug("setDocumentLocator(" + locator + ")");
1087         }
1088
1089         this.locator = locator;
1090
1091     }
1092
1093
1094     /**
1095      * Process notification of a skipped entity.
1096      *
1097      * @param name Name of the skipped entity
1098      *
1099      * @exception SAXException if a parsing error is to be reported
1100      */

1101     @Override
1102     public void skippedEntity(String name) throws SAXException {
1103
1104         if (saxLog.isDebugEnabled()) {
1105             saxLog.debug("skippedEntity(" + name + ")");
1106         }
1107
1108         // No processing required
1109
1110     }
1111
1112
1113     /**
1114      * Process notification of the beginning of the document being reached.
1115      *
1116      * @exception SAXException if a parsing error is to be reported
1117      */

1118     @Override
1119     public void startDocument() throws SAXException {
1120
1121         if (saxLog.isDebugEnabled()) {
1122             saxLog.debug("startDocument()");
1123         }
1124
1125         if (locator instanceof Locator2) {
1126             if (root instanceof DocumentProperties.Charset) {
1127                 String enc = ((Locator2) locator).getEncoding();
1128                 if (enc != null) {
1129                     try {
1130                         ((DocumentProperties.Charset) root).setCharset(B2CConverter.getCharset(enc));
1131                     } catch (UnsupportedEncodingException e) {
1132                         log.warn(sm.getString("digester.encodingInvalid", enc), e);
1133                     }
1134                 }
1135             }
1136         }
1137
1138         // ensure that the digester is properly configured, as
1139         // the digester could be used as a SAX ContentHandler
1140         // rather than via the parse() methods.
1141         configure();
1142     }
1143
1144
1145     /**
1146      * Process notification of the start of an XML element being reached.
1147      *
1148      * @param namespaceURI The Namespace URI, or the empty string if the element
1149      *   has no Namespace URI or if Namespace processing is not being performed.
1150      * @param localName The local name (without prefix), or the empty
1151      *   string if Namespace processing is not being performed.
1152      * @param qName The qualified name (with prefix), or the empty
1153      *   string if qualified names are not available.\
1154      * @param list The attributes attached to the element. If there are
1155      *   no attributes, it shall be an empty Attributes object.
1156      * @exception SAXException if a parsing error is to be reported
1157      */

1158     @Override
1159     public void startElement(String namespaceURI, String localName, String qName, Attributes list)
1160             throws SAXException {
1161         boolean debug = log.isDebugEnabled();
1162
1163         if (saxLog.isDebugEnabled()) {
1164             saxLog.debug("startElement(" + namespaceURI + "," + localName + "," + qName + ")");
1165         }
1166
1167         // Parse system properties
1168         list = updateAttributes(list);
1169
1170         // Save the body text accumulated for our surrounding element
1171         bodyTexts.push(bodyText);
1172         bodyText = new StringBuilder();
1173
1174         // the actual element name is either in localName or qName, depending
1175         // on whether the parser is namespace aware
1176         String name = localName;
1177         if ((name == null) || (name.length() < 1)) {
1178             name = qName;
1179         }
1180
1181         // Compute the current matching rule
1182         StringBuilder sb = new StringBuilder(match);
1183         if (match.length() > 0) {
1184             sb.append('/');
1185         }
1186         sb.append(name);
1187         match = sb.toString();
1188         if (debug) {
1189             log.debug("  New match='" + match + "'");
1190         }
1191
1192         // Fire "begin" events for all relevant rules
1193         List<Rule> rules = getRules().match(namespaceURI, match);
1194         matches.push(rules);
1195         if ((rules != null) && (rules.size() > 0)) {
1196             for (int i = 0; i < rules.size(); i++) {
1197                 try {
1198                     Rule rule = rules.get(i);
1199                     if (debug) {
1200                         log.debug("  Fire begin() for " + rule);
1201                     }
1202                     rule.begin(namespaceURI, name, list);
1203                 } catch (Exception e) {
1204                     log.error(sm.getString("digester.error.begin"), e);
1205                     throw createSAXException(e);
1206                 } catch (Error e) {
1207                     log.error(sm.getString("digester.error.begin"), e);
1208                     throw e;
1209                 }
1210             }
1211         } else {
1212             if (debug) {
1213                 log.debug(sm.getString("digester.noRulesFound", match));
1214             }
1215         }
1216
1217     }
1218
1219
1220     /**
1221      * Process notification that a namespace prefix is coming in to scope.
1222      *
1223      * @param prefix Prefix that is being declared
1224      * @param namespaceURI Corresponding namespace URI being mapped to
1225      *
1226      * @exception SAXException if a parsing error is to be reported
1227      */

1228     @Override
1229     public void startPrefixMapping(String prefix, String namespaceURI) throws SAXException {
1230
1231         if (saxLog.isDebugEnabled()) {
1232             saxLog.debug("startPrefixMapping(" + prefix + "," + namespaceURI + ")");
1233         }
1234
1235         // Register this prefix mapping
1236         ArrayStack<String> stack = namespaces.get(prefix);
1237         if (stack == null) {
1238             stack = new ArrayStack<>();
1239             namespaces.put(prefix, stack);
1240         }
1241         stack.push(namespaceURI);
1242
1243     }
1244
1245
1246     // ----------------------------------------------------- DTDHandler Methods
1247
1248
1249     /**
1250      * Receive notification of a notation declaration event.
1251      *
1252      * @param name The notation name
1253      * @param publicId The public identifier (if any)
1254      * @param systemId The system identifier (if any)
1255      */

1256     @Override
1257     public void notationDecl(String name, String publicId, String systemId) {
1258
1259         if (saxLog.isDebugEnabled()) {
1260             saxLog.debug("notationDecl(" + name + "," + publicId + "," + systemId + ")");
1261         }
1262
1263     }
1264
1265
1266     /**
1267      * Receive notification of an unparsed entity declaration event.
1268      *
1269      * @param name The unparsed entity name
1270      * @param publicId The public identifier (if any)
1271      * @param systemId The system identifier (if any)
1272      * @param notation The name of the associated notation
1273      */

1274     @Override
1275     public void unparsedEntityDecl(String name, String publicId, String systemId, String notation) {
1276
1277         if (saxLog.isDebugEnabled()) {
1278             saxLog.debug("unparsedEntityDecl(" + name + "," + publicId + "," + systemId + ","
1279                     + notation + ")");
1280         }
1281
1282     }
1283
1284
1285     // ----------------------------------------------- EntityResolver Methods
1286
1287     /**
1288      * Set the <code>EntityResolver</code> used by SAX when resolving
1289      * public id and system id.
1290      * This must be called before the first call to <code>parse()</code>.
1291      * @param entityResolver a class that implement the <code>EntityResolver</code> interface.
1292      */

1293     public void setEntityResolver(EntityResolver entityResolver) {
1294         this.entityResolver = entityResolver;
1295     }
1296
1297
1298     /**
1299      * Return the Entity Resolver used by the SAX parser.
1300      * @return Return the Entity Resolver used by the SAX parser.
1301      */

1302     public EntityResolver getEntityResolver() {
1303         return entityResolver;
1304     }
1305
1306     @Override
1307     public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId)
1308             throws SAXException, IOException {
1309
1310         if (saxLog.isDebugEnabled()) {
1311             saxLog.debug(
1312                     "resolveEntity('" + publicId + "', '" + systemId + "', '" + baseURI + "')");
1313         }
1314
1315         // Has this system identifier been registered?
1316         String entityURL = null;
1317         if (publicId != null) {
1318             entityURL = entityValidator.get(publicId);
1319         }
1320
1321         if (entityURL == null) {
1322             if (systemId == null) {
1323                 // cannot resolve
1324                 if (log.isDebugEnabled()) {
1325                     log.debug(" Cannot resolve entity: '" + publicId + "'");
1326                 }
1327                 return null;
1328
1329             } else {
1330                 // try to resolve using system ID
1331                 if (log.isDebugEnabled()) {
1332                     log.debug(" Trying to resolve using system ID '" + systemId + "'");
1333                 }
1334                 entityURL = systemId;
1335                 // resolve systemId against baseURI if it is not absolute
1336                 if (baseURI != null) {
1337                     try {
1338                         URI uri = new URI(systemId);
1339                         if (!uri.isAbsolute()) {
1340                             entityURL = new URI(baseURI).resolve(uri).toString();
1341                         }
1342                     } catch (URISyntaxException e) {
1343                         if (log.isDebugEnabled()) {
1344                             log.debug("Invalid URI '" + baseURI + "' or '" + systemId + "'");
1345                         }
1346                     }
1347                 }
1348             }
1349         }
1350
1351         // Return an input source to our alternative URL
1352         if (log.isDebugEnabled()) {
1353             log.debug(" Resolving to alternate DTD '" + entityURL + "'");
1354         }
1355
1356         try {
1357             return new InputSource(entityURL);
1358         } catch (Exception e) {
1359             throw createSAXException(e);
1360         }
1361     }
1362
1363
1364     // ----------------------------------------------- LexicalHandler Methods
1365
1366     @Override
1367     public void startDTD(String name, String publicId, String systemId) throws SAXException {
1368         setPublicId(publicId);
1369     }
1370
1371
1372     // ------------------------------------------------- ErrorHandler Methods
1373
1374     /**
1375      * Forward notification of a parsing error to the application supplied
1376      * error handler (if any).
1377      *
1378      * @param exception The error information
1379      *
1380      * @exception SAXException if a parsing exception occurs
1381      */

1382     @Override
1383     public void error(SAXParseException exception) throws SAXException {
1384         log.error(sm.getString("digester.parseError", Integer.valueOf(exception.getLineNumber()),
1385                 Integer.valueOf(exception.getColumnNumber())), exception);
1386         if (errorHandler != null) {
1387             errorHandler.error(exception);
1388         }
1389     }
1390
1391
1392     /**
1393      * Forward notification of a fatal parsing error to the application
1394      * supplied error handler (if any).
1395      *
1396      * @param exception The fatal error information
1397      *
1398      * @exception SAXException if a parsing exception occurs
1399      */

1400     @Override
1401     public void fatalError(SAXParseException exception) throws SAXException {
1402         log.error(sm.getString("digester.parseErrorFatal", Integer.valueOf(exception.getLineNumber()),
1403                 Integer.valueOf(exception.getColumnNumber())), exception);
1404         if (errorHandler != null) {
1405             errorHandler.fatalError(exception);
1406         }
1407     }
1408
1409
1410     /**
1411      * Forward notification of a parse warning to the application supplied
1412      * error handler (if any).
1413      *
1414      * @param exception The warning information
1415      *
1416      * @exception SAXException if a parsing exception occurs
1417      */

1418     @Override
1419     public void warning(SAXParseException exception) throws SAXException {
1420         log.error(sm.getString("digester.parseWarning", Integer.valueOf(exception.getLineNumber()),
1421                 Integer.valueOf(exception.getColumnNumber()), exception));
1422         if (errorHandler != null) {
1423             errorHandler.warning(exception);
1424         }
1425
1426     }
1427
1428
1429     // ------------------------------------------------------- Public Methods
1430
1431     /**
1432      * Parse the content of the specified file using this Digester.  Returns
1433      * the root element from the object stack (if any).
1434      *
1435      * @param file File containing the XML data to be parsed
1436      * @return the root object
1437      * @exception IOException if an input/output error occurs
1438      * @exception SAXException if a parsing exception occurs
1439      */

1440     public Object parse(File file) throws IOException, SAXException {
1441         configure();
1442         InputSource input = new InputSource(new FileInputStream(file));
1443         input.setSystemId("file://" + file.getAbsolutePath());
1444         getXMLReader().parse(input);
1445         return root;
1446     }
1447
1448
1449     /**
1450      * Parse the content of the specified input source using this Digester.
1451      * Returns the root element from the object stack (if any).
1452      *
1453      * @param input Input source containing the XML data to be parsed
1454      * @return the root object
1455      * @exception IOException if an input/output error occurs
1456      * @exception SAXException if a parsing exception occurs
1457      */

1458     public Object parse(InputSource input) throws IOException, SAXException {
1459         configure();
1460         getXMLReader().parse(input);
1461         return root;
1462     }
1463
1464
1465     /**
1466      * Parse the content of the specified input stream using this Digester.
1467      * Returns the root element from the object stack (if any).
1468      *
1469      * @param input Input stream containing the XML data to be parsed
1470      * @return the root object
1471      * @exception IOException if an input/output error occurs
1472      * @exception SAXException if a parsing exception occurs
1473      */

1474     public Object parse(InputStream input) throws IOException, SAXException {
1475         configure();
1476         InputSource is = new InputSource(input);
1477         getXMLReader().parse(is);
1478         return root;
1479     }
1480
1481
1482     /**
1483      * <p>Register the specified DTD URL for the specified public identifier.
1484      * This must be called before the first call to <code>parse()</code>.
1485      * </p><p>
1486      * <code>Digester</code> contains an internal <code>EntityResolver</code>
1487      * implementation. This maps <code>PUBLICID</code>'s to URLs
1488      * (from which the resource will be loaded). A common use case for this
1489      * method is to register local URLs (possibly computed at runtime by a
1490      * classloader) for DTDs. This allows the performance advantage of using
1491      * a local version without having to ensure every <code>SYSTEM</code>
1492      * URI on every processed xml document is local. This implementation provides
1493      * only basic functionality. If more sophisticated features are required,
1494      * using {@link #setEntityResolver} to set a custom resolver is recommended.
1495      * </p><p>
1496      * <strong>Note:</strong> This method will have no effect when a custom
1497      * <code>EntityResolver</code> has been set. (Setting a custom
1498      * <code>EntityResolver</code> overrides the internal implementation.)
1499      * </p>
1500      * @param publicId Public identifier of the DTD to be resolved
1501      * @param entityURL The URL to use for reading this DTD
1502      */

1503     public void register(String publicId, String entityURL) {
1504
1505         if (log.isDebugEnabled()) {
1506             log.debug("register('" + publicId + "', '" + entityURL + "'");
1507         }
1508         entityValidator.put(publicId, entityURL);
1509
1510     }
1511
1512
1513     // --------------------------------------------------------- Rule Methods
1514
1515
1516     /**
1517      * <p>Register a new Rule matching the specified pattern.
1518      * This method sets the <code>Digester</code> property on the rule.</p>
1519      *
1520      * @param pattern Element matching pattern
1521      * @param rule Rule to be registered
1522      */

1523     public void addRule(String pattern, Rule rule) {
1524
1525         rule.setDigester(this);
1526         getRules().add(pattern, rule);
1527
1528     }
1529
1530
1531     /**
1532      * Register a set of Rule instances defined in a RuleSet.
1533      *
1534      * @param ruleSet The RuleSet instance to configure from
1535      */

1536     public void addRuleSet(RuleSet ruleSet) {
1537         ruleSet.addRuleInstances(this);
1538     }
1539
1540
1541     /**
1542      * Add an "call method" rule for a method which accepts no arguments.
1543      *
1544      * @param pattern Element matching pattern
1545      * @param methodName Method name to be called
1546      * @see CallMethodRule
1547      */

1548     public void addCallMethod(String pattern, String methodName) {
1549
1550         addRule(pattern, new CallMethodRule(methodName));
1551
1552     }
1553
1554     /**
1555      * Add an "call method" rule for the specified parameters.
1556      *
1557      * @param pattern Element matching pattern
1558      * @param methodName Method name to be called
1559      * @param paramCount Number of expected parameters (or zero
1560      *  for a single parameter from the body of this element)
1561      * @see CallMethodRule
1562      */

1563     public void addCallMethod(String pattern, String methodName, int paramCount) {
1564
1565         addRule(pattern, new CallMethodRule(methodName, paramCount));
1566
1567     }
1568
1569
1570     /**
1571      * Add a "call parameter" rule for the specified parameters.
1572      *
1573      * @param pattern Element matching pattern
1574      * @param paramIndex Zero-relative parameter index to set
1575      *  (from the body of this element)
1576      * @see CallParamRule
1577      */

1578     public void addCallParam(String pattern, int paramIndex) {
1579
1580         addRule(pattern, new CallParamRule(paramIndex));
1581
1582     }
1583
1584
1585     /**
1586      * Add a "factory create" rule for the specified parameters.
1587      *
1588      * @param pattern Element matching pattern
1589      * @param creationFactory Previously instantiated ObjectCreationFactory
1590      *  to be utilized
1591      * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
1592      * object creation will be ignored.
1593      * @see FactoryCreateRule
1594      */

1595     public void addFactoryCreate(String pattern, ObjectCreationFactory creationFactory,
1596             boolean ignoreCreateExceptions) {
1597
1598         creationFactory.setDigester(this);
1599         addRule(pattern, new FactoryCreateRule(creationFactory, ignoreCreateExceptions));
1600
1601     }
1602
1603     /**
1604      * Add an "object create" rule for the specified parameters.
1605      *
1606      * @param pattern Element matching pattern
1607      * @param className Java class name to be created
1608      * @see ObjectCreateRule
1609      */

1610     public void addObjectCreate(String pattern, String className) {
1611
1612         addRule(pattern, new ObjectCreateRule(className));
1613
1614     }
1615
1616
1617     /**
1618      * Add an "object create" rule for the specified parameters.
1619      *
1620      * @param pattern Element matching pattern
1621      * @param className Default Java class name to be created
1622      * @param attributeName Attribute name that optionally overrides
1623      *  the default Java class name to be created
1624      * @see ObjectCreateRule
1625      */

1626     public void addObjectCreate(String pattern, String className, String attributeName) {
1627
1628         addRule(pattern, new ObjectCreateRule(className, attributeName));
1629
1630     }
1631
1632
1633     /**
1634      * Add a "set next" rule for the specified parameters.
1635      *
1636      * @param pattern Element matching pattern
1637      * @param methodName Method name to call on the parent element
1638      * @param paramType Java class name of the expected parameter type
1639      *  (if you wish to use a primitive type, specify the corresponding
1640      *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
1641      *  for a <code>boolean</code> parameter)
1642      * @see SetNextRule
1643      */

1644     public void addSetNext(String pattern, String methodName, String paramType) {
1645
1646         addRule(pattern, new SetNextRule(methodName, paramType));
1647
1648     }
1649
1650
1651     /**
1652      * Add a "set properties" rule for the specified parameters.
1653      *
1654      * @param pattern Element matching pattern
1655      * @see SetPropertiesRule
1656      */

1657     public void addSetProperties(String pattern) {
1658
1659         addRule(pattern, new SetPropertiesRule());
1660
1661     }
1662
1663
1664     // --------------------------------------------------- Object Stack Methods
1665
1666
1667     /**
1668      * Clear the current contents of the object stack.
1669      * <p>
1670      * Calling this method <i>might</i> allow another document of the same type
1671      * to be correctly parsed. However this method was not intended for this
1672      * purpose. In general, a separate Digester object should be created for
1673      * each document to be parsed.
1674      */

1675     public void clear() {
1676
1677         match = "";
1678         bodyTexts.clear();
1679         params.clear();
1680         publicId = null;
1681         stack.clear();
1682         log = null;
1683         saxLog = null;
1684         configured = false;
1685
1686     }
1687
1688
1689     public void reset() {
1690         root = null;
1691         setErrorHandler(null);
1692         clear();
1693     }
1694
1695
1696     /**
1697      * Return the top object on the stack without removing it.  If there are
1698      * no objects on the stack, return <code>null</code>.
1699      * @return the top object
1700      */

1701     public Object peek() {
1702         try {
1703             return stack.peek();
1704         } catch (EmptyStackException e) {
1705             log.warn(sm.getString("digester.emptyStack"));
1706             return null;
1707         }
1708     }
1709
1710
1711     /**
1712      * Return the n'th object down the stack, where 0 is the top element
1713      * and [getCount()-1] is the bottom element.  If the specified index
1714      * is out of range, return <code>null</code>.
1715      *
1716      * @param n Index of the desired element, where 0 is the top of the stack,
1717      *  1 is the next element down, and so on.
1718      * @return the specified object
1719      */

1720     public Object peek(int n) {
1721         try {
1722             return stack.peek(n);
1723         } catch (EmptyStackException e) {
1724             log.warn(sm.getString("digester.emptyStack"));
1725             return null;
1726         }
1727     }
1728
1729
1730     /**
1731      * Pop the top object off of the stack, and return it.  If there are
1732      * no objects on the stack, return <code>null</code>.
1733      * @return the top object
1734      */

1735     public Object pop() {
1736         try {
1737             return stack.pop();
1738         } catch (EmptyStackException e) {
1739             log.warn(sm.getString("digester.emptyStack"));
1740             return null;
1741         }
1742     }
1743
1744
1745     /**
1746      * Push a new object onto the top of the object stack.
1747      *
1748      * @param object The new object
1749      */

1750     public void push(Object object) {
1751
1752         if (stack.size() == 0) {
1753             root = object;
1754         }
1755         stack.push(object);
1756
1757     }
1758
1759     /**
1760      * When the Digester is being used as a SAXContentHandler,
1761      * this method allows you to access the root object that has been
1762      * created after parsing.
1763      *
1764      * @return the root object that has been created after parsing
1765      *  or null if the digester has not parsed any XML yet.
1766      */

1767     public Object getRoot() {
1768         return root;
1769     }
1770
1771
1772     // ------------------------------------------------ Parameter Stack Methods
1773
1774
1775     // ------------------------------------------------------ Protected Methods
1776
1777
1778     /**
1779      * <p>
1780      * Provide a hook for lazy configuration of this <code>Digester</code>
1781      * instance.  The default implementation does nothing, but subclasses
1782      * can override as needed.
1783      * </p>
1784      *
1785      * <p>
1786      * <strong>Note</strong> This method may be called more than once.
1787      * </p>
1788      */

1789     protected void configure() {
1790
1791         // Do not configure more than once
1792         if (configured) {
1793             return;
1794         }
1795
1796         log = LogFactory.getLog("org.apache.tomcat.util.digester.Digester");
1797         saxLog = LogFactory.getLog("org.apache.tomcat.util.digester.Digester.sax");
1798
1799         // Set the configuration flag to avoid repeating
1800         configured = true;
1801     }
1802
1803
1804     /**
1805      * <p>Return the top object on the parameters stack without removing it.  If there are
1806      * no objects on the stack, return <code>null</code>.</p>
1807      *
1808      * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters.
1809      * See {@link #params}.</p>
1810      * @return the top object on the parameters stack
1811      */

1812     public Object peekParams() {
1813         try {
1814             return params.peek();
1815         } catch (EmptyStackException e) {
1816             log.warn(sm.getString("digester.emptyStack"));
1817             return null;
1818         }
1819     }
1820
1821
1822     /**
1823      * <p>Pop the top object off of the parameters stack, and return it.  If there are
1824      * no objects on the stack, return <code>null</code>.</p>
1825      *
1826      * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters.
1827      * See {@link #params}.</p>
1828      * @return the top object on the parameters stack
1829      */

1830     public Object popParams() {
1831         try {
1832             if (log.isTraceEnabled()) {
1833                 log.trace("Popping params");
1834             }
1835             return params.pop();
1836         } catch (EmptyStackException e) {
1837             log.warn(sm.getString("digester.emptyStack"));
1838             return null;
1839         }
1840     }
1841
1842
1843     /**
1844      * <p>Push a new object onto the top of the parameters stack.</p>
1845      *
1846      * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters.
1847      * See {@link #params}.</p>
1848      *
1849      * @param object The new object
1850      */

1851     public void pushParams(Object object) {
1852         if (log.isTraceEnabled()) {
1853             log.trace("Pushing params");
1854         }
1855         params.push(object);
1856
1857     }
1858
1859     /**
1860      * Create a SAX exception which also understands about the location in
1861      * the digester file where the exception occurs
1862      * @param message The error message
1863      * @param e The root cause
1864      * @return the new exception
1865      */

1866     public SAXException createSAXException(String message, Exception e) {
1867         if ((e != null) && (e instanceof InvocationTargetException)) {
1868             Throwable t = e.getCause();
1869             if (t instanceof ThreadDeath) {
1870                 throw (ThreadDeath) t;
1871             }
1872             if (t instanceof VirtualMachineError) {
1873                 throw (VirtualMachineError) t;
1874             }
1875             if (t instanceof Exception) {
1876                 e = (Exception) t;
1877             }
1878         }
1879         if (locator != null) {
1880             String error = sm.getString("digester.errorLocation",
1881                     Integer.valueOf(locator.getLineNumber()),
1882                     Integer.valueOf(locator.getColumnNumber()), message);
1883             if (e != null) {
1884                 return new SAXParseException(error, locator, e);
1885             } else {
1886                 return new SAXParseException(error, locator);
1887             }
1888         }
1889         log.error(sm.getString("digester.noLocator"));
1890         if (e != null) {
1891             return new SAXException(message, e);
1892         } else {
1893             return new SAXException(message);
1894         }
1895     }
1896
1897     /**
1898      * Create a SAX exception which also understands about the location in
1899      * the digester file where the exception occurs
1900      * @param e The root cause
1901      * @return the new exception
1902      */

1903     public SAXException createSAXException(Exception e) {
1904         if (e instanceof InvocationTargetException) {
1905             Throwable t = e.getCause();
1906             if (t instanceof ThreadDeath) {
1907                 throw (ThreadDeath) t;
1908             }
1909             if (t instanceof VirtualMachineError) {
1910                 throw (VirtualMachineError) t;
1911             }
1912             if (t instanceof Exception) {
1913                 e = (Exception) t;
1914             }
1915         }
1916         return createSAXException(e.getMessage(), e);
1917     }
1918
1919     /**
1920      * Create a SAX exception which also understands about the location in
1921      * the digester file where the exception occurs
1922      * @param message The error message
1923      * @return the new exception
1924      */

1925     public SAXException createSAXException(String message) {
1926         return createSAXException(message, null);
1927     }
1928
1929
1930     // ------------------------------------------------------- Private Methods
1931
1932
1933    /**
1934      * Returns an attributes list which contains all the attributes
1935      * passed in, with any text of form "${xxx}" in an attribute value
1936      * replaced by the appropriate value from the system property.
1937      */

1938     private Attributes updateAttributes(Attributes list) {
1939
1940         if (list.getLength() == 0) {
1941             return list;
1942         }
1943
1944         AttributesImpl newAttrs = new AttributesImpl(list);
1945         int nAttributes = newAttrs.getLength();
1946         for (int i = 0; i < nAttributes; ++i) {
1947             String value = newAttrs.getValue(i);
1948             try {
1949                 newAttrs.setValue(i, IntrospectionUtils.replaceProperties(value, null, source, getClassLoader()).intern());
1950             } catch (Exception e) {
1951                 log.warn(sm.getString("digester.failedToUpdateAttributes", newAttrs.getLocalName(i), value), e);
1952             }
1953         }
1954
1955         return newAttrs;
1956     }
1957
1958
1959     /**
1960      * Return a new StringBuilder containing the same contents as the
1961      * input buffer, except that data of form ${varname} have been
1962      * replaced by the value of that var as defined in the system property.
1963      */

1964     private StringBuilder updateBodyText(StringBuilder bodyText) {
1965         String in = bodyText.toString();
1966         String out;
1967         try {
1968             out = IntrospectionUtils.replaceProperties(in, null, source, getClassLoader());
1969         } catch (Exception e) {
1970             return bodyText; // return unchanged data
1971         }
1972
1973         if (out == in) {
1974             // No substitutions required. Don't waste memory creating
1975             // a new buffer
1976             return bodyText;
1977         } else {
1978             return new StringBuilder(out);
1979         }
1980     }
1981
1982
1983     private static class EntityResolverWrapper implements EntityResolver {
1984
1985         private final EntityResolver entityResolver;
1986         private final PropertySource[] source;
1987         private final ClassLoader classLoader;
1988
1989         public EntityResolverWrapper(EntityResolver entityResolver, PropertySource[] source, ClassLoader classLoader) {
1990             this.entityResolver = entityResolver;
1991             this.source = source;
1992             this.classLoader = classLoader;
1993         }
1994
1995         @Override
1996         public InputSource resolveEntity(String publicId, String systemId)
1997                 throws SAXException, IOException {
1998             publicId = replace(publicId);
1999             systemId = replace(systemId);
2000             return entityResolver.resolveEntity(publicId, systemId);
2001         }
2002
2003         protected String replace(String input) {
2004             try {
2005                 return IntrospectionUtils.replaceProperties(input, null, source, classLoader);
2006             } catch (Exception e) {
2007                 return input;
2008             }
2009         }
2010     }
2011
2012
2013     private static class EntityResolver2Wrapper extends EntityResolverWrapper implements EntityResolver2 {
2014
2015         private final EntityResolver2 entityResolver2;
2016
2017         public EntityResolver2Wrapper(EntityResolver2 entityResolver, PropertySource[] source,
2018                 ClassLoader classLoader) {
2019             super(entityResolver, source, classLoader);
2020             this.entityResolver2 = entityResolver;
2021         }
2022
2023         @Override
2024         public InputSource getExternalSubset(String name, String baseURI)
2025                 throws SAXException, IOException {
2026             name = replace(name);
2027             baseURI = replace(baseURI);
2028             return entityResolver2.getExternalSubset(name, baseURI);
2029         }
2030
2031         @Override
2032         public InputSource resolveEntity(String name, String publicId, String baseURI,
2033                 String systemId) throws SAXException, IOException {
2034             name = replace(name);
2035             publicId = replace(publicId);
2036             baseURI = replace(baseURI);
2037             systemId = replace(systemId);
2038             return entityResolver2.resolveEntity(name, publicId, baseURI, systemId);
2039         }
2040     }
2041 }
2042