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