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