    001    /* $Id: PluginRules.java 729122 2008-12-23 21:14:13Z rahul $
    002     *
    003     * Licensed to the Apache Software Foundation (ASF) under one or more
    004     * contributor license agreements.  See the NOTICE file distributed with
    005     * this work for additional information regarding copyright ownership.
    006     * The ASF licenses this file to You under the Apache License, Version 2.0
    007     * (the "License"); you may not use this file except in compliance with
    008     * the License.  You may obtain a copy of the License at
    009     *
    010     *      http://www.apache.org/licenses/LICENSE-2.0
    011     *
    012     * Unless required by applicable law or agreed to in writing, software
    013     * distributed under the License is distributed on an "AS IS" BASIS,
    014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    015     * See the License for the specific language governing permissions and
    016     * limitations under the License.
    017     */
    018    package org.apache.commons.digester.plugins;
    019
    020    import java.util.List;
    021
    022    import org.apache.commons.digester.Digester;
    023    import org.apache.commons.digester.Rule;
    024    import org.apache.commons.digester.Rules;
    025    import org.apache.commons.digester.RulesBase;
    026    import org.apache.commons.logging.Log;
    027
    028    /**
    029     * A custom digester Rules manager which must be used as the Rules object
    030     * when using the plugins module functionality.
    031     * <p>
    032     * During parsing, a linked list of PluginCreateRule instances develop, and
    033     * this list also acts like a stack. The original instance that was set before
    034     * the Digester started parsing is always at the tail of the list, and the
    035     * Digester always holds a reference to the instance at the head of the list
    036     * in the rules member. Initially, this list/stack holds just one instance,
    037     * ie head and tail are the same object.
    038     * <p>
    039     * When the start of an xml element causes a PluginCreateRule to fire, a new
    040     * PluginRules instance is created and inserted at the head of the list (ie
    041     * pushed onto the stack of Rules objects). Digester.getRules() therefore
    042     * returns this new Rules object, and any custom rules associated with that
    043     * plugin are added to that instance.
    044     * <p>
    045     * When the end of the xml element is encountered (and therefore the
    046     * PluginCreateRule end method fires), the stack of Rules objects is popped,
    047     * so that Digester.getRules returns the previous Rules object.
    048     *
    049     * @since 1.6
    050     */
    051
    052    public class PluginRules implements Rules {
    053
    054        /**
    055         * The Digester instance with which this Rules instance is associated.
    056         */
    057        protected Digester digester = null;
    058
    059        /**
    060         * The (optional) object which generates new rules instances.
    061         */
    062        private RulesFactory rulesFactory;
    063
    064        /**
    065         * The rules implementation that we are "enhancing" with plugins
    066         * functionality, as per the Decorator pattern.
    067         */
    068        private Rules decoratedRules;
    069
    070        /** Object which contains information about all known plugins. */
    071        private PluginManager pluginManager;
    072
    073        /**
    074         * The path below which this rules object has responsibility.
    075         * For paths shorter than or equal the mountpoint, the parent's
    076         * match is called.
    077         */
    078        private String mountPoint = null;
    079
    080        /**
    081         * The Rules object that holds rules applying "above" the mountpoint,
    082         * ie the next Rules object down in the stack.
    083         */
    084        private PluginRules parent = null;
    085
    086        /**
    087         * A reference to the object that holds all data which should only
    088         * exist once per digester instance.
    089         */
    090        private PluginContext pluginContext = null;
    091
    092        // ------------------------------------------------------------- Constructor
    093
    094        /**
    095         * Constructor for top-level Rules objects. Exactly one of these must
    096         * be created and installed into the Digester instance as the Rules
    097         * object before parsing starts.
    098         */
    099        public PluginRules() {
    100            this(new RulesBase());
    101        }
    102
    103        /**
    104         * Constructor for top-level Rules object which handles rule-matching
    105         * using the specified implementation.
    106         */
    107        public PluginRules(Rules decoratedRules) {
    108            this.decoratedRules = decoratedRules;
    109
    110            pluginContext = new PluginContext();
    111            pluginManager = new PluginManager(pluginContext);
    112        }
    113
    114        /**
    115         * Constructs a Rules instance which has a parent Rules object
    116         * (which is different from having a delegate rules object).
    117         * <p>
    118         * One of these is created each time a PluginCreateRule's begin method
    119         * fires, in order to manage the custom rules associated with whatever
    120         * concrete plugin class the user has specified.
    121         *
    122         * @param digester is the object this rules will be associated with.
    123         * @param mountPoint is the digester match path for the element
    124         * matching a PluginCreateRule which caused this "nested parsing scope"
    125         * to begin. This is expected to be equal to digester.getMatch().
    126         * @param parent must be non-null.
    127         * @param pluginClass is the plugin class whose custom rules will be
    128         * loaded into this new PluginRules object.
    129         */
    130         PluginRules(
    131         Digester digester,
    132         String mountPoint,
    133         PluginRules parent,
    134         Class<?> pluginClass)
    135         throws PluginException {
    136            // no need to set digester or decoratedRules.digester,
    137            // because when Digester.setRules is called, the setDigester
    138            // method on this object will be called.
    139
    140            this.digester = digester;
    141            this.mountPoint = mountPoint;
    142            this.parent = parent;
    143            this.rulesFactory = parent.rulesFactory;
    144
    145            if (rulesFactory == null) {
    146                decoratedRules = new RulesBase();
    147            } else {
    148                decoratedRules = rulesFactory.newRules(digester, pluginClass);
    149            }
    150
    151            pluginContext = parent.pluginContext;
    152            pluginManager = new PluginManager(parent.pluginManager);
    153        }
    154
    155        // ------------------------------------------------------------- Properties
    156
    157        /**
    158         * Return the parent Rules object.
    159         */
    160        public Rules getParent() {
    161            return parent;
    162        }
    163
    164        /**
    165         * Return the Digester instance with which this instance is associated.
    166         */
    167        public Digester getDigester() {
    168            return digester;
    169        }
    170
    171        /**
    172         * Set the Digester instance with which this Rules instance is associated.
    173         *
    174         * @param digester The newly associated Digester instance
    175         */
    176        public void setDigester(Digester digester) {
    177            this.digester = digester;
    178            decoratedRules.setDigester(digester);
    179        }
    180
    181        /**
    182         * Return the namespace URI that will be applied to all subsequently
    183         * added <code>Rule</code> objects.
    184         */
    185        public String getNamespaceURI() {
    186            return decoratedRules.getNamespaceURI();
    187        }
    188
    189        /**
    190         * Set the namespace URI that will be applied to all subsequently
    191         * added <code>Rule</code> objects.
    192         *
    193         * @param namespaceURI Namespace URI that must match on all
    194         *  subsequently added rules, or <code>null</code> for matching
    195         *  regardless of the current namespace URI
    196         */
    197        public void setNamespaceURI(String namespaceURI) {
    198            decoratedRules.setNamespaceURI(namespaceURI);
    199        }
    200
    201        /**
    202         * Return the object which "knows" about all declared plugins.
    203         *
    204         * @return The pluginManager value
    205         */
    206        public PluginManager getPluginManager() {
    207            return pluginManager;
    208        }
    209
    210        /**
    211         * See {@link PluginContext#getRuleFinders}.
    212         */
    213        public List<RuleFinder> getRuleFinders() {
    214            return pluginContext.getRuleFinders();
    215        }
    216
    217        /**
    218         * See {@link PluginContext#setRuleFinders}.
    219         */
    220        public void setRuleFinders(List<RuleFinder> ruleFinders) {
    221            pluginContext.setRuleFinders(ruleFinders);
    222        }
    223
    224        /**
    225         * Return the rules factory object (or null if one has not been specified).
    226         */
    227        public RulesFactory getRulesFactory() {
    228            return rulesFactory;
    229        }
    230
    231        /**
    232         * Set the object which is used to generate the new Rules instances created
    233         * to hold and process the rules associated with each plugged-in class.
    234         */
    235        public void setRulesFactory(RulesFactory factory) {
    236            rulesFactory = factory;
    237        }
    238
    239        // --------------------------------------------------------- Public Methods
    240
    241        /**
    242         * This package-scope method is used by the PluginCreateRule class to
    243         * get direct access to the rules that were dynamically added by the
    244         * plugin. No other class should need access to this object.
    245         */
    246        Rules getDecoratedRules() {
    247            return decoratedRules;
    248        }
    249
    250        /**
    251         * Return the list of rules registered with this object, in the order
    252         * they were registered with this object.
    253         * <p>
    254         * Note that Rule objects stored in parent Rules objects are not
    255         * returned by this method.
    256         *
    257         * @return list of all Rule objects known to this Rules instance.
    258         */
    259        public List<Rule> rules() {
    260            return decoratedRules.rules();
    261        }
    262
    263        /**
    264         * Register a new Rule instance matching the specified pattern.
    265         *
    266         * @param pattern Nesting pattern to be matched for this Rule.
    267         * This parameter treats equally patterns that begin with and without
    268         * a leading slash ('/').
    269         * @param rule Rule instance to be registered
    270         */
    271        public void add(String pattern, Rule rule) {
    272            Log log = LogUtils.getLogger(digester);
    273            boolean debug = log.isDebugEnabled();
    274
    275            if (debug) {
    276                log.debug("add entry" + ": mapping pattern [" + pattern + "]" +
    277                      " to rule of type [" + rule.getClass().getName() + "]");
    278            }
    279
    280            // allow patterns with a leading slash character
    281            if (pattern.startsWith("/"))
    282            {
    283                pattern = pattern.substring(1);
    284            }
    285
    286            if (mountPoint != null) {
    287                if (!pattern.equals(mountPoint)
    288                  && !pattern.startsWith(mountPoint + "/")) {
    289                    // This can only occur if a plugin attempts to add a
    290                    // rule with a pattern that doesn't start with the
    291                    // prefix passed to the addRules method. Plugins mustn't
    292                    // add rules outside the scope of the tag they were specified
    293                    // on, so refuse this.
    294
    295                    // alas, can't throw exception
    296                    log.warn(
    297                        "An attempt was made to add a rule with a pattern that"
    298                        + "is not at or below the mountpoint of the current"
    299                        + " PluginRules object."
    300                        + " Rule pattern: " + pattern
    301                        + ", mountpoint: " + mountPoint
    302                        + ", rule type: " + rule.getClass().getName());
    303                    return;
    304                }
    305            }
    306
    307            decoratedRules.add(pattern, rule);
    308
    309            if (rule instanceof InitializableRule) {
    310                try {
    311                    ((InitializableRule)rule).postRegisterInit(pattern);
    312                } catch (PluginConfigurationException e) {
    313                    // Currently, Digester doesn't handle exceptions well
    314                    // from the add method. The workaround is for the
    315                    // initialisable rule to remember that its initialisation
    316                    // failed, and to throw the exception when begin is
    317                    // called for the first time.
    318                    if (debug) {
    319                        log.debug("Rule initialisation failed", e);
    320                    }
    321                    // throw e; -- alas, can't do this
    322                    return;
    323                }
    324            }
    325
    326            if (debug) {
    327                log.debug("add exit" + ": mapped pattern [" + pattern + "]" +
    328                      " to rule of type [" + rule.getClass().getName() + "]");
    329            }
    330        }
    331
    332        /**
    333         * Clear all rules.
    334         */
    335        public void clear() {
    336            decoratedRules.clear();
    337        }
    338
    339        /**
    340         * Return a List of all registered Rule instances that match the specified
    341         * nesting pattern, or a zero-length List if there are no matches.  If more
    342         * than one Rule instance matches, they <strong>must</strong> be returned
    343         * in the order originally registered through the <code>add()</code>
    344         * method.
    345         *
    346         * @param path the path to the xml nodes to be matched.
    347         *
    348         * @deprecated Call match(namespaceURI,pattern) instead.
    349         */
    350        public List<Rule> match(String path) {
    351            return (match(null, path));
    352        }
    353
    354        /**
    355         * Return a List of all registered Rule instances that match the specified
    356         * nodepath, or a zero-length List if there are no matches.  If more
    357         * than one Rule instance matches, they <strong>must</strong> be returned
    358         * in the order originally registered through the <code>add()</code>
    359         * method.
    360         * <p>
    361         * @param namespaceURI Namespace URI for which to select matching rules,
    362         *  or <code>null</code> to match regardless of namespace URI
    363         * @param path the path to the xml nodes to be matched.
    364         */
    365        public List<Rule> match(String namespaceURI, String path) {
    366            Log log = LogUtils.getLogger(digester);
    367            boolean debug = log.isDebugEnabled();
    368
    369            if (debug) {
    370                log.debug(
    371                    "Matching path [" + path +
    372                    "] on rules object " + this.toString());
    373            }
    374
    375            List<Rule> matches;
    376            if ((mountPoint != null) &&
    377                (path.length() <= mountPoint.length())) {
    378                if (debug) {
    379                    log.debug(
    380                        "Path [" + path + "] delegated to parent.");
    381                }
    382
    383                matches = parent.match(namespaceURI, path);
    384
    385                // Note that in the case where path equals mountPoint,
    386                // we deliberately return only the rules from the parent,
    387                // even though this object may hold some rules matching
    388                // this same path. See PluginCreateRule's begin, body and end
    389                // methods for the reason.
    390            } else {
    391                    log.debug("delegating to decorated rules.");
    392                matches = decoratedRules.match(namespaceURI, path);
    393            }
    394
    395            return matches;
    396        }
    397
    398        /** See {@link PluginContext#setPluginClassAttribute}. */
    399        public void setPluginClassAttribute(String namespaceUri,
    400                                            String attrName) {
    401            pluginContext.setPluginClassAttribute(namespaceUri, attrName);
    402        }
    403
    404        /** See {@link PluginContext#setPluginIdAttribute}. */
    405        public void setPluginIdAttribute(String namespaceUri,
    406                                         String attrName) {
    407            pluginContext.setPluginIdAttribute(namespaceUri, attrName);
    408        }
    409
    410        /** See {@link PluginContext#getPluginClassAttrNs}. */
    411        public String getPluginClassAttrNs() {
    412            return pluginContext.getPluginClassAttrNs();
    413        }
    414
    415        /** See {@link PluginContext#getPluginClassAttr}. */
    416        public String getPluginClassAttr() {
    417            return pluginContext.getPluginClassAttr();
    418        }
    419
    420        /** See {@link PluginContext#getPluginIdAttrNs}. */
    421        public String getPluginIdAttrNs() {
    422            return pluginContext.getPluginIdAttrNs();
    423        }
    424
    425        /** See {@link PluginContext#getPluginIdAttr}. */
    426        public String getPluginIdAttr() {
    427            return pluginContext.getPluginIdAttr();
    428        }
    429    }