0001: /* $Id: Digester.java 529683 2007-04-17 17:43:38Z bayard $
0002: *
0003: * Licensed to the Apache Software Foundation (ASF) under one or more
0004: * contributor license agreements. See the NOTICE file distributed with
0005: * this work for additional information regarding copyright ownership.
0006: * The ASF licenses this file to You under the Apache License, Version 2.0
0007: * (the "License"); you may not use this file except in compliance with
0008: * the License. You may obtain a copy of the License at
0009: *
0010: * http://www.apache.org/licenses/LICENSE-2.0
0011: *
0012: * Unless required by applicable law or agreed to in writing, software
0013: * distributed under the License is distributed on an "AS IS" BASIS,
0014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0015: * See the License for the specific language governing permissions and
0016: * limitations under the License.
0017: */
0018:
0019: package org.apache.commons.digester;
0020:
0021: import java.io.File;
0022: import java.io.FileInputStream;
0023: import java.io.IOException;
0024: import java.io.InputStream;
0025: import java.io.Reader;
0026: import java.lang.reflect.InvocationTargetException;
0027: import java.net.URL;
0028: import java.net.URLConnection;
0029: import java.net.MalformedURLException;
0030: import java.util.ArrayList;
0031: import java.util.EmptyStackException;
0032: import java.util.HashMap;
0033: import java.util.Iterator;
0034: import java.util.List;
0035: import java.util.Map;
0036: import java.util.Properties;
0037:
0038: import javax.xml.parsers.ParserConfigurationException;
0039: import javax.xml.parsers.SAXParser;
0040: import javax.xml.parsers.SAXParserFactory;
0041:
0042: import org.apache.commons.logging.Log;
0043: import org.apache.commons.logging.LogFactory;
0044: import org.apache.commons.collections.ArrayStack;
0045:
0046: import org.xml.sax.Attributes;
0047: import org.xml.sax.ContentHandler;
0048: import org.xml.sax.EntityResolver;
0049: import org.xml.sax.ErrorHandler;
0050: import org.xml.sax.InputSource;
0051: import org.xml.sax.Locator;
0052: import org.xml.sax.SAXException;
0053: import org.xml.sax.SAXNotRecognizedException;
0054: import org.xml.sax.SAXNotSupportedException;
0055: import org.xml.sax.SAXParseException;
0056: import org.xml.sax.XMLReader;
0057: import org.xml.sax.helpers.DefaultHandler;
0058:
0059: /**
0060: * <p>A <strong>Digester</strong> processes an XML input stream by matching a
0061: * series of element nesting patterns to execute Rules that have been added
0062: * prior to the start of parsing. This package was inspired by the
0063: * <code>XmlMapper</code> class that was part of Tomcat 3.0 and 3.1,
0064: * but is organized somewhat differently.</p>
0065: *
0066: * <p>See the <a href="package-summary.html#package_description">Digester
0067: * Developer Guide</a> for more information.</p>
0068: *
0069: * <p><strong>IMPLEMENTATION NOTE</strong> - A single Digester instance may
0070: * only be used within the context of a single thread at a time, and a call
0071: * to <code>parse()</code> must be completed before another can be initiated
0072: * even from the same thread.</p>
0073: *
0074: * <p><strong>IMPLEMENTATION NOTE</strong> - A bug in Xerces 2.0.2 prevents
0075: * the support of XML schema. You need Xerces 2.1/2.3 and up to make
0076: * this class working with XML schema</p>
0077: */
0078:
0079: public class Digester extends DefaultHandler {
0080:
0081: // --------------------------------------------------------- Constructors
0082:
0083: /**
0084: * Construct a new Digester with default properties.
0085: */
0086: public Digester() {
0087:
0088: super ();
0089:
0090: }
0091:
0092: /**
0093: * Construct a new Digester, allowing a SAXParser to be passed in. This
0094: * allows Digester to be used in environments which are unfriendly to
0095: * JAXP1.1 (such as WebLogic 6.0). This may help in places where
0096: * you are able to load JAXP 1.1 classes yourself.
0097: */
0098: public Digester(SAXParser parser) {
0099:
0100: super ();
0101:
0102: this .parser = parser;
0103:
0104: }
0105:
0106: /**
0107: * Construct a new Digester, allowing an XMLReader to be passed in. This
0108: * allows Digester to be used in environments which are unfriendly to
0109: * JAXP1.1 (such as WebLogic 6.0). Note that if you use this option you
0110: * have to configure namespace and validation support yourself, as these
0111: * properties only affect the SAXParser and emtpy constructor.
0112: */
0113: public Digester(XMLReader reader) {
0114:
0115: super ();
0116:
0117: this .reader = reader;
0118:
0119: }
0120:
0121: // --------------------------------------------------- Instance Variables
0122:
0123: /**
0124: * The body text of the current element.
0125: */
0126: protected StringBuffer bodyText = new StringBuffer();
0127:
0128: /**
0129: * The stack of body text string buffers for surrounding elements.
0130: */
0131: protected ArrayStack bodyTexts = new ArrayStack();
0132:
0133: /**
0134: * Stack whose elements are List objects, each containing a list of
0135: * Rule objects as returned from Rules.getMatch(). As each xml element
0136: * in the input is entered, the matching rules are pushed onto this
0137: * stack. After the end tag is reached, the matches are popped again.
0138: * The depth of is stack is therefore exactly the same as the current
0139: * "nesting" level of the input xml.
0140: *
0141: * @since 1.6
0142: */
0143: protected ArrayStack matches = new ArrayStack(10);
0144:
0145: /**
0146: * The class loader to use for instantiating application objects.
0147: * If not specified, the context class loader, or the class loader
0148: * used to load Digester itself, is used, based on the value of the
0149: * <code>useContextClassLoader</code> variable.
0150: */
0151: protected ClassLoader classLoader = null;
0152:
0153: /**
0154: * Has this Digester been configured yet.
0155: */
0156: protected boolean configured = false;
0157:
0158: /**
0159: * The EntityResolver used by the SAX parser. By default it use this class
0160: */
0161: protected EntityResolver entityResolver;
0162:
0163: /**
0164: * The URLs of entityValidator that have been registered, keyed by the public
0165: * identifier that corresponds.
0166: */
0167: protected HashMap entityValidator = new HashMap();
0168:
0169: /**
0170: * The application-supplied error handler that is notified when parsing
0171: * warnings, errors, or fatal errors occur.
0172: */
0173: protected ErrorHandler errorHandler = null;
0174:
0175: /**
0176: * The SAXParserFactory that is created the first time we need it.
0177: */
0178: protected SAXParserFactory factory = null;
0179:
0180: /**
0181: * @deprecated This is now managed by {@link ParserFeatureSetterFactory}
0182: */
0183: protected String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
0184:
0185: /**
0186: * The Locator associated with our parser.
0187: */
0188: protected Locator locator = null;
0189:
0190: /**
0191: * The current match pattern for nested element processing.
0192: */
0193: protected String match = "";
0194:
0195: /**
0196: * Do we want a "namespace aware" parser.
0197: */
0198: protected boolean namespaceAware = false;
0199:
0200: /**
0201: * Registered namespaces we are currently processing. The key is the
0202: * namespace prefix that was declared in the document. The value is an
0203: * ArrayStack of the namespace URIs this prefix has been mapped to --
0204: * the top Stack element is the most current one. (This architecture
0205: * is required because documents can declare nested uses of the same
0206: * prefix for different Namespace URIs).
0207: */
0208: protected HashMap namespaces = new HashMap();
0209:
0210: /**
0211: * The parameters stack being utilized by CallMethodRule and
0212: * CallParamRule rules.
0213: */
0214: protected ArrayStack params = new ArrayStack();
0215:
0216: /**
0217: * The SAXParser we will use to parse the input stream.
0218: */
0219: protected SAXParser parser = null;
0220:
0221: /**
0222: * The public identifier of the DTD we are currently parsing under
0223: * (if any).
0224: */
0225: protected String publicId = null;
0226:
0227: /**
0228: * The XMLReader used to parse digester rules.
0229: */
0230: protected XMLReader reader = null;
0231:
0232: /**
0233: * The "root" element of the stack (in other words, the last object
0234: * that was popped.
0235: */
0236: protected Object root = null;
0237:
0238: /**
0239: * The <code>Rules</code> implementation containing our collection of
0240: * <code>Rule</code> instances and associated matching policy. If not
0241: * established before the first rule is added, a default implementation
0242: * will be provided.
0243: */
0244: protected Rules rules = null;
0245:
0246: /**
0247: * The XML schema language to use for validating an XML instance. By
0248: * default this value is set to <code>W3C_XML_SCHEMA</code>
0249: */
0250: protected String schemaLanguage = W3C_XML_SCHEMA;
0251:
0252: /**
0253: * The XML schema to use for validating an XML instance.
0254: */
0255: protected String schemaLocation = null;
0256:
0257: /**
0258: * The object stack being constructed.
0259: */
0260: protected ArrayStack stack = new ArrayStack();
0261:
0262: /**
0263: * Do we want to use the Context ClassLoader when loading classes
0264: * for instantiating new objects. Default is <code>false</code>.
0265: */
0266: protected boolean useContextClassLoader = false;
0267:
0268: /**
0269: * Do we want to use a validating parser.
0270: */
0271: protected boolean validating = false;
0272:
0273: /**
0274: * The Log to which most logging calls will be made.
0275: */
0276: protected Log log = LogFactory
0277: .getLog("org.apache.commons.digester.Digester");
0278:
0279: /**
0280: * The Log to which all SAX event related logging calls will be made.
0281: */
0282: protected Log saxLog = LogFactory
0283: .getLog("org.apache.commons.digester.Digester.sax");
0284:
0285: /**
0286: * The schema language supported. By default, we use this one.
0287: */
0288: protected static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
0289:
0290: /**
0291: * An optional class that substitutes values in attributes and body text.
0292: * This may be null and so a null check is always required before use.
0293: */
0294: protected Substitutor substitutor;
0295:
0296: /** Stacks used for interrule communication, indexed by name String */
0297: private HashMap stacksByName = new HashMap();
0298:
0299: /**
0300: * If not null, then calls by the parser to this object's characters,
0301: * startElement, endElement and processingInstruction methods are
0302: * forwarded to the specified object. This is intended to allow rules
0303: * to temporarily "take control" of the sax events. In particular,
0304: * this is used by NodeCreateRule.
0305: * <p>
0306: * See setCustomContentHandler.
0307: */
0308: private ContentHandler customContentHandler = null;
0309:
0310: /**
0311: * Object which will receive callbacks for every pop/push action
0312: * on the default stack or named stacks.
0313: */
0314: private StackAction stackAction = null;
0315:
0316: // ------------------------------------------------------------- Properties
0317:
0318: /**
0319: * Return the currently mapped namespace URI for the specified prefix,
0320: * if any; otherwise return <code>null</code>. These mappings come and
0321: * go dynamically as the document is parsed.
0322: *
0323: * @param prefix Prefix to look up
0324: */
0325: public String findNamespaceURI(String prefix) {
0326:
0327: ArrayStack nsStack = (ArrayStack) namespaces.get(prefix);
0328: if (nsStack == null) {
0329: return null;
0330: }
0331: try {
0332: return ((String) nsStack.peek());
0333: } catch (EmptyStackException e) {
0334: return null;
0335: }
0336:
0337: }
0338:
0339: /**
0340: * Return the class loader to be used for instantiating application objects
0341: * when required. This is determined based upon the following rules:
0342: * <ul>
0343: * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
0344: * <li>The thread context class loader, if it exists and the
0345: * <code>useContextClassLoader</code> property is set to true</li>
0346: * <li>The class loader used to load the Digester class itself.
0347: * </ul>
0348: */
0349: public ClassLoader getClassLoader() {
0350:
0351: if (this .classLoader != null) {
0352: return (this .classLoader);
0353: }
0354: if (this .useContextClassLoader) {
0355: ClassLoader classLoader = Thread.currentThread()
0356: .getContextClassLoader();
0357: if (classLoader != null) {
0358: return (classLoader);
0359: }
0360: }
0361: return (this .getClass().getClassLoader());
0362:
0363: }
0364:
0365: /**
0366: * Set the class loader to be used for instantiating application objects
0367: * when required.
0368: *
0369: * @param classLoader The new class loader to use, or <code>null</code>
0370: * to revert to the standard rules
0371: */
0372: public void setClassLoader(ClassLoader classLoader) {
0373:
0374: this .classLoader = classLoader;
0375:
0376: }
0377:
0378: /**
0379: * Return the current depth of the element stack.
0380: */
0381: public int getCount() {
0382:
0383: return (stack.size());
0384:
0385: }
0386:
0387: /**
0388: * Return the name of the XML element that is currently being processed.
0389: */
0390: public String getCurrentElementName() {
0391:
0392: String elementName = match;
0393: int lastSlash = elementName.lastIndexOf('/');
0394: if (lastSlash >= 0) {
0395: elementName = elementName.substring(lastSlash + 1);
0396: }
0397: return (elementName);
0398:
0399: }
0400:
0401: /**
0402: * Return the debugging detail level of our currently enabled logger.
0403: *
0404: * @deprecated This method now always returns 0. Digester uses the apache
0405: * jakarta commons-logging library; see the documentation for that library
0406: * for more information.
0407: */
0408: public int getDebug() {
0409:
0410: return (0);
0411:
0412: }
0413:
0414: /**
0415: * Set the debugging detail level of our currently enabled logger.
0416: *
0417: * @param debug New debugging detail level (0=off, increasing integers
0418: * for more detail)
0419: *
0420: * @deprecated This method now has no effect at all. Digester uses
0421: * the apache jakarta comons-logging library; see the documentation
0422: * for that library for more information.
0423: */
0424: public void setDebug(int debug) {
0425:
0426: ; // No action is taken
0427:
0428: }
0429:
0430: /**
0431: * Return the error handler for this Digester.
0432: */
0433: public ErrorHandler getErrorHandler() {
0434:
0435: return (this .errorHandler);
0436:
0437: }
0438:
0439: /**
0440: * Set the error handler for this Digester.
0441: *
0442: * @param errorHandler The new error handler
0443: */
0444: public void setErrorHandler(ErrorHandler errorHandler) {
0445:
0446: this .errorHandler = errorHandler;
0447:
0448: }
0449:
0450: /**
0451: * Return the SAXParserFactory we will use, creating one if necessary.
0452: */
0453: public SAXParserFactory getFactory() {
0454:
0455: if (factory == null) {
0456: factory = SAXParserFactory.newInstance();
0457: factory.setNamespaceAware(namespaceAware);
0458: factory.setValidating(validating);
0459: }
0460: return (factory);
0461:
0462: }
0463:
0464: /**
0465: * Returns a flag indicating whether the requested feature is supported
0466: * by the underlying implementation of <code>org.xml.sax.XMLReader</code>.
0467: * See <a href="http://www.saxproject.org">the saxproject website</a>
0468: * for information about the standard SAX2 feature flags.
0469: *
0470: * @param feature Name of the feature to inquire about
0471: *
0472: * @exception ParserConfigurationException if a parser configuration error
0473: * occurs
0474: * @exception SAXNotRecognizedException if the property name is
0475: * not recognized
0476: * @exception SAXNotSupportedException if the property name is
0477: * recognized but not supported
0478: */
0479: public boolean getFeature(String feature)
0480: throws ParserConfigurationException,
0481: SAXNotRecognizedException, SAXNotSupportedException {
0482:
0483: return (getFactory().getFeature(feature));
0484:
0485: }
0486:
0487: /**
0488: * Sets a flag indicating whether the requested feature is supported
0489: * by the underlying implementation of <code>org.xml.sax.XMLReader</code>.
0490: * See <a href="http://www.saxproject.org">the saxproject website</a>
0491: * for information about the standard SAX2 feature flags. In order to be
0492: * effective, this method must be called <strong>before</strong> the
0493: * <code>getParser()</code> method is called for the first time, either
0494: * directly or indirectly.
0495: *
0496: * @param feature Name of the feature to set the status for
0497: * @param value The new value for this feature
0498: *
0499: * @exception ParserConfigurationException if a parser configuration error
0500: * occurs
0501: * @exception SAXNotRecognizedException if the property name is
0502: * not recognized
0503: * @exception SAXNotSupportedException if the property name is
0504: * recognized but not supported
0505: */
0506: public void setFeature(String feature, boolean value)
0507: throws ParserConfigurationException,
0508: SAXNotRecognizedException, SAXNotSupportedException {
0509:
0510: getFactory().setFeature(feature, value);
0511:
0512: }
0513:
0514: /**
0515: * Return the current Logger associated with this instance of the Digester
0516: */
0517: public Log getLogger() {
0518:
0519: return log;
0520:
0521: }
0522:
0523: /**
0524: * Set the current logger for this Digester.
0525: */
0526: public void setLogger(Log log) {
0527:
0528: this .log = log;
0529:
0530: }
0531:
0532: /**
0533: * Gets the logger used for logging SAX-related information.
0534: * <strong>Note</strong> the output is finely grained.
0535: *
0536: * @since 1.6
0537: */
0538: public Log getSAXLogger() {
0539:
0540: return saxLog;
0541: }
0542:
0543: /**
0544: * Sets the logger used for logging SAX-related information.
0545: * <strong>Note</strong> the output is finely grained.
0546: * @param saxLog Log, not null
0547: *
0548: * @since 1.6
0549: */
0550: public void setSAXLogger(Log saxLog) {
0551:
0552: this .saxLog = saxLog;
0553: }
0554:
0555: /**
0556: * Return the current rule match path
0557: */
0558: public String getMatch() {
0559:
0560: return match;
0561:
0562: }
0563:
0564: /**
0565: * Return the "namespace aware" flag for parsers we create.
0566: */
0567: public boolean getNamespaceAware() {
0568:
0569: return (this .namespaceAware);
0570:
0571: }
0572:
0573: /**
0574: * Set the "namespace aware" flag for parsers we create.
0575: *
0576: * @param namespaceAware The new "namespace aware" flag
0577: */
0578: public void setNamespaceAware(boolean namespaceAware) {
0579:
0580: this .namespaceAware = namespaceAware;
0581:
0582: }
0583:
0584: /**
0585: * Set the publid id of the current file being parse.
0586: * @param publicId the DTD/Schema public's id.
0587: */
0588: public void setPublicId(String publicId) {
0589: this .publicId = publicId;
0590: }
0591:
0592: /**
0593: * Return the public identifier of the DTD we are currently
0594: * parsing under, if any.
0595: */
0596: public String getPublicId() {
0597:
0598: return (this .publicId);
0599:
0600: }
0601:
0602: /**
0603: * Return the namespace URI that will be applied to all subsequently
0604: * added <code>Rule</code> objects.
0605: */
0606: public String getRuleNamespaceURI() {
0607:
0608: return (getRules().getNamespaceURI());
0609:
0610: }
0611:
0612: /**
0613: * Set the namespace URI that will be applied to all subsequently
0614: * added <code>Rule</code> objects.
0615: *
0616: * @param ruleNamespaceURI Namespace URI that must match on all
0617: * subsequently added rules, or <code>null</code> for matching
0618: * regardless of the current namespace URI
0619: */
0620: public void setRuleNamespaceURI(String ruleNamespaceURI) {
0621:
0622: getRules().setNamespaceURI(ruleNamespaceURI);
0623:
0624: }
0625:
0626: /**
0627: * Return the SAXParser we will use to parse the input stream. If there
0628: * is a problem creating the parser, return <code>null</code>.
0629: */
0630: public SAXParser getParser() {
0631:
0632: // Return the parser we already created (if any)
0633: if (parser != null) {
0634: return (parser);
0635: }
0636:
0637: // Create a new parser
0638: try {
0639: if (validating && (schemaLocation != null)) {
0640: // There is no portable way to specify the location of
0641: // an xml schema to be applied to the input document, so
0642: // we have to use parser-specific code for this. That code
0643: // is hidden behind the ParserFeatureSetterFactory class.
0644:
0645: Properties properties = new Properties();
0646: properties.put("SAXParserFactory", getFactory());
0647: if (schemaLocation != null) {
0648: properties.put("schemaLocation", schemaLocation);
0649: properties.put("schemaLanguage", schemaLanguage);
0650: }
0651: parser = ParserFeatureSetterFactory
0652: .newSAXParser(properties);
0653: } else {
0654: // The user doesn't want to use any non-portable parsing features,
0655: // so we can just use the portable API here. Note that method
0656: // getFactory returns a factory already configured with the
0657: // appropriate namespaceAware and validating properties.
0658:
0659: parser = getFactory().newSAXParser();
0660: }
0661: } catch (Exception e) {
0662: log.error("Digester.getParser: ", e);
0663: return (null);
0664: }
0665:
0666: return (parser);
0667:
0668: }
0669:
0670: /**
0671: * Return the current value of the specified property for the underlying
0672: * <code>XMLReader</code> implementation.
0673: * See <a href="http://www.saxproject.org">the saxproject website</a>
0674: * for information about the standard SAX2 properties.
0675: *
0676: * @param property Property name to be retrieved
0677: *
0678: * @exception SAXNotRecognizedException if the property name is
0679: * not recognized
0680: * @exception SAXNotSupportedException if the property name is
0681: * recognized but not supported
0682: */
0683: public Object getProperty(String property)
0684: throws SAXNotRecognizedException, SAXNotSupportedException {
0685:
0686: return (getParser().getProperty(property));
0687:
0688: }
0689:
0690: /**
0691: * Set the current value of the specified property for the underlying
0692: * <code>XMLReader</code> implementation.
0693: * See <a href="http://www.saxproject.org">the saxproject website</a>
0694: * for information about the standard SAX2 properties.
0695: *
0696: * @param property Property name to be set
0697: * @param value Property value to be set
0698: *
0699: * @exception SAXNotRecognizedException if the property name is
0700: * not recognized
0701: * @exception SAXNotSupportedException if the property name is
0702: * recognized but not supported
0703: */
0704: public void setProperty(String property, Object value)
0705: throws SAXNotRecognizedException, SAXNotSupportedException {
0706:
0707: getParser().setProperty(property, value);
0708:
0709: }
0710:
0711: /**
0712: * By setting the reader in the constructor, you can bypass JAXP and
0713: * be able to use digester in Weblogic 6.0.
0714: *
0715: * @deprecated Use getXMLReader() instead, which can throw a
0716: * SAXException if the reader cannot be instantiated
0717: */
0718: public XMLReader getReader() {
0719:
0720: try {
0721: return (getXMLReader());
0722: } catch (SAXException e) {
0723: log.error("Cannot get XMLReader", e);
0724: return (null);
0725: }
0726:
0727: }
0728:
0729: /**
0730: * Return the <code>Rules</code> implementation object containing our
0731: * rules collection and associated matching policy. If none has been
0732: * established, a default implementation will be created and returned.
0733: */
0734: public Rules getRules() {
0735:
0736: if (this .rules == null) {
0737: this .rules = new RulesBase();
0738: this .rules.setDigester(this );
0739: }
0740: return (this .rules);
0741:
0742: }
0743:
0744: /**
0745: * Set the <code>Rules</code> implementation object containing our
0746: * rules collection and associated matching policy.
0747: *
0748: * @param rules New Rules implementation
0749: */
0750: public void setRules(Rules rules) {
0751:
0752: this .rules = rules;
0753: this .rules.setDigester(this );
0754:
0755: }
0756:
0757: /**
0758: * Return the XML Schema URI used for validating an XML instance.
0759: */
0760: public String getSchema() {
0761:
0762: return (this .schemaLocation);
0763:
0764: }
0765:
0766: /**
0767: * Set the XML Schema URI used for validating the input XML.
0768: * <p>
0769: * It is often desirable to <i>force</i> the input document to be
0770: * validated against a particular schema regardless of what type
0771: * the input document declares itself to be. This method allows that
0772: * to be done.
0773: * <p>
0774: * Note, however, that there is no standard API for enabling this
0775: * feature on the underlying SAX parser; this method therefore only works
0776: * for those parsers explicitly supported by Digester's
0777: * ParserFeatureSetterFactory class. If the underlying parser does not
0778: * support the feature, or is not one of the supported parsers, then
0779: * an exception will be thrown when getParser is called (explicitly,
0780: * or implicitly via the parse method).
0781: * <p>
0782: * See also method setSchemaLanguage which allows the type of the schema
0783: * specified here to be defined. By default, the schema is expected to
0784: * be a W3C xml schema definition.
0785: * <p>
0786: * IMPORTANT NOTE: This functionality was never very reliable, and has
0787: * been horribly broken since the 1.6 release of Digester. There are
0788: * currently no plans to fix it, so you are strongly recommended to
0789: * avoid using this method. Instead, create an XMLParser instance
0790: * yourself, configure validation appropriately, and pass it as a
0791: * parameter to the Digester constructor.
0792: *
0793: * @param schemaLocation a URI to the schema.
0794: */
0795: public void setSchema(String schemaLocation) {
0796:
0797: this .schemaLocation = schemaLocation;
0798:
0799: }
0800:
0801: /**
0802: * Return the XML Schema language used when parsing.
0803: */
0804: public String getSchemaLanguage() {
0805:
0806: return (this .schemaLanguage);
0807:
0808: }
0809:
0810: /**
0811: * Set the XML Schema language used when parsing. By default, we use W3C.
0812: *
0813: * @param schemaLanguage a URI to the schema language.
0814: */
0815: public void setSchemaLanguage(String schemaLanguage) {
0816:
0817: this .schemaLanguage = schemaLanguage;
0818:
0819: }
0820:
0821: /**
0822: * Return the boolean as to whether the context classloader should be used.
0823: */
0824: public boolean getUseContextClassLoader() {
0825:
0826: return useContextClassLoader;
0827:
0828: }
0829:
0830: /**
0831: * Determine whether to use the Context ClassLoader (the one found by
0832: * calling <code>Thread.currentThread().getContextClassLoader()</code>)
0833: * to resolve/load classes that are defined in various rules. If not
0834: * using Context ClassLoader, then the class-loading defaults to
0835: * using the calling-class' ClassLoader.
0836: *
0837: * @param use determines whether to use Context ClassLoader.
0838: */
0839: public void setUseContextClassLoader(boolean use) {
0840:
0841: useContextClassLoader = use;
0842:
0843: }
0844:
0845: /**
0846: * Return the validating parser flag.
0847: */
0848: public boolean getValidating() {
0849:
0850: return (this .validating);
0851:
0852: }
0853:
0854: /**
0855: * Set the validating parser flag. This must be called before
0856: * <code>parse()</code> is called the first time.
0857: *
0858: * @param validating The new validating parser flag.
0859: */
0860: public void setValidating(boolean validating) {
0861:
0862: this .validating = validating;
0863:
0864: }
0865:
0866: /**
0867: * Return the XMLReader to be used for parsing the input document.
0868: *
0869: * FIX ME: there is a bug in JAXP/XERCES that prevent the use of a
0870: * parser that contains a schema with a DTD.
0871: * @exception SAXException if no XMLReader can be instantiated
0872: */
0873: public XMLReader getXMLReader() throws SAXException {
0874: if (reader == null) {
0875: reader = getParser().getXMLReader();
0876: }
0877:
0878: reader.setDTDHandler(this );
0879: reader.setContentHandler(this );
0880:
0881: if (entityResolver == null) {
0882: reader.setEntityResolver(this );
0883: } else {
0884: reader.setEntityResolver(entityResolver);
0885: }
0886:
0887: reader.setErrorHandler(this );
0888: return reader;
0889: }
0890:
0891: /**
0892: * Gets the <code>Substitutor</code> used to convert attributes and body text.
0893: * @return Substitutor, null if not substitutions are to be performed.
0894: */
0895: public Substitutor getSubstitutor() {
0896: return substitutor;
0897: }
0898:
0899: /**
0900: * Sets the <code>Substitutor</code> to be used to convert attributes and body text.
0901: * @param substitutor the Substitutor to be used to convert attributes and body text
0902: * or null if not substitution of these values is to be performed.
0903: */
0904: public void setSubstitutor(Substitutor substitutor) {
0905: this .substitutor = substitutor;
0906: }
0907:
0908: /*
0909: * See setCustomContentHandler.
0910: *
0911: * @since 1.7
0912: */
0913: public ContentHandler getCustomContentHandler() {
0914: return customContentHandler;
0915: }
0916:
0917: /**
0918: * Redirects (or cancels redirecting) of SAX ContentHandler events to an
0919: * external object.
0920: * <p>
0921: * When this object's customContentHandler is non-null, any SAX events
0922: * received from the parser will simply be passed on to the specified
0923: * object instead of this object handling them. This allows Rule classes
0924: * to take control of the SAX event stream for a while in order to do
0925: * custom processing. Such a rule should save the old value before setting
0926: * a new one, and restore the old value in order to resume normal digester
0927: * processing.
0928: * <p>
0929: * An example of a Rule which needs this feature is NodeCreateRule.
0930: * <p>
0931: * Note that saving the old value is probably not needed as it should always
0932: * be null; a custom rule that wants to take control could only have been
0933: * called when there was no custom content handler. But it seems cleaner
0934: * to properly save/restore the value and maybe some day this will come in
0935: * useful.
0936: * <p>
0937: * Note also that this is not quite equivalent to
0938: * <pre>
0939: * digester.getXMLReader().setContentHandler(handler)
0940: * </pre>
0941: * for these reasons:
0942: * <ul>
0943: * <li>Some xml parsers don't like having setContentHandler called after
0944: * parsing has started. The Aelfred parser is one example.</li>
0945: * <li>Directing the events via the Digester object potentially allows
0946: * us to log information about those SAX events at the digester level.</li>
0947: * </ul>
0948: *
0949: * @since 1.7
0950: */
0951: public void setCustomContentHandler(ContentHandler handler) {
0952: customContentHandler = handler;
0953: }
0954:
0955: /**
0956: * Define a callback object which is invoked whever an object is pushed onto
0957: * a digester object stack, or popped off one.
0958: *
0959: * @since 1.8
0960: */
0961: public void setStackAction(StackAction stackAction) {
0962: this .stackAction = stackAction;
0963: }
0964:
0965: /**
0966: * See setStackAction.
0967: *
0968: * @since 1.8
0969: */
0970: public StackAction getStackAction() {
0971: return stackAction;
0972: }
0973:
0974: /**
0975: * Get the most current namespaces for all prefixes.
0976: *
0977: * @return Map A map with namespace prefixes as keys and most current
0978: * namespace URIs for the corresponding prefixes as values
0979: *
0980: * @since 1.8
0981: */
0982: public Map getCurrentNamespaces() {
0983: if (!namespaceAware) {
0984: log.warn("Digester is not namespace aware");
0985: }
0986: Map currentNamespaces = new HashMap();
0987: Iterator nsIterator = namespaces.entrySet().iterator();
0988: while (nsIterator.hasNext()) {
0989: Map.Entry nsEntry = (Map.Entry) nsIterator.next();
0990: try {
0991: currentNamespaces.put(nsEntry.getKey(),
0992: ((ArrayStack) nsEntry.getValue()).peek());
0993: } catch (RuntimeException e) {
0994: // rethrow, after logging
0995: log.error(e.getMessage(), e);
0996: throw e;
0997: }
0998: }
0999: return currentNamespaces;
1000: }
1001:
1002: // ------------------------------------------------- ContentHandler Methods
1003:
1004: /**
1005: * Process notification of character data received from the body of
1006: * an XML element.
1007: *
1008: * @param buffer The characters from the XML document
1009: * @param start Starting offset into the buffer
1010: * @param length Number of characters from the buffer
1011: *
1012: * @exception SAXException if a parsing error is to be reported
1013: */
1014: public void characters(char buffer[], int start, int length)
1015: throws SAXException {
1016:
1017: if (customContentHandler != null) {
1018: // forward calls instead of handling them here
1019: customContentHandler.characters(buffer, start, length);
1020: return;
1021: }
1022:
1023: if (saxLog.isDebugEnabled()) {
1024: saxLog.debug("characters("
1025: + new String(buffer, start, length) + ")");
1026: }
1027:
1028: bodyText.append(buffer, start, length);
1029:
1030: }
1031:
1032: /**
1033: * Process notification of the end of the document being reached.
1034: *
1035: * @exception SAXException if a parsing error is to be reported
1036: */
1037: public void endDocument() throws SAXException {
1038:
1039: if (saxLog.isDebugEnabled()) {
1040: if (getCount() > 1) {
1041: saxLog.debug("endDocument(): " + getCount()
1042: + " elements left");
1043: } else {
1044: saxLog.debug("endDocument()");
1045: }
1046: }
1047:
1048: // Fire "finish" events for all defined rules
1049: Iterator rules = getRules().rules().iterator();
1050: while (rules.hasNext()) {
1051: Rule rule = (Rule) rules.next();
1052: try {
1053: rule.finish();
1054: } catch (Exception e) {
1055: log.error("Finish event threw exception", e);
1056: throw createSAXException(e);
1057: } catch (Error e) {
1058: log.error("Finish event threw error", e);
1059: throw e;
1060: }
1061: }
1062:
1063: // Perform final cleanup
1064: clear();
1065:
1066: }
1067:
1068: /**
1069: * Process notification of the end of an XML element being reached.
1070: *
1071: * @param namespaceURI - The Namespace URI, or the empty string if the
1072: * element has no Namespace URI or if Namespace processing is not
1073: * being performed.
1074: * @param localName - The local name (without prefix), or the empty
1075: * string if Namespace processing is not being performed.
1076: * @param qName - The qualified XML 1.0 name (with prefix), or the
1077: * empty string if qualified names are not available.
1078: * @exception SAXException if a parsing error is to be reported
1079: */
1080: public void endElement(String namespaceURI, String localName,
1081: String qName) throws SAXException {
1082:
1083: if (customContentHandler != null) {
1084: // forward calls instead of handling them here
1085: customContentHandler.endElement(namespaceURI, localName,
1086: qName);
1087: return;
1088: }
1089:
1090: boolean debug = log.isDebugEnabled();
1091:
1092: if (debug) {
1093: if (saxLog.isDebugEnabled()) {
1094: saxLog.debug("endElement(" + namespaceURI + ","
1095: + localName + "," + qName + ")");
1096: }
1097: log.debug(" match='" + match + "'");
1098: log.debug(" bodyText='" + bodyText + "'");
1099: }
1100:
1101: // the actual element name is either in localName or qName, depending
1102: // on whether the parser is namespace aware
1103: String name = localName;
1104: if ((name == null) || (name.length() < 1)) {
1105: name = qName;
1106: }
1107:
1108: // Fire "body" events for all relevant rules
1109: List rules = (List) matches.pop();
1110: if ((rules != null) && (rules.size() > 0)) {
1111: String bodyText = this .bodyText.toString();
1112: Substitutor substitutor = getSubstitutor();
1113: if (substitutor != null) {
1114: bodyText = substitutor.substitute(bodyText);
1115: }
1116: for (int i = 0; i < rules.size(); i++) {
1117: try {
1118: Rule rule = (Rule) rules.get(i);
1119: if (debug) {
1120: log.debug(" Fire body() for " + rule);
1121: }
1122: rule.body(namespaceURI, name, bodyText);
1123: } catch (Exception e) {
1124: log.error("Body event threw exception", e);
1125: throw createSAXException(e);
1126: } catch (Error e) {
1127: log.error("Body event threw error", e);
1128: throw e;
1129: }
1130: }
1131: } else {
1132: if (debug) {
1133: log.debug(" No rules found matching '" + match + "'.");
1134: }
1135: }
1136:
1137: // Recover the body text from the surrounding element
1138: bodyText = (StringBuffer) bodyTexts.pop();
1139: if (debug) {
1140: log.debug(" Popping body text '" + bodyText.toString()
1141: + "'");
1142: }
1143:
1144: // Fire "end" events for all relevant rules in reverse order
1145: if (rules != null) {
1146: for (int i = 0; i < rules.size(); i++) {
1147: int j = (rules.size() - i) - 1;
1148: try {
1149: Rule rule = (Rule) rules.get(j);
1150: if (debug) {
1151: log.debug(" Fire end() for " + rule);
1152: }
1153: rule.end(namespaceURI, name);
1154: } catch (Exception e) {
1155: log.error("End event threw exception", e);
1156: throw createSAXException(e);
1157: } catch (Error e) {
1158: log.error("End event threw error", e);
1159: throw e;
1160: }
1161: }
1162: }
1163:
1164: // Recover the previous match expression
1165: int slash = match.lastIndexOf('/');
1166: if (slash >= 0) {
1167: match = match.substring(0, slash);
1168: } else {
1169: match = "";
1170: }
1171:
1172: }
1173:
1174: /**
1175: * Process notification that a namespace prefix is going out of scope.
1176: *
1177: * @param prefix Prefix that is going out of scope
1178: *
1179: * @exception SAXException if a parsing error is to be reported
1180: */
1181: public void endPrefixMapping(String prefix) throws SAXException {
1182:
1183: if (saxLog.isDebugEnabled()) {
1184: saxLog.debug("endPrefixMapping(" + prefix + ")");
1185: }
1186:
1187: // Deregister this prefix mapping
1188: ArrayStack stack = (ArrayStack) namespaces.get(prefix);
1189: if (stack == null) {
1190: return;
1191: }
1192: try {
1193: stack.pop();
1194: if (stack.empty())
1195: namespaces.remove(prefix);
1196: } catch (EmptyStackException e) {
1197: throw createSAXException("endPrefixMapping popped too many times");
1198: }
1199:
1200: }
1201:
1202: /**
1203: * Process notification of ignorable whitespace received from the body of
1204: * an XML element.
1205: *
1206: * @param buffer The characters from the XML document
1207: * @param start Starting offset into the buffer
1208: * @param len Number of characters from the buffer
1209: *
1210: * @exception SAXException if a parsing error is to be reported
1211: */
1212: public void ignorableWhitespace(char buffer[], int start, int len)
1213: throws SAXException {
1214:
1215: if (saxLog.isDebugEnabled()) {
1216: saxLog.debug("ignorableWhitespace("
1217: + new String(buffer, start, len) + ")");
1218: }
1219:
1220: ; // No processing required
1221:
1222: }
1223:
1224: /**
1225: * Process notification of a processing instruction that was encountered.
1226: *
1227: * @param target The processing instruction target
1228: * @param data The processing instruction data (if any)
1229: *
1230: * @exception SAXException if a parsing error is to be reported
1231: */
1232: public void processingInstruction(String target, String data)
1233: throws SAXException {
1234:
1235: if (customContentHandler != null) {
1236: // forward calls instead of handling them here
1237: customContentHandler.processingInstruction(target, data);
1238: return;
1239: }
1240:
1241: if (saxLog.isDebugEnabled()) {
1242: saxLog.debug("processingInstruction('" + target + "','"
1243: + data + "')");
1244: }
1245:
1246: ; // No processing is required
1247:
1248: }
1249:
1250: /**
1251: * Gets the document locator associated with our parser.
1252: *
1253: * @return the Locator supplied by the document parser
1254: */
1255: public Locator getDocumentLocator() {
1256:
1257: return locator;
1258:
1259: }
1260:
1261: /**
1262: * Sets the document locator associated with our parser.
1263: *
1264: * @param locator The new locator
1265: */
1266: public void setDocumentLocator(Locator locator) {
1267:
1268: if (saxLog.isDebugEnabled()) {
1269: saxLog.debug("setDocumentLocator(" + locator + ")");
1270: }
1271:
1272: this .locator = locator;
1273:
1274: }
1275:
1276: /**
1277: * Process notification of a skipped entity.
1278: *
1279: * @param name Name of the skipped entity
1280: *
1281: * @exception SAXException if a parsing error is to be reported
1282: */
1283: public void skippedEntity(String name) throws SAXException {
1284:
1285: if (saxLog.isDebugEnabled()) {
1286: saxLog.debug("skippedEntity(" + name + ")");
1287: }
1288:
1289: ; // No processing required
1290:
1291: }
1292:
1293: /**
1294: * Process notification of the beginning of the document being reached.
1295: *
1296: * @exception SAXException if a parsing error is to be reported
1297: */
1298: public void startDocument() throws SAXException {
1299:
1300: if (saxLog.isDebugEnabled()) {
1301: saxLog.debug("startDocument()");
1302: }
1303:
1304: // ensure that the digester is properly configured, as
1305: // the digester could be used as a SAX ContentHandler
1306: // rather than via the parse() methods.
1307: configure();
1308: }
1309:
1310: /**
1311: * Process notification of the start of an XML element being reached.
1312: *
1313: * @param namespaceURI The Namespace URI, or the empty string if the element
1314: * has no Namespace URI or if Namespace processing is not being performed.
1315: * @param localName The local name (without prefix), or the empty
1316: * string if Namespace processing is not being performed.
1317: * @param qName The qualified name (with prefix), or the empty
1318: * string if qualified names are not available.\
1319: * @param list The attributes attached to the element. If there are
1320: * no attributes, it shall be an empty Attributes object.
1321: * @exception SAXException if a parsing error is to be reported
1322: */
1323: public void startElement(String namespaceURI, String localName,
1324: String qName, Attributes list) throws SAXException {
1325: boolean debug = log.isDebugEnabled();
1326:
1327: if (customContentHandler != null) {
1328: // forward calls instead of handling them here
1329: customContentHandler.startElement(namespaceURI, localName,
1330: qName, list);
1331: return;
1332: }
1333:
1334: if (saxLog.isDebugEnabled()) {
1335: saxLog.debug("startElement(" + namespaceURI + ","
1336: + localName + "," + qName + ")");
1337: }
1338:
1339: // Save the body text accumulated for our surrounding element
1340: bodyTexts.push(bodyText);
1341: if (debug) {
1342: log.debug(" Pushing body text '" + bodyText.toString()
1343: + "'");
1344: }
1345: bodyText = new StringBuffer();
1346:
1347: // the actual element name is either in localName or qName, depending
1348: // on whether the parser is namespace aware
1349: String name = localName;
1350: if ((name == null) || (name.length() < 1)) {
1351: name = qName;
1352: }
1353:
1354: // Compute the current matching rule
1355: StringBuffer sb = new StringBuffer(match);
1356: if (match.length() > 0) {
1357: sb.append('/');
1358: }
1359: sb.append(name);
1360: match = sb.toString();
1361: if (debug) {
1362: log.debug(" New match='" + match + "'");
1363: }
1364:
1365: // Fire "begin" events for all relevant rules
1366: List rules = getRules().match(namespaceURI, match);
1367: matches.push(rules);
1368: if ((rules != null) && (rules.size() > 0)) {
1369: Substitutor substitutor = getSubstitutor();
1370: if (substitutor != null) {
1371: list = substitutor.substitute(list);
1372: }
1373: for (int i = 0; i < rules.size(); i++) {
1374: try {
1375: Rule rule = (Rule) rules.get(i);
1376: if (debug) {
1377: log.debug(" Fire begin() for " + rule);
1378: }
1379: rule.begin(namespaceURI, name, list);
1380: } catch (Exception e) {
1381: log.error("Begin event threw exception", e);
1382: throw createSAXException(e);
1383: } catch (Error e) {
1384: log.error("Begin event threw error", e);
1385: throw e;
1386: }
1387: }
1388: } else {
1389: if (debug) {
1390: log.debug(" No rules found matching '" + match + "'.");
1391: }
1392: }
1393:
1394: }
1395:
1396: /**
1397: * Process notification that a namespace prefix is coming in to scope.
1398: *
1399: * @param prefix Prefix that is being declared
1400: * @param namespaceURI Corresponding namespace URI being mapped to
1401: *
1402: * @exception SAXException if a parsing error is to be reported
1403: */
1404: public void startPrefixMapping(String prefix, String namespaceURI)
1405: throws SAXException {
1406:
1407: if (saxLog.isDebugEnabled()) {
1408: saxLog.debug("startPrefixMapping(" + prefix + ","
1409: + namespaceURI + ")");
1410: }
1411:
1412: // Register this prefix mapping
1413: ArrayStack stack = (ArrayStack) namespaces.get(prefix);
1414: if (stack == null) {
1415: stack = new ArrayStack();
1416: namespaces.put(prefix, stack);
1417: }
1418: stack.push(namespaceURI);
1419:
1420: }
1421:
1422: // ----------------------------------------------------- DTDHandler Methods
1423:
1424: /**
1425: * Receive notification of a notation declaration event.
1426: *
1427: * @param name The notation name
1428: * @param publicId The public identifier (if any)
1429: * @param systemId The system identifier (if any)
1430: */
1431: public void notationDecl(String name, String publicId,
1432: String systemId) {
1433:
1434: if (saxLog.isDebugEnabled()) {
1435: saxLog.debug("notationDecl(" + name + "," + publicId + ","
1436: + systemId + ")");
1437: }
1438:
1439: }
1440:
1441: /**
1442: * Receive notification of an unparsed entity declaration event.
1443: *
1444: * @param name The unparsed entity name
1445: * @param publicId The public identifier (if any)
1446: * @param systemId The system identifier (if any)
1447: * @param notation The name of the associated notation
1448: */
1449: public void unparsedEntityDecl(String name, String publicId,
1450: String systemId, String notation) {
1451:
1452: if (saxLog.isDebugEnabled()) {
1453: saxLog.debug("unparsedEntityDecl(" + name + "," + publicId
1454: + "," + systemId + "," + notation + ")");
1455: }
1456:
1457: }
1458:
1459: // ----------------------------------------------- EntityResolver Methods
1460:
1461: /**
1462: * Set the <code>EntityResolver</code> used by SAX when resolving
1463: * public id and system id.
1464: * This must be called before the first call to <code>parse()</code>.
1465: * @param entityResolver a class that implement the <code>EntityResolver</code> interface.
1466: */
1467: public void setEntityResolver(EntityResolver entityResolver) {
1468: this .entityResolver = entityResolver;
1469: }
1470:
1471: /**
1472: * Return the Entity Resolver used by the SAX parser.
1473: * @return Return the Entity Resolver used by the SAX parser.
1474: */
1475: public EntityResolver getEntityResolver() {
1476: return entityResolver;
1477: }
1478:
1479: /**
1480: * Resolve the requested external entity.
1481: *
1482: * @param publicId The public identifier of the entity being referenced
1483: * @param systemId The system identifier of the entity being referenced
1484: *
1485: * @exception SAXException if a parsing exception occurs
1486: *
1487: */
1488: public InputSource resolveEntity(String publicId, String systemId)
1489: throws SAXException {
1490:
1491: if (saxLog.isDebugEnabled()) {
1492: saxLog.debug("resolveEntity('" + publicId + "', '"
1493: + systemId + "')");
1494: }
1495:
1496: if (publicId != null)
1497: this .publicId = publicId;
1498:
1499: // Has this system identifier been registered?
1500: URL entityURL = null;
1501: if (publicId != null) {
1502: entityURL = (URL) entityValidator.get(publicId);
1503: }
1504:
1505: // Redirect the schema location to a local destination
1506: if (schemaLocation != null && entityURL == null
1507: && systemId != null) {
1508: entityURL = (URL) entityValidator.get(systemId);
1509: }
1510:
1511: if (entityURL == null) {
1512: if (systemId == null) {
1513: // cannot resolve
1514: if (log.isDebugEnabled()) {
1515: log.debug(" Cannot resolve entity: '" + entityURL
1516: + "'");
1517: }
1518: return (null);
1519:
1520: } else {
1521: // try to resolve using system ID
1522: if (log.isDebugEnabled()) {
1523: log.debug(" Trying to resolve using system ID '"
1524: + systemId + "'");
1525: }
1526: try {
1527: entityURL = new URL(systemId);
1528: } catch (MalformedURLException e) {
1529: throw new IllegalArgumentException(
1530: "Malformed URL '" + systemId + "' : "
1531: + e.getMessage());
1532: }
1533: }
1534: }
1535:
1536: // Return an input source to our alternative URL
1537: if (log.isDebugEnabled()) {
1538: log
1539: .debug(" Resolving to alternate DTD '" + entityURL
1540: + "'");
1541: }
1542:
1543: try {
1544: return createInputSourceFromURL(entityURL);
1545: } catch (Exception e) {
1546: throw createSAXException(e);
1547: }
1548: }
1549:
1550: // ------------------------------------------------- ErrorHandler Methods
1551:
1552: /**
1553: * Forward notification of a parsing error to the application supplied
1554: * error handler (if any).
1555: *
1556: * @param exception The error information
1557: *
1558: * @exception SAXException if a parsing exception occurs
1559: */
1560: public void error(SAXParseException exception) throws SAXException {
1561:
1562: log.error("Parse Error at line " + exception.getLineNumber()
1563: + " column " + exception.getColumnNumber() + ": "
1564: + exception.getMessage(), exception);
1565: if (errorHandler != null) {
1566: errorHandler.error(exception);
1567: }
1568:
1569: }
1570:
1571: /**
1572: * Forward notification of a fatal parsing error to the application
1573: * supplied error handler (if any).
1574: *
1575: * @param exception The fatal error information
1576: *
1577: * @exception SAXException if a parsing exception occurs
1578: */
1579: public void fatalError(SAXParseException exception)
1580: throws SAXException {
1581:
1582: log.error("Parse Fatal Error at line "
1583: + exception.getLineNumber() + " column "
1584: + exception.getColumnNumber() + ": "
1585: + exception.getMessage(), exception);
1586: if (errorHandler != null) {
1587: errorHandler.fatalError(exception);
1588: }
1589:
1590: }
1591:
1592: /**
1593: * Forward notification of a parse warning to the application supplied
1594: * error handler (if any).
1595: *
1596: * @param exception The warning information
1597: *
1598: * @exception SAXException if a parsing exception occurs
1599: */
1600: public void warning(SAXParseException exception)
1601: throws SAXException {
1602: if (errorHandler != null) {
1603: log.warn("Parse Warning Error at line "
1604: + exception.getLineNumber() + " column "
1605: + exception.getColumnNumber() + ": "
1606: + exception.getMessage(), exception);
1607:
1608: errorHandler.warning(exception);
1609: }
1610:
1611: }
1612:
1613: // ------------------------------------------------------- Public Methods
1614:
1615: /**
1616: * Log a message to our associated logger.
1617: *
1618: * @param message The message to be logged
1619: * @deprecated Call getLogger() and use it's logging methods
1620: */
1621: public void log(String message) {
1622:
1623: log.info(message);
1624:
1625: }
1626:
1627: /**
1628: * Log a message and exception to our associated logger.
1629: *
1630: * @param message The message to be logged
1631: * @deprecated Call getLogger() and use it's logging methods
1632: */
1633: public void log(String message, Throwable exception) {
1634:
1635: log.error(message, exception);
1636:
1637: }
1638:
1639: /**
1640: * Parse the content of the specified file using this Digester. Returns
1641: * the root element from the object stack (if any).
1642: *
1643: * @param file File containing the XML data to be parsed
1644: *
1645: * @exception IOException if an input/output error occurs
1646: * @exception SAXException if a parsing exception occurs
1647: */
1648: public Object parse(File file) throws IOException, SAXException {
1649:
1650: configure();
1651: InputSource input = new InputSource(new FileInputStream(file));
1652: input.setSystemId(file.toURL().toString());
1653: getXMLReader().parse(input);
1654: cleanup();
1655: return (root);
1656:
1657: }
1658:
1659: /**
1660: * Parse the content of the specified input source using this Digester.
1661: * Returns the root element from the object stack (if any).
1662: *
1663: * @param input Input source containing the XML data to be parsed
1664: *
1665: * @exception IOException if an input/output error occurs
1666: * @exception SAXException if a parsing exception occurs
1667: */
1668: public Object parse(InputSource input) throws IOException,
1669: SAXException {
1670:
1671: configure();
1672: getXMLReader().parse(input);
1673: cleanup();
1674: return (root);
1675:
1676: }
1677:
1678: /**
1679: * Parse the content of the specified input stream using this Digester.
1680: * Returns the root element from the object stack (if any).
1681: *
1682: * @param input Input stream containing the XML data to be parsed
1683: *
1684: * @exception IOException if an input/output error occurs
1685: * @exception SAXException if a parsing exception occurs
1686: */
1687: public Object parse(InputStream input) throws IOException,
1688: SAXException {
1689:
1690: configure();
1691: InputSource is = new InputSource(input);
1692: getXMLReader().parse(is);
1693: cleanup();
1694: return (root);
1695:
1696: }
1697:
1698: /**
1699: * Parse the content of the specified reader using this Digester.
1700: * Returns the root element from the object stack (if any).
1701: *
1702: * @param reader Reader containing the XML data to be parsed
1703: *
1704: * @exception IOException if an input/output error occurs
1705: * @exception SAXException if a parsing exception occurs
1706: */
1707: public Object parse(Reader reader) throws IOException, SAXException {
1708:
1709: configure();
1710: InputSource is = new InputSource(reader);
1711: getXMLReader().parse(is);
1712: cleanup();
1713: return (root);
1714:
1715: }
1716:
1717: /**
1718: * Parse the content of the specified URI using this Digester.
1719: * Returns the root element from the object stack (if any).
1720: *
1721: * @param uri URI containing the XML data to be parsed
1722: *
1723: * @exception IOException if an input/output error occurs
1724: * @exception SAXException if a parsing exception occurs
1725: */
1726: public Object parse(String uri) throws IOException, SAXException {
1727:
1728: configure();
1729: InputSource is = createInputSourceFromURL(uri);
1730: getXMLReader().parse(is);
1731: cleanup();
1732: return (root);
1733:
1734: }
1735:
1736: /**
1737: * Parse the content of the specified URL using this Digester.
1738: * Returns the root element from the object stack (if any).
1739: *
1740: * @param url URL containing the XML data to be parsed
1741: *
1742: * @exception IOException if an input/output error occurs
1743: * @exception SAXException if a parsing exception occurs
1744: *
1745: * @since 1.8
1746: */
1747: public Object parse(URL url) throws IOException, SAXException {
1748:
1749: configure();
1750: InputSource is = createInputSourceFromURL(url);
1751: getXMLReader().parse(is);
1752: cleanup();
1753: return (root);
1754:
1755: }
1756:
1757: /**
1758: * <p>Register the specified DTD URL for the specified public identifier.
1759: * This must be called before the first call to <code>parse()</code>.
1760: * </p><p>
1761: * <code>Digester</code> contains an internal <code>EntityResolver</code>
1762: * implementation. This maps <code>PUBLICID</code>'s to URLs
1763: * (from which the resource will be loaded). A common use case for this
1764: * method is to register local URLs (possibly computed at runtime by a
1765: * classloader) for DTDs. This allows the performance advantage of using
1766: * a local version without having to ensure every <code>SYSTEM</code>
1767: * URI on every processed xml document is local. This implementation provides
1768: * only basic functionality. If more sophisticated features are required,
1769: * using {@link #setEntityResolver} to set a custom resolver is recommended.
1770: * </p><p>
1771: * <strong>Note:</strong> This method will have no effect when a custom
1772: * <code>EntityResolver</code> has been set. (Setting a custom
1773: * <code>EntityResolver</code> overrides the internal implementation.)
1774: * </p>
1775: * @param publicId Public identifier of the DTD to be resolved
1776: * @param entityURL The URL to use for reading this DTD
1777: *
1778: * @since 1.8
1779: */
1780: public void register(String publicId, URL entityURL) {
1781:
1782: if (log.isDebugEnabled()) {
1783: log.debug("register('" + publicId + "', '" + entityURL
1784: + "'");
1785: }
1786: entityValidator.put(publicId, entityURL);
1787:
1788: }
1789:
1790: /**
1791: * <p>Convenience method that registers the string version of an entity URL
1792: * instead of a URL version.</p>
1793: *
1794: * @param publicId Public identifier of the entity to be resolved
1795: * @param entityURL The URL to use for reading this entity
1796: */
1797: public void register(String publicId, String entityURL) {
1798:
1799: if (log.isDebugEnabled()) {
1800: log.debug("register('" + publicId + "', '" + entityURL
1801: + "'");
1802: }
1803: try {
1804: entityValidator.put(publicId, new URL(entityURL));
1805: } catch (MalformedURLException e) {
1806: throw new IllegalArgumentException("Malformed URL '"
1807: + entityURL + "' : " + e.getMessage());
1808: }
1809:
1810: }
1811:
1812: /**
1813: * <p><code>List</code> of <code>InputSource</code> instances
1814: * created by a <code>createInputSourceFromURL()</code> method
1815: * call. These represent open input streams that need to be
1816: * closed to avoid resource leaks, as well as potentially locked
1817: * JAR files on Windows.</p>
1818: */
1819: protected List inputSources = new ArrayList(5);
1820:
1821: /**
1822: * Given a URL, return an InputSource that reads from that URL.
1823: * <p>
1824: * Ideally this function would not be needed and code could just use
1825: * <code>new InputSource(entityURL)</code>. Unfortunately it appears
1826: * that when the entityURL points to a file within a jar archive a
1827: * caching mechanism inside the InputSource implementation causes a
1828: * file-handle to the jar file to remain open. On Windows systems
1829: * this then causes the jar archive file to be locked on disk
1830: * ("in use") which makes it impossible to delete the jar file -
1831: * and that really stuffs up "undeploy" in webapps in particular.
1832: * <p>
1833: * In JDK1.4 and later, Apache XercesJ is used as the xml parser.
1834: * The InputSource object provided is converted into an XMLInputSource,
1835: * and eventually passed to an instance of XMLDocumentScannerImpl to
1836: * specify the source data to be converted into tokens for the rest
1837: * of the XMLReader code to handle. XMLDocumentScannerImpl calls
1838: * fEntityManager.startDocumentEntity(source), where fEntityManager
1839: * is declared in ancestor class XMLScanner to be an XMLEntityManager. In
1840: * that class, if the input source stream is null, then:
1841: * <pre>
1842: * URL location = new URL(expandedSystemId);
1843: * URLConnection connect = location.openConnection();
1844: * if (connect instanceof HttpURLConnection) {
1845: * setHttpProperties(connect,xmlInputSource);
1846: * }
1847: * stream = connect.getInputStream();
1848: * </pre>
1849: * This method pretty much duplicates the standard behaviour, except
1850: * that it calls URLConnection.setUseCaches(false) before opening
1851: * the connection.
1852: *
1853: * @since 1.8
1854: */
1855: public InputSource createInputSourceFromURL(URL url)
1856: throws MalformedURLException, IOException {
1857:
1858: URLConnection connection = url.openConnection();
1859: connection.setUseCaches(false);
1860: InputStream stream = connection.getInputStream();
1861: InputSource source = new InputSource(stream);
1862: source.setSystemId(url.toExternalForm());
1863: inputSources.add(source);
1864: return source;
1865:
1866: }
1867:
1868: /**
1869: * <p>Convenience method that creates an <code>InputSource</code>
1870: * from the string version of a URL.</p>
1871: *
1872: * @param url URL for which to create an <code>InputSource</code>
1873: *
1874: * @since 1.8
1875: */
1876: public InputSource createInputSourceFromURL(String url)
1877: throws MalformedURLException, IOException {
1878:
1879: return createInputSourceFromURL(new URL(url));
1880:
1881: }
1882:
1883: // --------------------------------------------------------- Rule Methods
1884:
1885: /**
1886: * <p>Register a new Rule matching the specified pattern.
1887: * This method sets the <code>Digester</code> property on the rule.</p>
1888: *
1889: * @param pattern Element matching pattern
1890: * @param rule Rule to be registered
1891: */
1892: public void addRule(String pattern, Rule rule) {
1893:
1894: rule.setDigester(this );
1895: getRules().add(pattern, rule);
1896:
1897: }
1898:
1899: /**
1900: * Register a set of Rule instances defined in a RuleSet.
1901: *
1902: * @param ruleSet The RuleSet instance to configure from
1903: */
1904: public void addRuleSet(RuleSet ruleSet) {
1905:
1906: String oldNamespaceURI = getRuleNamespaceURI();
1907: String newNamespaceURI = ruleSet.getNamespaceURI();
1908: if (log.isDebugEnabled()) {
1909: if (newNamespaceURI == null) {
1910: log.debug("addRuleSet() with no namespace URI");
1911: } else {
1912: log.debug("addRuleSet() with namespace URI "
1913: + newNamespaceURI);
1914: }
1915: }
1916: setRuleNamespaceURI(newNamespaceURI);
1917: ruleSet.addRuleInstances(this );
1918: setRuleNamespaceURI(oldNamespaceURI);
1919:
1920: }
1921:
1922: /**
1923: * Add a "bean property setter" rule for the specified parameters.
1924: *
1925: * @param pattern Element matching pattern
1926: * @see BeanPropertySetterRule
1927: */
1928: public void addBeanPropertySetter(String pattern) {
1929:
1930: addRule(pattern, new BeanPropertySetterRule());
1931:
1932: }
1933:
1934: /**
1935: * Add a "bean property setter" rule for the specified parameters.
1936: *
1937: * @param pattern Element matching pattern
1938: * @param propertyName Name of property to set
1939: * @see BeanPropertySetterRule
1940: */
1941: public void addBeanPropertySetter(String pattern,
1942: String propertyName) {
1943:
1944: addRule(pattern, new BeanPropertySetterRule(propertyName));
1945:
1946: }
1947:
1948: /**
1949: * Add an "call method" rule for a method which accepts no arguments.
1950: *
1951: * @param pattern Element matching pattern
1952: * @param methodName Method name to be called
1953: * @see CallMethodRule
1954: */
1955: public void addCallMethod(String pattern, String methodName) {
1956:
1957: addRule(pattern, new CallMethodRule(methodName));
1958:
1959: }
1960:
1961: /**
1962: * Add an "call method" rule for the specified parameters.
1963: *
1964: * @param pattern Element matching pattern
1965: * @param methodName Method name to be called
1966: * @param paramCount Number of expected parameters (or zero
1967: * for a single parameter from the body of this element)
1968: * @see CallMethodRule
1969: */
1970: public void addCallMethod(String pattern, String methodName,
1971: int paramCount) {
1972:
1973: addRule(pattern, new CallMethodRule(methodName, paramCount));
1974:
1975: }
1976:
1977: /**
1978: * Add an "call method" rule for the specified parameters.
1979: * If <code>paramCount</code> is set to zero the rule will use
1980: * the body of the matched element as the single argument of the
1981: * method, unless <code>paramTypes</code> is null or empty, in this
1982: * case the rule will call the specified method with no arguments.
1983: *
1984: * @param pattern Element matching pattern
1985: * @param methodName Method name to be called
1986: * @param paramCount Number of expected parameters (or zero
1987: * for a single parameter from the body of this element)
1988: * @param paramTypes Set of Java class names for the types
1989: * of the expected parameters
1990: * (if you wish to use a primitive type, specify the corresonding
1991: * Java wrapper class instead, such as <code>java.lang.Boolean</code>
1992: * for a <code>boolean</code> parameter)
1993: * @see CallMethodRule
1994: */
1995: public void addCallMethod(String pattern, String methodName,
1996: int paramCount, String paramTypes[]) {
1997:
1998: addRule(pattern, new CallMethodRule(methodName, paramCount,
1999: paramTypes));
2000:
2001: }
2002:
2003: /**
2004: * Add an "call method" rule for the specified parameters.
2005: * If <code>paramCount</code> is set to zero the rule will use
2006: * the body of the matched element as the single argument of the
2007: * method, unless <code>paramTypes</code> is null or empty, in this
2008: * case the rule will call the specified method with no arguments.
2009: *
2010: * @param pattern Element matching pattern
2011: * @param methodName Method name to be called
2012: * @param paramCount Number of expected parameters (or zero
2013: * for a single parameter from the body of this element)
2014: * @param paramTypes The Java class names of the arguments
2015: * (if you wish to use a primitive type, specify the corresonding
2016: * Java wrapper class instead, such as <code>java.lang.Boolean</code>
2017: * for a <code>boolean</code> parameter)
2018: * @see CallMethodRule
2019: */
2020: public void addCallMethod(String pattern, String methodName,
2021: int paramCount, Class paramTypes[]) {
2022:
2023: addRule(pattern, new CallMethodRule(methodName, paramCount,
2024: paramTypes));
2025:
2026: }
2027:
2028: /**
2029: * Add a "call parameter" rule for the specified parameters.
2030: *
2031: * @param pattern Element matching pattern
2032: * @param paramIndex Zero-relative parameter index to set
2033: * (from the body of this element)
2034: * @see CallParamRule
2035: */
2036: public void addCallParam(String pattern, int paramIndex) {
2037:
2038: addRule(pattern, new CallParamRule(paramIndex));
2039:
2040: }
2041:
2042: /**
2043: * Add a "call parameter" rule for the specified parameters.
2044: *
2045: * @param pattern Element matching pattern
2046: * @param paramIndex Zero-relative parameter index to set
2047: * (from the specified attribute)
2048: * @param attributeName Attribute whose value is used as the
2049: * parameter value
2050: * @see CallParamRule
2051: */
2052: public void addCallParam(String pattern, int paramIndex,
2053: String attributeName) {
2054:
2055: addRule(pattern, new CallParamRule(paramIndex, attributeName));
2056:
2057: }
2058:
2059: /**
2060: * Add a "call parameter" rule.
2061: * This will either take a parameter from the stack
2062: * or from the current element body text.
2063: *
2064: * @param paramIndex The zero-relative parameter number
2065: * @param fromStack Should the call parameter be taken from the top of the stack?
2066: * @see CallParamRule
2067: */
2068: public void addCallParam(String pattern, int paramIndex,
2069: boolean fromStack) {
2070:
2071: addRule(pattern, new CallParamRule(paramIndex, fromStack));
2072:
2073: }
2074:
2075: /**
2076: * Add a "call parameter" rule that sets a parameter from the stack.
2077: * This takes a parameter from the given position on the stack.
2078: *
2079: * @param paramIndex The zero-relative parameter number
2080: * @param stackIndex set the call parameter to the stackIndex'th object down the stack,
2081: * where 0 is the top of the stack, 1 the next element down and so on
2082: * @see CallMethodRule
2083: */
2084: public void addCallParam(String pattern, int paramIndex,
2085: int stackIndex) {
2086:
2087: addRule(pattern, new CallParamRule(paramIndex, stackIndex));
2088:
2089: }
2090:
2091: /**
2092: * Add a "call parameter" rule that sets a parameter from the current
2093: * <code>Digester</code> matching path.
2094: * This is sometimes useful when using rules that support wildcards.
2095: *
2096: * @param pattern the pattern that this rule should match
2097: * @param paramIndex The zero-relative parameter number
2098: * @see CallMethodRule
2099: */
2100: public void addCallParamPath(String pattern, int paramIndex) {
2101: addRule(pattern, new PathCallParamRule(paramIndex));
2102: }
2103:
2104: /**
2105: * Add a "call parameter" rule that sets a parameter from a
2106: * caller-provided object. This can be used to pass constants such as
2107: * strings to methods; it can also be used to pass mutable objects,
2108: * providing ways for objects to do things like "register" themselves
2109: * with some shared object.
2110: * <p>
2111: * Note that when attempting to locate a matching method to invoke,
2112: * the true type of the paramObj is used, so that despite the paramObj
2113: * being passed in here as type Object, the target method can declare
2114: * its parameters as being the true type of the object (or some ancestor
2115: * type, according to the usual type-conversion rules).
2116: *
2117: * @param paramIndex The zero-relative parameter number
2118: * @param paramObj Any arbitrary object to be passed to the target
2119: * method.
2120: * @see CallMethodRule
2121: *
2122: * @since 1.6
2123: */
2124: public void addObjectParam(String pattern, int paramIndex,
2125: Object paramObj) {
2126:
2127: addRule(pattern, new ObjectParamRule(paramIndex, paramObj));
2128:
2129: }
2130:
2131: /**
2132: * Add a "factory create" rule for the specified parameters.
2133: * Exceptions thrown during the object creation process will be propagated.
2134: *
2135: * @param pattern Element matching pattern
2136: * @param className Java class name of the object creation factory class
2137: * @see FactoryCreateRule
2138: */
2139: public void addFactoryCreate(String pattern, String className) {
2140:
2141: addFactoryCreate(pattern, className, false);
2142:
2143: }
2144:
2145: /**
2146: * Add a "factory create" rule for the specified parameters.
2147: * Exceptions thrown during the object creation process will be propagated.
2148: *
2149: * @param pattern Element matching pattern
2150: * @param clazz Java class of the object creation factory class
2151: * @see FactoryCreateRule
2152: */
2153: public void addFactoryCreate(String pattern, Class clazz) {
2154:
2155: addFactoryCreate(pattern, clazz, false);
2156:
2157: }
2158:
2159: /**
2160: * Add a "factory create" rule for the specified parameters.
2161: * Exceptions thrown during the object creation process will be propagated.
2162: *
2163: * @param pattern Element matching pattern
2164: * @param className Java class name of the object creation factory class
2165: * @param attributeName Attribute name which, if present, overrides the
2166: * value specified by <code>className</code>
2167: * @see FactoryCreateRule
2168: */
2169: public void addFactoryCreate(String pattern, String className,
2170: String attributeName) {
2171:
2172: addFactoryCreate(pattern, className, attributeName, false);
2173:
2174: }
2175:
2176: /**
2177: * Add a "factory create" rule for the specified parameters.
2178: * Exceptions thrown during the object creation process will be propagated.
2179: *
2180: * @param pattern Element matching pattern
2181: * @param clazz Java class of the object creation factory class
2182: * @param attributeName Attribute name which, if present, overrides the
2183: * value specified by <code>className</code>
2184: * @see FactoryCreateRule
2185: */
2186: public void addFactoryCreate(String pattern, Class clazz,
2187: String attributeName) {
2188:
2189: addFactoryCreate(pattern, clazz, attributeName, false);
2190:
2191: }
2192:
2193: /**
2194: * Add a "factory create" rule for the specified parameters.
2195: * Exceptions thrown during the object creation process will be propagated.
2196: *
2197: * @param pattern Element matching pattern
2198: * @param creationFactory Previously instantiated ObjectCreationFactory
2199: * to be utilized
2200: * @see FactoryCreateRule
2201: */
2202: public void addFactoryCreate(String pattern,
2203: ObjectCreationFactory creationFactory) {
2204:
2205: addFactoryCreate(pattern, creationFactory, false);
2206:
2207: }
2208:
2209: /**
2210: * Add a "factory create" rule for the specified parameters.
2211: *
2212: * @param pattern Element matching pattern
2213: * @param className Java class name of the object creation factory class
2214: * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
2215: * object creation will be ignored.
2216: * @see FactoryCreateRule
2217: */
2218: public void addFactoryCreate(String pattern, String className,
2219: boolean ignoreCreateExceptions) {
2220:
2221: addRule(pattern, new FactoryCreateRule(className,
2222: ignoreCreateExceptions));
2223:
2224: }
2225:
2226: /**
2227: * Add a "factory create" rule for the specified parameters.
2228: *
2229: * @param pattern Element matching pattern
2230: * @param clazz Java class of the object creation factory class
2231: * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
2232: * object creation will be ignored.
2233: * @see FactoryCreateRule
2234: */
2235: public void addFactoryCreate(String pattern, Class clazz,
2236: boolean ignoreCreateExceptions) {
2237:
2238: addRule(pattern, new FactoryCreateRule(clazz,
2239: ignoreCreateExceptions));
2240:
2241: }
2242:
2243: /**
2244: * Add a "factory create" rule for the specified parameters.
2245: *
2246: * @param pattern Element matching pattern
2247: * @param className Java class name of the object creation factory class
2248: * @param attributeName Attribute name which, if present, overrides the
2249: * value specified by <code>className</code>
2250: * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
2251: * object creation will be ignored.
2252: * @see FactoryCreateRule
2253: */
2254: public void addFactoryCreate(String pattern, String className,
2255: String attributeName, boolean ignoreCreateExceptions) {
2256:
2257: addRule(pattern, new FactoryCreateRule(className,
2258: attributeName, ignoreCreateExceptions));
2259:
2260: }
2261:
2262: /**
2263: * Add a "factory create" rule for the specified parameters.
2264: *
2265: * @param pattern Element matching pattern
2266: * @param clazz Java class of the object creation factory class
2267: * @param attributeName Attribute name which, if present, overrides the
2268: * value specified by <code>className</code>
2269: * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
2270: * object creation will be ignored.
2271: * @see FactoryCreateRule
2272: */
2273: public void addFactoryCreate(String pattern, Class clazz,
2274: String attributeName, boolean ignoreCreateExceptions) {
2275:
2276: addRule(pattern, new FactoryCreateRule(clazz, attributeName,
2277: ignoreCreateExceptions));
2278:
2279: }
2280:
2281: /**
2282: * Add a "factory create" rule for the specified parameters.
2283: *
2284: * @param pattern Element matching pattern
2285: * @param creationFactory Previously instantiated ObjectCreationFactory
2286: * to be utilized
2287: * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
2288: * object creation will be ignored.
2289: * @see FactoryCreateRule
2290: */
2291: public void addFactoryCreate(String pattern,
2292: ObjectCreationFactory creationFactory,
2293: boolean ignoreCreateExceptions) {
2294:
2295: creationFactory.setDigester(this );
2296: addRule(pattern, new FactoryCreateRule(creationFactory,
2297: ignoreCreateExceptions));
2298:
2299: }
2300:
2301: /**
2302: * Add an "object create" rule for the specified parameters.
2303: *
2304: * @param pattern Element matching pattern
2305: * @param className Java class name to be created
2306: * @see ObjectCreateRule
2307: */
2308: public void addObjectCreate(String pattern, String className) {
2309:
2310: addRule(pattern, new ObjectCreateRule(className));
2311:
2312: }
2313:
2314: /**
2315: * Add an "object create" rule for the specified parameters.
2316: *
2317: * @param pattern Element matching pattern
2318: * @param clazz Java class to be created
2319: * @see ObjectCreateRule
2320: */
2321: public void addObjectCreate(String pattern, Class clazz) {
2322:
2323: addRule(pattern, new ObjectCreateRule(clazz));
2324:
2325: }
2326:
2327: /**
2328: * Add an "object create" rule for the specified parameters.
2329: *
2330: * @param pattern Element matching pattern
2331: * @param className Default Java class name to be created
2332: * @param attributeName Attribute name that optionally overrides
2333: * the default Java class name to be created
2334: * @see ObjectCreateRule
2335: */
2336: public void addObjectCreate(String pattern, String className,
2337: String attributeName) {
2338:
2339: addRule(pattern, new ObjectCreateRule(className, attributeName));
2340:
2341: }
2342:
2343: /**
2344: * Add an "object create" rule for the specified parameters.
2345: *
2346: * @param pattern Element matching pattern
2347: * @param attributeName Attribute name that optionally overrides
2348: * @param clazz Default Java class to be created
2349: * the default Java class name to be created
2350: * @see ObjectCreateRule
2351: */
2352: public void addObjectCreate(String pattern, String attributeName,
2353: Class clazz) {
2354:
2355: addRule(pattern, new ObjectCreateRule(attributeName, clazz));
2356:
2357: }
2358:
2359: /**
2360: * Adds an {@link SetNestedPropertiesRule}.
2361: *
2362: * @param pattern register the rule with this pattern
2363: *
2364: * @since 1.6
2365: */
2366: public void addSetNestedProperties(String pattern) {
2367:
2368: addRule(pattern, new SetNestedPropertiesRule());
2369: }
2370:
2371: /**
2372: * Adds an {@link SetNestedPropertiesRule}.
2373: *
2374: * @param pattern register the rule with this pattern
2375: * @param elementName elment name that a property maps to
2376: * @param propertyName property name of the element mapped from
2377: *
2378: * @since 1.6
2379: */
2380: public void addSetNestedProperties(String pattern,
2381: String elementName, String propertyName) {
2382:
2383: addRule(pattern, new SetNestedPropertiesRule(elementName,
2384: propertyName));
2385: }
2386:
2387: /**
2388: * Adds an {@link SetNestedPropertiesRule}.
2389: *
2390: * @param pattern register the rule with this pattern
2391: * @param elementNames elment names that (in order) map to properties
2392: * @param propertyNames property names that (in order) elements are mapped to
2393: *
2394: * @since 1.6
2395: */
2396: public void addSetNestedProperties(String pattern,
2397: String[] elementNames, String[] propertyNames) {
2398:
2399: addRule(pattern, new SetNestedPropertiesRule(elementNames,
2400: propertyNames));
2401: }
2402:
2403: /**
2404: * Add a "set next" rule for the specified parameters.
2405: *
2406: * @param pattern Element matching pattern
2407: * @param methodName Method name to call on the parent element
2408: * @see SetNextRule
2409: */
2410: public void addSetNext(String pattern, String methodName) {
2411:
2412: addRule(pattern, new SetNextRule(methodName));
2413:
2414: }
2415:
2416: /**
2417: * Add a "set next" rule for the specified parameters.
2418: *
2419: * @param pattern Element matching pattern
2420: * @param methodName Method name to call on the parent element
2421: * @param paramType Java class name of the expected parameter type
2422: * (if you wish to use a primitive type, specify the corresonding
2423: * Java wrapper class instead, such as <code>java.lang.Boolean</code>
2424: * for a <code>boolean</code> parameter)
2425: * @see SetNextRule
2426: */
2427: public void addSetNext(String pattern, String methodName,
2428: String paramType) {
2429:
2430: addRule(pattern, new SetNextRule(methodName, paramType));
2431:
2432: }
2433:
2434: /**
2435: * Add {@link SetRootRule} with the specified parameters.
2436: *
2437: * @param pattern Element matching pattern
2438: * @param methodName Method name to call on the root object
2439: * @see SetRootRule
2440: */
2441: public void addSetRoot(String pattern, String methodName) {
2442:
2443: addRule(pattern, new SetRootRule(methodName));
2444:
2445: }
2446:
2447: /**
2448: * Add {@link SetRootRule} with the specified parameters.
2449: *
2450: * @param pattern Element matching pattern
2451: * @param methodName Method name to call on the root object
2452: * @param paramType Java class name of the expected parameter type
2453: * @see SetRootRule
2454: */
2455: public void addSetRoot(String pattern, String methodName,
2456: String paramType) {
2457:
2458: addRule(pattern, new SetRootRule(methodName, paramType));
2459:
2460: }
2461:
2462: /**
2463: * Add a "set properties" rule for the specified parameters.
2464: *
2465: * @param pattern Element matching pattern
2466: * @see SetPropertiesRule
2467: */
2468: public void addSetProperties(String pattern) {
2469:
2470: addRule(pattern, new SetPropertiesRule());
2471:
2472: }
2473:
2474: /**
2475: * Add a "set properties" rule with a single overridden parameter.
2476: * See {@link SetPropertiesRule#SetPropertiesRule(String attributeName, String propertyName)}
2477: *
2478: * @param pattern Element matching pattern
2479: * @param attributeName map this attribute
2480: * @param propertyName to this property
2481: * @see SetPropertiesRule
2482: */
2483: public void addSetProperties(String pattern, String attributeName,
2484: String propertyName) {
2485:
2486: addRule(pattern, new SetPropertiesRule(attributeName,
2487: propertyName));
2488:
2489: }
2490:
2491: /**
2492: * Add a "set properties" rule with overridden parameters.
2493: * See {@link SetPropertiesRule#SetPropertiesRule(String [] attributeNames, String [] propertyNames)}
2494: *
2495: * @param pattern Element matching pattern
2496: * @param attributeNames names of attributes with custom mappings
2497: * @param propertyNames property names these attributes map to
2498: * @see SetPropertiesRule
2499: */
2500: public void addSetProperties(String pattern,
2501: String[] attributeNames, String[] propertyNames) {
2502:
2503: addRule(pattern, new SetPropertiesRule(attributeNames,
2504: propertyNames));
2505:
2506: }
2507:
2508: /**
2509: * Add a "set property" rule for the specified parameters.
2510: *
2511: * @param pattern Element matching pattern
2512: * @param name Attribute name containing the property name to be set
2513: * @param value Attribute name containing the property value to set
2514: * @see SetPropertyRule
2515: */
2516: public void addSetProperty(String pattern, String name, String value) {
2517:
2518: addRule(pattern, new SetPropertyRule(name, value));
2519:
2520: }
2521:
2522: /**
2523: * Add a "set top" rule for the specified parameters.
2524: *
2525: * @param pattern Element matching pattern
2526: * @param methodName Method name to call on the parent element
2527: * @see SetTopRule
2528: */
2529: public void addSetTop(String pattern, String methodName) {
2530:
2531: addRule(pattern, new SetTopRule(methodName));
2532:
2533: }
2534:
2535: /**
2536: * Add a "set top" rule for the specified parameters.
2537: *
2538: * @param pattern Element matching pattern
2539: * @param methodName Method name to call on the parent element
2540: * @param paramType Java class name of the expected parameter type
2541: * (if you wish to use a primitive type, specify the corresonding
2542: * Java wrapper class instead, such as <code>java.lang.Boolean</code>
2543: * for a <code>boolean</code> parameter)
2544: * @see SetTopRule
2545: */
2546: public void addSetTop(String pattern, String methodName,
2547: String paramType) {
2548:
2549: addRule(pattern, new SetTopRule(methodName, paramType));
2550:
2551: }
2552:
2553: // --------------------------------------------------- Object Stack Methods
2554:
2555: /**
2556: * Clear the current contents of the default object stack, the param stack,
2557: * all named stacks, and other internal variables.
2558: * <p>
2559: * Calling this method <i>might</i> allow another document of the same type
2560: * to be correctly parsed. However this method was not intended for this
2561: * purpose. In general, a separate Digester object should be created for
2562: * each document to be parsed.
2563: */
2564: public void clear() {
2565:
2566: match = "";
2567: bodyTexts.clear();
2568: params.clear();
2569: publicId = null;
2570: stack.clear();
2571: stacksByName.clear();
2572: customContentHandler = null;
2573: }
2574:
2575: /**
2576: * Return the top object on the stack without removing it. If there are
2577: * no objects on the stack, return <code>null</code>.
2578: */
2579: public Object peek() {
2580:
2581: try {
2582: return (stack.peek());
2583: } catch (EmptyStackException e) {
2584: log.warn("Empty stack (returning null)");
2585: return (null);
2586: }
2587:
2588: }
2589:
2590: /**
2591: * Return the n'th object down the stack, where 0 is the top element
2592: * and [getCount()-1] is the bottom element. If the specified index
2593: * is out of range, return <code>null</code>.
2594: *
2595: * @param n Index of the desired element, where 0 is the top of the stack,
2596: * 1 is the next element down, and so on.
2597: */
2598: public Object peek(int n) {
2599:
2600: try {
2601: return (stack.peek(n));
2602: } catch (EmptyStackException e) {
2603: log.warn("Empty stack (returning null)");
2604: return (null);
2605: }
2606:
2607: }
2608:
2609: /**
2610: * Pop the top object off of the stack, and return it. If there are
2611: * no objects on the stack, return <code>null</code>.
2612: */
2613: public Object pop() {
2614:
2615: try {
2616: Object popped = stack.pop();
2617: if (stackAction != null) {
2618: popped = stackAction.onPop(this , null, popped);
2619: }
2620: return popped;
2621: } catch (EmptyStackException e) {
2622: log.warn("Empty stack (returning null)");
2623: return (null);
2624: }
2625:
2626: }
2627:
2628: /**
2629: * Push a new object onto the top of the object stack.
2630: *
2631: * @param object The new object
2632: */
2633: public void push(Object object) {
2634:
2635: if (stackAction != null) {
2636: object = stackAction.onPush(this , null, object);
2637: }
2638:
2639: if (stack.size() == 0) {
2640: root = object;
2641: }
2642: stack.push(object);
2643: }
2644:
2645: /**
2646: * Pushes the given object onto the stack with the given name.
2647: * If no stack already exists with the given name then one will be created.
2648: *
2649: * @param stackName the name of the stack onto which the object should be pushed
2650: * @param value the Object to be pushed onto the named stack.
2651: *
2652: * @since 1.6
2653: */
2654: public void push(String stackName, Object value) {
2655: if (stackAction != null) {
2656: value = stackAction.onPush(this , stackName, value);
2657: }
2658:
2659: ArrayStack namedStack = (ArrayStack) stacksByName
2660: .get(stackName);
2661: if (namedStack == null) {
2662: namedStack = new ArrayStack();
2663: stacksByName.put(stackName, namedStack);
2664: }
2665: namedStack.push(value);
2666: }
2667:
2668: /**
2669: * <p>Pops (gets and removes) the top object from the stack with the given name.</p>
2670: *
2671: * <p><strong>Note:</strong> a stack is considered empty
2672: * if no objects have been pushed onto it yet.</p>
2673: *
2674: * @param stackName the name of the stack from which the top value is to be popped.
2675: * @return the top <code>Object</code> on the stack or or null if the stack is either
2676: * empty or has not been created yet
2677: * @throws EmptyStackException if the named stack is empty
2678: *
2679: * @since 1.6
2680: */
2681: public Object pop(String stackName) {
2682: Object result = null;
2683: ArrayStack namedStack = (ArrayStack) stacksByName
2684: .get(stackName);
2685: if (namedStack == null) {
2686: if (log.isDebugEnabled()) {
2687: log.debug("Stack '" + stackName + "' is empty");
2688: }
2689: throw new EmptyStackException();
2690: }
2691:
2692: result = namedStack.pop();
2693:
2694: if (stackAction != null) {
2695: result = stackAction.onPop(this , stackName, result);
2696: }
2697:
2698: return result;
2699: }
2700:
2701: /**
2702: * <p>Gets the top object from the stack with the given name.
2703: * This method does not remove the object from the stack.
2704: * </p>
2705: * <p><strong>Note:</strong> a stack is considered empty
2706: * if no objects have been pushed onto it yet.</p>
2707: *
2708: * @param stackName the name of the stack to be peeked
2709: * @return the top <code>Object</code> on the stack or null if the stack is either
2710: * empty or has not been created yet
2711: * @throws EmptyStackException if the named stack is empty
2712: *
2713: * @since 1.6
2714: */
2715: public Object peek(String stackName) {
2716: return peek(stackName, 0);
2717: }
2718:
2719: /**
2720: * <p>Gets the top object from the stack with the given name.
2721: * This method does not remove the object from the stack.
2722: * </p>
2723: * <p><strong>Note:</strong> a stack is considered empty
2724: * if no objects have been pushed onto it yet.</p>
2725: *
2726: * @param stackName the name of the stack to be peeked
2727: * @param n Index of the desired element, where 0 is the top of the stack,
2728: * 1 is the next element down, and so on.
2729: * @return the specified <code>Object</code> on the stack.
2730: * @throws EmptyStackException if the named stack is empty
2731: *
2732: * @since 1.6
2733: */
2734: public Object peek(String stackName, int n) {
2735: Object result = null;
2736: ArrayStack namedStack = (ArrayStack) stacksByName
2737: .get(stackName);
2738: if (namedStack == null) {
2739: if (log.isDebugEnabled()) {
2740: log.debug("Stack '" + stackName + "' is empty");
2741: }
2742: throw new EmptyStackException();
2743:
2744: } else {
2745:
2746: result = namedStack.peek(n);
2747: }
2748: return result;
2749: }
2750:
2751: /**
2752: * <p>Is the stack with the given name empty?</p>
2753: * <p><strong>Note:</strong> a stack is considered empty
2754: * if no objects have been pushed onto it yet.</p>
2755: * @param stackName the name of the stack whose emptiness
2756: * should be evaluated
2757: * @return true if the given stack if empty
2758: *
2759: * @since 1.6
2760: */
2761: public boolean isEmpty(String stackName) {
2762: boolean result = true;
2763: ArrayStack namedStack = (ArrayStack) stacksByName
2764: .get(stackName);
2765: if (namedStack != null) {
2766: result = namedStack.isEmpty();
2767: }
2768: return result;
2769: }
2770:
2771: /**
2772: * Returns the root element of the tree of objects created as a result
2773: * of applying the rule objects to the input XML.
2774: * <p>
2775: * If the digester stack was "primed" by explicitly pushing a root
2776: * object onto the stack before parsing started, then that root object
2777: * is returned here.
2778: * <p>
2779: * Alternatively, if a Rule which creates an object (eg ObjectCreateRule)
2780: * matched the root element of the xml, then the object created will be
2781: * returned here.
2782: * <p>
2783: * In other cases, the object most recently pushed onto an empty digester
2784: * stack is returned. This would be a most unusual use of digester, however;
2785: * one of the previous configurations is much more likely.
2786: * <p>
2787: * Note that when using one of the Digester.parse methods, the return
2788: * value from the parse method is exactly the same as the return value
2789: * from this method. However when the Digester is being used as a
2790: * SAXContentHandler, no such return value is available; in this case, this
2791: * method allows you to access the root object that has been created
2792: * after parsing has completed.
2793: *
2794: * @return the root object that has been created after parsing
2795: * or null if the digester has not parsed any XML yet.
2796: */
2797: public Object getRoot() {
2798: return root;
2799: }
2800:
2801: /**
2802: * This method allows the "root" variable to be reset to null.
2803: * <p>
2804: * It is not considered safe for a digester instance to be reused
2805: * to parse multiple xml documents. However if you are determined to
2806: * do so, then you should call both clear() and resetRoot() before
2807: * each parse.
2808: *
2809: * @since 1.7
2810: */
2811: public void resetRoot() {
2812: root = null;
2813: }
2814:
2815: // ------------------------------------------------ Parameter Stack Methods
2816:
2817: // ------------------------------------------------------ Protected Methods
2818:
2819: /**
2820: * <p>Clean up allocated resources after parsing is complete. The
2821: * default method closes input streams that have been created by
2822: * Digester itself. If you override this method in a subclass, be
2823: * sure to call <code>super.cleanup()</code> to invoke this logic.</p>
2824: *
2825: * @since 1.8
2826: */
2827: protected void cleanup() {
2828:
2829: // If we created any InputSource objects in this instance,
2830: // they each have an input stream that should be closed
2831: Iterator sources = inputSources.iterator();
2832: while (sources.hasNext()) {
2833: InputSource source = (InputSource) sources.next();
2834: try {
2835: source.getByteStream().close();
2836: } catch (IOException e) {
2837: ; // Fall through so we get them all
2838: }
2839: }
2840:
2841: }
2842:
2843: /**
2844: * <p>
2845: * Provide a hook for lazy configuration of this <code>Digester</code>
2846: * instance. The default implementation does nothing, but subclasses
2847: * can override as needed.
2848: * </p>
2849: *
2850: * <p>
2851: * <strong>Note</strong> This method may be called more than once.
2852: * Once only initialization code should be placed in {@link #initialize}
2853: * or the code should take responsibility by checking and setting the
2854: * {@link #configured} flag.
2855: * </p>
2856: */
2857: protected void configure() {
2858:
2859: // Do not configure more than once
2860: if (configured) {
2861: return;
2862: }
2863:
2864: // Perform lazy configuration as needed
2865: initialize(); // call hook method for subclasses that want to be initialized once only
2866: // Nothing else required by default
2867:
2868: // Set the configuration flag to avoid repeating
2869: configured = true;
2870:
2871: }
2872:
2873: /**
2874: * <p>
2875: * Provides a hook for lazy initialization of this <code>Digester</code>
2876: * instance.
2877: * The default implementation does nothing, but subclasses
2878: * can override as needed.
2879: * Digester (by default) only calls this method once.
2880: * </p>
2881: *
2882: * <p>
2883: * <strong>Note</strong> This method will be called by {@link #configure}
2884: * only when the {@link #configured} flag is false.
2885: * Subclasses that override <code>configure</code> or who set <code>configured</code>
2886: * may find that this method may be called more than once.
2887: * </p>
2888: *
2889: * @since 1.6
2890: */
2891: protected void initialize() {
2892:
2893: // Perform lazy initialization as needed
2894: ; // Nothing required by default
2895:
2896: }
2897:
2898: // -------------------------------------------------------- Package Methods
2899:
2900: /**
2901: * Return the set of DTD URL registrations, keyed by public identifier.
2902: */
2903: Map getRegistrations() {
2904:
2905: return (entityValidator);
2906:
2907: }
2908:
2909: /**
2910: * Return the set of rules that apply to the specified match position.
2911: * The selected rules are those that match exactly, or those rules
2912: * that specify a suffix match and the tail of the rule matches the
2913: * current match position. Exact matches have precedence over
2914: * suffix matches, then (among suffix matches) the longest match
2915: * is preferred.
2916: *
2917: * @param match The current match position
2918: *
2919: * @deprecated Call <code>match()</code> on the <code>Rules</code>
2920: * implementation returned by <code>getRules()</code>
2921: */
2922: List getRules(String match) {
2923:
2924: return (getRules().match(match));
2925:
2926: }
2927:
2928: /**
2929: * <p>Return the top object on the parameters stack without removing it. If there are
2930: * no objects on the stack, return <code>null</code>.</p>
2931: *
2932: * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters.
2933: * See {@link #params}.</p>
2934: */
2935: public Object peekParams() {
2936:
2937: try {
2938: return (params.peek());
2939: } catch (EmptyStackException e) {
2940: log.warn("Empty stack (returning null)");
2941: return (null);
2942: }
2943:
2944: }
2945:
2946: /**
2947: * <p>Return the n'th object down the parameters stack, where 0 is the top element
2948: * and [getCount()-1] is the bottom element. If the specified index
2949: * is out of range, return <code>null</code>.</p>
2950: *
2951: * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters.
2952: * See {@link #params}.</p>
2953: *
2954: * @param n Index of the desired element, where 0 is the top of the stack,
2955: * 1 is the next element down, and so on.
2956: */
2957: public Object peekParams(int n) {
2958:
2959: try {
2960: return (params.peek(n));
2961: } catch (EmptyStackException e) {
2962: log.warn("Empty stack (returning null)");
2963: return (null);
2964: }
2965:
2966: }
2967:
2968: /**
2969: * <p>Pop the top object off of the parameters stack, and return it. If there are
2970: * no objects on the stack, return <code>null</code>.</p>
2971: *
2972: * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters.
2973: * See {@link #params}.</p>
2974: */
2975: public Object popParams() {
2976:
2977: try {
2978: if (log.isTraceEnabled()) {
2979: log.trace("Popping params");
2980: }
2981: return (params.pop());
2982: } catch (EmptyStackException e) {
2983: log.warn("Empty stack (returning null)");
2984: return (null);
2985: }
2986:
2987: }
2988:
2989: /**
2990: * <p>Push a new object onto the top of the parameters stack.</p>
2991: *
2992: * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters.
2993: * See {@link #params}.</p>
2994: *
2995: * @param object The new object
2996: */
2997: public void pushParams(Object object) {
2998: if (log.isTraceEnabled()) {
2999: log.trace("Pushing params");
3000: }
3001: params.push(object);
3002:
3003: }
3004:
3005: /**
3006: * Create a SAX exception which also understands about the location in
3007: * the digester file where the exception occurs
3008: *
3009: * @return the new exception
3010: */
3011: public SAXException createSAXException(String message, Exception e) {
3012: if ((e != null) && (e instanceof InvocationTargetException)) {
3013: Throwable t = ((InvocationTargetException) e)
3014: .getTargetException();
3015: if ((t != null) && (t instanceof Exception)) {
3016: e = (Exception) t;
3017: }
3018: }
3019: if (locator != null) {
3020: String error = "Error at line " + locator.getLineNumber()
3021: + " char " + locator.getColumnNumber() + ": "
3022: + message;
3023: if (e != null) {
3024: return new SAXParseException(error, locator, e);
3025: } else {
3026: return new SAXParseException(error, locator);
3027: }
3028: }
3029: log.error("No Locator!");
3030: if (e != null) {
3031: return new SAXException(message, e);
3032: } else {
3033: return new SAXException(message);
3034: }
3035: }
3036:
3037: /**
3038: * Create a SAX exception which also understands about the location in
3039: * the digester file where the exception occurs
3040: *
3041: * @return the new exception
3042: */
3043: public SAXException createSAXException(Exception e) {
3044: if (e instanceof InvocationTargetException) {
3045: Throwable t = ((InvocationTargetException) e)
3046: .getTargetException();
3047: if ((t != null) && (t instanceof Exception)) {
3048: e = (Exception) t;
3049: }
3050: }
3051: return createSAXException(e.getMessage(), e);
3052: }
3053:
3054: /**
3055: * Create a SAX exception which also understands about the location in
3056: * the digester file where the exception occurs
3057: *
3058: * @return the new exception
3059: */
3060: public SAXException createSAXException(String message) {
3061: return createSAXException(message, null);
3062: }
3063:
3064: }
|