    001    /* $Id: PluginCreateRule.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
    019    package org.apache.commons.digester.plugins;
    020
    021    import java.util.List;
    022
    023    import org.apache.commons.digester.Rule;
    024    import org.apache.commons.logging.Log;
    025
    026    /**
    027     * Allows the original rules for parsing the configuration file to define
    028     * points at which plugins are allowed, by configuring a PluginCreateRule
    029     * with the appropriate pattern.
    030     *
    031     * @since 1.6
    032     */
    033    public class PluginCreateRule extends Rule implements InitializableRule {
    034
    035        // see setPluginClassAttribute
    036        private String pluginClassAttrNs = null;
    037        private String pluginClassAttr = null;
    038
    039        // see setPluginIdAttribute
    040        private String pluginIdAttrNs = null;
    041        private String pluginIdAttr = null;
    042
    043        /**
    044         * In order to invoke the addRules method on the plugin class correctly,
    045         * we need to know the pattern which this rule is matched by.
    046         */
    047        private String pattern;
    048
    049        /** A base class that any plugin must derive from. */
    050        private Class<?> baseClass = null;
    051
    052        /**
    053         * Info about optional default plugin to be used if no plugin-id is
    054         * specified in the input data. This can simplify the syntax where one
    055         * particular plugin is usually used.
    056         */
    057        private Declaration defaultPlugin;
    058
    059        /**
    060         * Currently, none of the Rules methods allow exceptions to be thrown.
    061         * Therefore if this class cannot initialise itself properly, it cannot
    062         * cause the digester to stop. Instead, we cache the exception and throw
    063         * it the first time the begin() method is called.
    064         */
    065        private PluginConfigurationException initException;
    066
    067        //-------------------- constructors -------------------------------------
    068
    069        /**
    070         * Create a plugin rule where the user <i>must</i> specify a plugin-class
    071         * or plugin-id.
    072         *
    073         * @param baseClass is the class which any specified plugin <i>must</i> be
    074         * descended from.
    075         */
    076        public PluginCreateRule(Class<?> baseClass) {
    077            this.baseClass = baseClass;
    078        }
    079
    080        /**
    081         * Create a plugin rule where the user <i>may</i> specify a plugin.
    082         * If the user doesn't specify a plugin, then the default class specified
    083         * in this constructor is used.
    084         *
    085         * @param baseClass is the class which any specified plugin <i>must</i> be
    086         * descended from.
    087         * @param dfltPluginClass is the class which will be used if the user
    088         * doesn't specify any plugin-class or plugin-id. This class will have
    089         * custom rules installed for it just like a declared plugin.
    090         */
    091        public PluginCreateRule(Class<?> baseClass, Class<?> dfltPluginClass) {
    092            this.baseClass = baseClass;
    093            if (dfltPluginClass != null) {
    094                defaultPlugin = new Declaration(dfltPluginClass);
    095            }
    096        }
    097
    098        /**
    099         * Create a plugin rule where the user <i>may</i> specify a plugin.
    100         * If the user doesn't specify a plugin, then the default class specified
    101         * in this constructor is used.
    102         *
    103         * @param baseClass is the class which any specified plugin <i>must</i> be
    104         * descended from.
    105         * @param dfltPluginClass is the class which will be used if the user
    106         * doesn't specify any plugin-class or plugin-id. This class will have
    107         * custom rules installed for it just like a declared plugin.
    108         * @param dfltPluginRuleLoader is a RuleLoader instance which knows how
    109         * to load the custom rules associated with this default plugin.
    110         */
    111        public PluginCreateRule(Class<?> baseClass, Class<?> dfltPluginClass,
    112                        RuleLoader dfltPluginRuleLoader) {
    113
    114            this.baseClass = baseClass;
    115            if (dfltPluginClass != null) {
    116                defaultPlugin =
    117                    new Declaration(dfltPluginClass, dfltPluginRuleLoader);
    118            }
    119        }
    120
    121        //------------------- properties ---------------------------------------
    122
    123        /**
    124         * Sets the xml attribute which the input xml uses to indicate to a
    125         * PluginCreateRule which class should be instantiated.
    126         * <p>
    127         * See {@link PluginRules#setPluginClassAttribute} for more info.
    128         */
    129        public void setPluginClassAttribute(String namespaceUri, String attrName) {
    130            pluginClassAttrNs = namespaceUri;
    131            pluginClassAttr = attrName;
    132        }
    133
    134        /**
    135         * Sets the xml attribute which the input xml uses to indicate to a
    136         * PluginCreateRule which plugin declaration is being referenced.
    137         * <p>
    138         * See {@link PluginRules#setPluginIdAttribute} for more info.
    139         */
    140        public void setPluginIdAttribute(String namespaceUri, String attrName) {
    141            pluginIdAttrNs = namespaceUri;
    142            pluginIdAttr = attrName;
    143        }
    144
    145        //------------------- methods --------------------------------------------
    146
    147        /**
    148         * Invoked after this rule has been added to the set of digester rules,
    149         * associated with the specified pattern. Check all configuration data is
    150         * valid and remember the pattern for later.
    151         *
    152         * @param matchPattern is the digester match pattern that is associated
    153         * with this rule instance, eg "root/widget".
    154         * @exception PluginConfigurationException
    155         */
    156        public void postRegisterInit(String matchPattern)
    157                                     throws PluginConfigurationException {
    158            Log log = LogUtils.getLogger(digester);
    159            boolean debug = log.isDebugEnabled();
    160            if (debug) {
    161                log.debug("PluginCreateRule.postRegisterInit" +
    162                          ": rule registered for pattern [" + matchPattern + "]");
    163            }
    164
    165            if (digester == null) {
    166                // We require setDigester to be called before this method.
    167                // Note that this means that PluginCreateRule cannot be added
    168                // to a Rules object which has not yet been added to a
    169                // Digester object.
    170                initException = new PluginConfigurationException(
    171                     "Invalid invocation of postRegisterInit" +
    172                     ": digester not set.");
    173                throw initException;
    174            }
    175
    176            if (pattern != null) {
    177                // We have been called twice, ie a single instance has been
    178                // associated with multiple patterns.
    179                //
    180                // Generally, Digester Rule instances can be associated with
    181                // multiple patterns. However for plugins, this creates some
    182                // complications. Some day this may be supported; however for
    183                // now we just reject this situation.
    184                initException = new PluginConfigurationException(
    185                   "A single PluginCreateRule instance has been mapped to" +
    186                     " multiple patterns; this is not supported.");
    187                throw initException;
    188            }
    189
    190            if (matchPattern.indexOf('*') != -1) {
    191                // having wildcards in patterns is extremely difficult to
    192                // deal with. For now, we refuse to allow this.
    193                //
    194                // TODO: check for any chars not valid in xml element name
    195                // rather than just *.
    196                //
    197                // Reasons include:
    198                // (a) handling recursive plugins, and
    199                // (b) determining whether one pattern is "below" another,
    200                //     as done by PluginRules. Without wildcards, "below"
    201                //     just means startsWith, which is easy to check.
    202                initException = new PluginConfigurationException(
    203                     "A PluginCreateRule instance has been mapped to" +
    204                     " pattern [" + matchPattern + "]." +
    205                     " This pattern includes a wildcard character." +
    206                     " This is not supported by the plugin architecture.");
    207                throw initException;
    208            }
    209
    210            if (baseClass == null) {
    211                baseClass = Object.class;
    212            }
    213
    214            PluginRules rules = (PluginRules) digester.getRules();
    215            PluginManager pm = rules.getPluginManager();
    216
    217            // check default class is valid
    218            if (defaultPlugin != null) {
    219                if (!baseClass.isAssignableFrom(defaultPlugin.getPluginClass())) {
    220                    initException = new PluginConfigurationException(
    221                         "Default class [" +
    222                         defaultPlugin.getPluginClass().getName() +
    223                         "] does not inherit from [" +
    224                         baseClass.getName() + "].");
    225                    throw initException;
    226                }
    227
    228                try {
    229                    defaultPlugin.init(digester, pm);
    230
    231                } catch(PluginException pwe) {
    232
    233                    throw new PluginConfigurationException(
    234                        pwe.getMessage(), pwe.getCause());
    235                }
    236            }
    237
    238            // remember the pattern for later
    239            pattern = matchPattern;
    240
    241            if (pluginClassAttr ==  null) {
    242                // the user hasn't set explicit xml attr names on this rule,
    243                // so fetch the default values
    244                pluginClassAttrNs = rules.getPluginClassAttrNs();
    245                pluginClassAttr = rules.getPluginClassAttr();
    246
    247                if (debug) {
    248                    log.debug(
    249                        "init: pluginClassAttr set to per-digester values ["
    250                        + "ns=" + pluginClassAttrNs
    251                        + ", name=" + pluginClassAttr + "]");
    252                }
    253            } else {
    254                if (debug) {
    255                    log.debug(
    256                        "init: pluginClassAttr set to rule-specific values ["
    257                        + "ns=" + pluginClassAttrNs
    258                        + ", name=" + pluginClassAttr + "]");
    259                }
    260            }
    261
    262            if (pluginIdAttr ==  null) {
    263                // the user hasn't set explicit xml attr names on this rule,
    264                // so fetch the default values
    265                pluginIdAttrNs = rules.getPluginIdAttrNs();
    266                pluginIdAttr = rules.getPluginIdAttr();
    267
    268                if (debug) {
    269                    log.debug(
    270                        "init: pluginIdAttr set to per-digester values ["
    271                        + "ns=" + pluginIdAttrNs
    272                        + ", name=" + pluginIdAttr + "]");
    273                }
    274            } else {
    275                if (debug) {
    276                    log.debug(
    277                        "init: pluginIdAttr set to rule-specific values ["
    278                        + "ns=" + pluginIdAttrNs
    279                        + ", name=" + pluginIdAttr + "]");
    280                }
    281            }
    282        }
    283
    284        /**
    285         * Invoked when the Digester matches this rule against an xml element.
    286         * <p>
    287         * A new instance of the target class is created, and pushed onto the
    288         * stack. A new "private" PluginRules object is then created and set as
    289         * the digester's default Rules object. Any custom rules associated with
    290         * the plugin class are then loaded into that new Rules object.
    291         * Finally, any custom rules that are associated with the current pattern
    292         * (such as SetPropertiesRules) have their begin methods executed.
    293         *
    294         * @param namespace
    295         * @param name
    296         * @param attributes
    297         *
    298         * @throws ClassNotFoundException
    299         * @throws PluginInvalidInputException
    300         * @throws PluginConfigurationException
    301         */
    302        public void begin(String namespace, String name,
    303                          org.xml.sax.Attributes attributes)
    304                          throws java.lang.Exception {
    305            Log log = digester.getLogger();
    306            boolean debug = log.isDebugEnabled();
    307            if (debug) {
    308                log.debug("PluginCreateRule.begin" + ": pattern=[" + pattern + "]" +
    309                      " match=[" + digester.getMatch() + "]");
    310            }
    311
    312            if (initException != null) {
    313                // we had a problem during initialisation that we could
    314                // not report then; report it now.
    315                throw initException;
    316            }
    317
    318            // load any custom rules associated with the plugin
    319            PluginRules oldRules = (PluginRules) digester.getRules();
    320            PluginManager pluginManager = oldRules.getPluginManager();
    321            Declaration currDeclaration = null;
    322
    323            String pluginClassName;
    324            if (pluginClassAttrNs == null) {
    325                // Yep, this is ugly.
    326                //
    327                // In a namespace-aware parser, the one-param version will
    328                // return attributes with no namespace.
    329                //
    330                // In a non-namespace-aware parser, the two-param version will
    331                // never return any attributes, ever.
    332                pluginClassName = attributes.getValue(pluginClassAttr);
    333            } else {
    334                pluginClassName =
    335                    attributes.getValue(pluginClassAttrNs, pluginClassAttr);
    336            }
    337
    338            String pluginId;
    339            if (pluginIdAttrNs == null) {
    340                pluginId = attributes.getValue(pluginIdAttr);
    341            } else {
    342                pluginId =
    343                    attributes.getValue(pluginIdAttrNs, pluginIdAttr);
    344            }
    345
    346            if (pluginClassName != null) {
    347                // The user is using a plugin "inline", ie without a previous
    348                // explicit declaration. If they have used the same plugin class
    349                // before, we have already gone to the effort of creating a
    350                // Declaration object, so retrieve it. If there is no existing
    351                // declaration object for this class, then create one.
    352
    353                currDeclaration = pluginManager.getDeclarationByClass(
    354                    pluginClassName);
    355
    356                if (currDeclaration == null) {
    357                    currDeclaration = new Declaration(pluginClassName);
    358                    try {
    359                        currDeclaration.init(digester, pluginManager);
    360                    } catch(PluginException pwe) {
    361                        throw new PluginInvalidInputException(
    362                            pwe.getMessage(), pwe.getCause());
    363                    }
    364                    pluginManager.addDeclaration(currDeclaration);
    365                }
    366            } else if (pluginId != null) {
    367                currDeclaration = pluginManager.getDeclarationById(pluginId);
    368
    369                if (currDeclaration == null) {
    370                    throw new PluginInvalidInputException(
    371                        "Plugin id [" + pluginId + "] is not defined.");
    372                }
    373            } else if (defaultPlugin != null) {
    374                currDeclaration = defaultPlugin;
    375            } else {
    376                throw new PluginInvalidInputException(
    377                    "No plugin class specified for element " +
    378                    pattern);
    379            }
    380
    381            // get the class of the user plugged-in type
    382            Class<?> pluginClass = currDeclaration.getPluginClass();
    383
    384            String path = digester.getMatch();
    385
    386            // create a new Rules object and effectively push it onto a stack of
    387            // rules objects. The stack is actually a linked list; using the
    388            // PluginRules constructor below causes the new instance to link
    389            // to the previous head-of-stack, then the Digester.setRules() makes
    390            // the new instance the new head-of-stack.
    391            PluginRules newRules = new PluginRules(digester, path, oldRules, pluginClass);
    392            digester.setRules(newRules);
    393
    394            if (debug) {
    395                log.debug("PluginCreateRule.begin: installing new plugin: " +
    396                    "oldrules=" + oldRules.toString() +
    397                    ", newrules=" + newRules.toString());
    398            }
    399
    400            // load up the custom rules
    401            currDeclaration.configure(digester, pattern);
    402
    403            // create an instance of the plugin class
    404            Object instance = pluginClass.newInstance();
    405            getDigester().push(instance);
    406            if (debug) {
    407                log.debug(
    408                    "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" +
    409                    " match=[" + digester.getMatch() + "]" +
    410                    " pushed instance of plugin [" + pluginClass.getName() + "]");
    411            }
    412
    413            // and now we have to fire any custom rules which would have
    414            // been matched by the same path that matched this rule, had
    415            // they been loaded at that time.
    416            List<Rule> rules = newRules.getDecoratedRules().match(namespace, path);
    417            fireBeginMethods(rules, namespace, name, attributes);
    418        }
    419
    420        /**
    421         * Process the body text of this element.
    422         *
    423         * @param text The body text of this element
    424         */
    425        public void body(String namespace, String name, String text)
    426            throws Exception {
    427
    428            // While this class itself has no work to do in the body method,
    429            // we do need to fire the body methods of all dynamically-added
    430            // rules matching the same path as this rule. During begin, we had
    431            // to manually execute the dynamic rules' begin methods because they
    432            // didn't exist in the digester's Rules object when the match begin.
    433            // So in order to ensure consistent ordering of rule execution, the
    434            // PluginRules class deliberately avoids returning any such rules
    435            // in later calls to the match method, instead relying on this
    436            // object to execute them at the appropriate time.
    437            //
    438            // Note that this applies only to rules matching exactly the path
    439            // which is also matched by this PluginCreateRule.
    440
    441            String path = digester.getMatch();
    442            PluginRules newRules = (PluginRules) digester.getRules();
    443            List<Rule> rules = newRules.getDecoratedRules().match(namespace, path);
    444            fireBodyMethods(rules, namespace, name, text);
    445        }
    446
    447        /**
    448         * Invoked by the digester when the closing tag matching this Rule's
    449         * pattern is encountered.
    450         * </p>
    451         *
    452         * @param namespace Description of the Parameter
    453         * @param name Description of the Parameter
    454         * @exception Exception Description of the Exception
    455         *
    456         * @see #begin
    457         */
    458        public void end(String namespace, String name)
    459                        throws Exception {
    460
    461
    462            // see body method for more info
    463            String path = digester.getMatch();
    464            PluginRules newRules = (PluginRules) digester.getRules();
    465            List<Rule> rules = newRules.getDecoratedRules().match(namespace, path);
    466            fireEndMethods(rules, namespace, name);
    467
    468            // pop the stack of PluginRules instances, which
    469            // discards all custom rules associated with this plugin
    470            digester.setRules(newRules.getParent());
    471
    472            // and get rid of the instance of the plugin class from the
    473            // digester object stack.
    474            digester.pop();
    475        }
    476
    477        /**
    478         * Return the pattern that this Rule is associated with.
    479         * <p>
    480         * In general, Rule instances <i>can</i> be associated with multiple
    481         * patterns. A PluginCreateRule, however, will only function correctly
    482         * when associated with a single pattern. It is possible to fix this, but
    483         * I can't be bothered just now because this feature is unlikely to be
    484         * used.
    485         * </p>
    486         *
    487         * @return The pattern value
    488         */
    489        public String getPattern() {
    490            return pattern;
    491        }
    492
    493        /**
    494         * Duplicate the processing that the Digester does when firing the
    495         * begin methods of rules. It would be really nice if the Digester
    496         * class provided a way for this functionality to just be invoked
    497         * directly.
    498         */
    499        public void fireBeginMethods(List<Rule> rules,
    500                          String namespace, String name,
    501                          org.xml.sax.Attributes list)
    502                          throws java.lang.Exception {
    503
    504            if ((rules != null) && (rules.size() > 0)) {
    505                Log log = digester.getLogger();
    506                boolean debug = log.isDebugEnabled();
    507                for (int i = 0; i < rules.size(); i++) {
    508                    try {
    509                        Rule rule = (Rule) rules.get(i);
    510                        if (debug) {
    511                            log.debug("  Fire begin() for " + rule);
    512                        }
    513                        rule.begin(namespace, name, list);
    514                    } catch (Exception e) {
    515                        throw digester.createSAXException(e);
    516                    } catch (Error e) {
    517                        throw e;
    518                    }
    519                }
    520            }
    521        }
    522
    523        /**
    524         * Duplicate the processing that the Digester does when firing the
    525         * body methods of rules. It would be really nice if the Digester
    526         * class provided a way for this functionality to just be invoked
    527         * directly.
    528         */
    529        private void fireBodyMethods(List<Rule> rules,
    530                        String namespaceURI, String name,
    531                        String text) throws Exception {
    532
    533            if ((rules != null) && (rules.size() > 0)) {
    534                Log log = digester.getLogger();
    535                boolean debug = log.isDebugEnabled();
    536                for (int i = 0; i < rules.size(); i++) {
    537                    try {
    538                        Rule rule = (Rule) rules.get(i);
    539                        if (debug) {
    540                            log.debug("  Fire body() for " + rule);
    541                        }
    542                        rule.body(namespaceURI, name, text);
    543                    } catch (Exception e) {
    544                        throw digester.createSAXException(e);
    545                    } catch (Error e) {
    546                        throw e;
    547                    }
    548                }
    549            }
    550        }
    551
    552        /**
    553         * Duplicate the processing that the Digester does when firing the
    554         * end methods of rules. It would be really nice if the Digester
    555         * class provided a way for this functionality to just be invoked
    556         * directly.
    557         */
    558        public void fireEndMethods(List<Rule> rules,
    559                        String namespaceURI, String name)
    560                        throws Exception {
    561
    562            // Fire "end" events for all relevant rules in reverse order
    563            if (rules != null) {
    564                Log log = digester.getLogger();
    565                boolean debug = log.isDebugEnabled();
    566                for (int i = 0; i < rules.size(); i++) {
    567                    int j = (rules.size() - i) - 1;
    568                    try {
    569                        Rule rule = (Rule) rules.get(j);
    570                        if (debug) {
    571                            log.debug("  Fire end() for " + rule);
    572                        }
    573                        rule.end(namespaceURI, name);
    574                    } catch (Exception e) {
    575                        throw digester.createSAXException(e);
    576                    } catch (Error e) {
    577                        throw e;
    578                    }
    579                }
    580            }
    581        }
    582    }