alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Commons Digester example source code file (PluginCreateRule.java)

This example Commons Digester source code file (PluginCreateRule.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Commons Digester tags/keywords

class, declaration, exception, exception, list, log, pluginconfigurationexception, pluginconfigurationexception, plugincreaterule, pluginrules, pluginrules, rule, string, string, util

The Commons Digester PluginCreateRule.java source code

/* $Id: PluginCreateRule.java 992060 2010-09-02 19:09:47Z simonetripodi $
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.digester.plugins;

import java.util.List;

import org.apache.commons.digester.Rule;
import org.apache.commons.logging.Log;

/**
 * Allows the original rules for parsing the configuration file to define
 * points at which plugins are allowed, by configuring a PluginCreateRule
 * with the appropriate pattern.
 *
 * @since 1.6
 */
public class PluginCreateRule extends Rule implements InitializableRule {

    // see setPluginClassAttribute
    private String pluginClassAttrNs = null;
    private String pluginClassAttr = null;
    
    // see setPluginIdAttribute
    private String pluginIdAttrNs = null;
    private String pluginIdAttr = null;
    
    /**
     * In order to invoke the addRules method on the plugin class correctly,
     * we need to know the pattern which this rule is matched by.
     */
    private String pattern;

    /** A base class that any plugin must derive from. */
    private Class<?> baseClass = null;

    /**
     * Info about optional default plugin to be used if no plugin-id is
     * specified in the input data. This can simplify the syntax where one
     * particular plugin is usually used.
     */
    private Declaration defaultPlugin;

    /**
     * Currently, none of the Rules methods allow exceptions to be thrown.
     * Therefore if this class cannot initialise itself properly, it cannot
     * cause the digester to stop. Instead, we cache the exception and throw
     * it the first time the begin() method is called.
     */
    private PluginConfigurationException initException;

    //-------------------- constructors -------------------------------------

    /**
     * Create a plugin rule where the user <i>must specify a plugin-class
     * or plugin-id.
     * 
     * @param baseClass is the class which any specified plugin <i>must be
     * descended from.
     */
    public PluginCreateRule(Class<?> baseClass) {
        this.baseClass = baseClass;
    }

    /**
     * Create a plugin rule where the user <i>may specify a plugin.
     * If the user doesn't specify a plugin, then the default class specified 
     * in this constructor is used.
     * 
     * @param baseClass is the class which any specified plugin <i>must be
     * descended from.
     * @param dfltPluginClass is the class which will be used if the user
     * doesn't specify any plugin-class or plugin-id. This class will have
     * custom rules installed for it just like a declared plugin.
     */
    public PluginCreateRule(Class<?> baseClass, Class dfltPluginClass) {
        this.baseClass = baseClass;
        if (dfltPluginClass != null) {
            defaultPlugin = new Declaration(dfltPluginClass);
        }
    }

    /**
     * Create a plugin rule where the user <i>may specify a plugin.
     * If the user doesn't specify a plugin, then the default class specified 
     * in this constructor is used.
     * 
     * @param baseClass is the class which any specified plugin <i>must be
     * descended from.
     * @param dfltPluginClass is the class which will be used if the user
     * doesn't specify any plugin-class or plugin-id. This class will have
     * custom rules installed for it just like a declared plugin.
     * @param dfltPluginRuleLoader is a RuleLoader instance which knows how
     * to load the custom rules associated with this default plugin.
     */
    public PluginCreateRule(Class<?> baseClass, Class dfltPluginClass,
                    RuleLoader dfltPluginRuleLoader) {

        this.baseClass = baseClass;
        if (dfltPluginClass != null) {
            defaultPlugin = 
                new Declaration(dfltPluginClass, dfltPluginRuleLoader);
        }
    }

    //------------------- properties ---------------------------------------
    
    /**
     * Sets the xml attribute which the input xml uses to indicate to a 
     * PluginCreateRule which class should be instantiated.
     * <p>
     * See {@link PluginRules#setPluginClassAttribute} for more info.
     */
    public void setPluginClassAttribute(String namespaceUri, String attrName) {
        pluginClassAttrNs = namespaceUri;
        pluginClassAttr = attrName;
    }

    /**
     * Sets the xml attribute which the input xml uses to indicate to a 
     * PluginCreateRule which plugin declaration is being referenced.
     * <p>
     * See {@link PluginRules#setPluginIdAttribute} for more info.
     */
    public void setPluginIdAttribute(String namespaceUri, String attrName) {
        pluginIdAttrNs = namespaceUri;
        pluginIdAttr = attrName;
    }

    //------------------- methods --------------------------------------------

    /**
     * Invoked after this rule has been added to the set of digester rules,
     * associated with the specified pattern. Check all configuration data is
     * valid and remember the pattern for later.
     * 
     * @param matchPattern is the digester match pattern that is associated 
     * with this rule instance, eg "root/widget".
     * @exception PluginConfigurationException
     */
    public void postRegisterInit(String matchPattern)
                                 throws PluginConfigurationException {
        Log log = LogUtils.getLogger(digester);
        boolean debug = log.isDebugEnabled();
        if (debug) {
            log.debug("PluginCreateRule.postRegisterInit" + 
                      ": rule registered for pattern [" + matchPattern + "]");
        }

        if (digester == null) {
            // We require setDigester to be called before this method.
            // Note that this means that PluginCreateRule cannot be added
            // to a Rules object which has not yet been added to a
            // Digester object.
            initException = new PluginConfigurationException(
                 "Invalid invocation of postRegisterInit" + 
                 ": digester not set.");
            throw initException;
        }

        if (pattern != null) {
            // We have been called twice, ie a single instance has been
            // associated with multiple patterns.
            //
            // Generally, Digester Rule instances can be associated with 
            // multiple patterns. However for plugins, this creates some 
            // complications. Some day this may be supported; however for 
            // now we just reject this situation.
            initException = new PluginConfigurationException(
               "A single PluginCreateRule instance has been mapped to" + 
                 " multiple patterns; this is not supported.");
            throw initException;
        }

        if (matchPattern.indexOf('*') != -1) {
            // having wildcards in patterns is extremely difficult to
            // deal with. For now, we refuse to allow this.
            //
            // TODO: check for any chars not valid in xml element name
            // rather than just *.
            //
            // Reasons include:
            // (a) handling recursive plugins, and
            // (b) determining whether one pattern is "below" another,
            //     as done by PluginRules. Without wildcards, "below"
            //     just means startsWith, which is easy to check.
            initException = new PluginConfigurationException(
                 "A PluginCreateRule instance has been mapped to" + 
                 " pattern [" + matchPattern + "]." + 
                 " This pattern includes a wildcard character." + 
                 " This is not supported by the plugin architecture.");
            throw initException;
        }

        if (baseClass == null) {
            baseClass = Object.class;
        }
        
        PluginRules rules = (PluginRules) digester.getRules();
        PluginManager pm = rules.getPluginManager();

        // check default class is valid
        if (defaultPlugin != null) {
            if (!baseClass.isAssignableFrom(defaultPlugin.getPluginClass())) {
                initException = new PluginConfigurationException(
                     "Default class [" + 
                     defaultPlugin.getPluginClass().getName() + 
                     "] does not inherit from [" + 
                     baseClass.getName() + "].");
                throw initException;
            }

            try {
                defaultPlugin.init(digester, pm);
                
            } catch(PluginException pwe) {
            
                throw new PluginConfigurationException(
                    pwe.getMessage(), pwe.getCause());
            }
        }

        // remember the pattern for later
        pattern = matchPattern;
        
        if (pluginClassAttr ==  null) {
            // the user hasn't set explicit xml attr names on this rule,
            // so fetch the default values
            pluginClassAttrNs = rules.getPluginClassAttrNs();
            pluginClassAttr = rules.getPluginClassAttr();
            
            if (debug) {
                log.debug(
                    "init: pluginClassAttr set to per-digester values ["
                    + "ns=" + pluginClassAttrNs 
                    + ", name=" + pluginClassAttr + "]");
            }
        } else {
            if (debug) {
                log.debug(
                    "init: pluginClassAttr set to rule-specific values ["
                    + "ns=" + pluginClassAttrNs 
                    + ", name=" + pluginClassAttr + "]");
            }
        }
        
        if (pluginIdAttr ==  null) {
            // the user hasn't set explicit xml attr names on this rule,
            // so fetch the default values
            pluginIdAttrNs = rules.getPluginIdAttrNs();
            pluginIdAttr = rules.getPluginIdAttr();
            
            if (debug) {
                log.debug(
                    "init: pluginIdAttr set to per-digester values ["
                    + "ns=" + pluginIdAttrNs 
                    + ", name=" + pluginIdAttr + "]");
            }
        } else {
            if (debug) {
                log.debug(
                    "init: pluginIdAttr set to rule-specific values ["
                    + "ns=" + pluginIdAttrNs 
                    + ", name=" + pluginIdAttr + "]");
            }
        }
    }

    /**
     * Invoked when the Digester matches this rule against an xml element.
     * <p>
     * A new instance of the target class is created, and pushed onto the
     * stack. A new "private" PluginRules object is then created and set as
     * the digester's default Rules object. Any custom rules associated with
     * the plugin class are then loaded into that new Rules object.
     * Finally, any custom rules that are associated with the current pattern
     * (such as SetPropertiesRules) have their begin methods executed.
     * 
     * @param namespace 
     * @param name 
     * @param attributes
     *
     * @throws ClassNotFoundException
     * @throws PluginInvalidInputException
     * @throws PluginConfigurationException
     */
    @Override
    public void begin(String namespace, String name,
                      org.xml.sax.Attributes attributes)
                      throws java.lang.Exception {
        Log log = digester.getLogger();
        boolean debug = log.isDebugEnabled();
        if (debug) {
            log.debug("PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + 
                  " match=[" + digester.getMatch() + "]");
        }

        if (initException != null) {
            // we had a problem during initialisation that we could
            // not report then; report it now.
            throw initException;
        }
        
        // load any custom rules associated with the plugin
        PluginRules oldRules = (PluginRules) digester.getRules();
        PluginManager pluginManager = oldRules.getPluginManager();
        Declaration currDeclaration = null;
            
        String pluginClassName; 
        if (pluginClassAttrNs == null) {
            // Yep, this is ugly.
            //
            // In a namespace-aware parser, the one-param version will 
            // return attributes with no namespace.
            //
            // In a non-namespace-aware parser, the two-param version will 
            // never return any attributes, ever.
            pluginClassName = attributes.getValue(pluginClassAttr);
        } else {
            pluginClassName = 
                attributes.getValue(pluginClassAttrNs, pluginClassAttr);
        }

        String pluginId; 
        if (pluginIdAttrNs == null) {
            pluginId = attributes.getValue(pluginIdAttr);
        } else {
            pluginId = 
                attributes.getValue(pluginIdAttrNs, pluginIdAttr);
        }
        
        if (pluginClassName != null) {
            // The user is using a plugin "inline", ie without a previous
            // explicit declaration. If they have used the same plugin class
            // before, we have already gone to the effort of creating a 
            // Declaration object, so retrieve it. If there is no existing
            // declaration object for this class, then create one.

            currDeclaration = pluginManager.getDeclarationByClass(
                pluginClassName);

            if (currDeclaration == null) {
                currDeclaration = new Declaration(pluginClassName);
                try {
                    currDeclaration.init(digester, pluginManager);
                } catch(PluginException pwe) {
                    throw new PluginInvalidInputException(
                        pwe.getMessage(), pwe.getCause());
                }
                pluginManager.addDeclaration(currDeclaration);
            }
        } else if (pluginId != null) {
            currDeclaration = pluginManager.getDeclarationById(pluginId);
                
            if (currDeclaration == null) {
                throw new PluginInvalidInputException(
                    "Plugin id [" + pluginId + "] is not defined.");
            }
        } else if (defaultPlugin != null) {
            currDeclaration = defaultPlugin;
        } else {
            throw new PluginInvalidInputException(
                "No plugin class specified for element " +
                pattern);
        }
            
        // get the class of the user plugged-in type
        Class<?> pluginClass = currDeclaration.getPluginClass();
        
        String path = digester.getMatch();

        // create a new Rules object and effectively push it onto a stack of
        // rules objects. The stack is actually a linked list; using the
        // PluginRules constructor below causes the new instance to link
        // to the previous head-of-stack, then the Digester.setRules() makes
        // the new instance the new head-of-stack.
        PluginRules newRules = new PluginRules(digester, path, oldRules, pluginClass);
        digester.setRules(newRules);
        
        if (debug) {
            log.debug("PluginCreateRule.begin: installing new plugin: " +
                "oldrules=" + oldRules.toString() +
                ", newrules=" + newRules.toString());
        }
              
        // load up the custom rules
        currDeclaration.configure(digester, pattern);

        // create an instance of the plugin class
        Object instance = pluginClass.newInstance();
        getDigester().push(instance);
        if (debug) {
            log.debug(
                "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + 
                " match=[" + digester.getMatch() + "]" + 
                " pushed instance of plugin [" + pluginClass.getName() + "]");
        }
        
        // and now we have to fire any custom rules which would have
        // been matched by the same path that matched this rule, had
        // they been loaded at that time.
        List<Rule> rules = newRules.getDecoratedRules().match(namespace, path);
        fireBeginMethods(rules, namespace, name, attributes); 
    }

    /**
     * Process the body text of this element.
     *
     * @param text The body text of this element
     */
    @Override
    public void body(String namespace, String name, String text)
        throws Exception {

        // While this class itself has no work to do in the body method,
        // we do need to fire the body methods of all dynamically-added
        // rules matching the same path as this rule. During begin, we had
        // to manually execute the dynamic rules' begin methods because they
        // didn't exist in the digester's Rules object when the match begin.
        // So in order to ensure consistent ordering of rule execution, the
        // PluginRules class deliberately avoids returning any such rules
        // in later calls to the match method, instead relying on this
        // object to execute them at the appropriate time.
        //
        // Note that this applies only to rules matching exactly the path
        // which is also matched by this PluginCreateRule. 

        String path = digester.getMatch();
        PluginRules newRules = (PluginRules) digester.getRules();
        List<Rule> rules = newRules.getDecoratedRules().match(namespace, path);
        fireBodyMethods(rules, namespace, name, text);
    }

    /**
     * Invoked by the digester when the closing tag matching this Rule's
     * pattern is encountered.
     * </p>
     * 
     * @param namespace Description of the Parameter
     * @param name Description of the Parameter
     * @exception Exception Description of the Exception
     *
     * @see #begin
     */
    @Override
    public void end(String namespace, String name)
                    throws Exception {


        // see body method for more info
        String path = digester.getMatch();
        PluginRules newRules = (PluginRules) digester.getRules();
        List<Rule> rules = newRules.getDecoratedRules().match(namespace, path);
        fireEndMethods(rules, namespace, name);
        
        // pop the stack of PluginRules instances, which
        // discards all custom rules associated with this plugin
        digester.setRules(newRules.getParent());
        
        // and get rid of the instance of the plugin class from the
        // digester object stack.
        digester.pop();
    }

    /**
     * Return the pattern that this Rule is associated with.
     * <p>
     * In general, Rule instances <i>can be associated with multiple
     * patterns. A PluginCreateRule, however, will only function correctly
     * when associated with a single pattern. It is possible to fix this, but
     * I can't be bothered just now because this feature is unlikely to be
     * used.
     * </p>
     * 
     * @return The pattern value
     */
    public String getPattern() {
        return pattern;
    }
    
    /**
     * Duplicate the processing that the Digester does when firing the
     * begin methods of rules. It would be really nice if the Digester
     * class provided a way for this functionality to just be invoked
     * directly.
     */
    public void fireBeginMethods(List<Rule> rules,
                      String namespace, String name,
                      org.xml.sax.Attributes list)
                      throws java.lang.Exception {
        
        if ((rules != null) && (rules.size() > 0)) {
            Log log = digester.getLogger();
            boolean debug = log.isDebugEnabled();
            for (int i = 0; i < rules.size(); i++) {
                try {
                    Rule rule = rules.get(i);
                    if (debug) {
                        log.debug("  Fire begin() for " + rule);
                    }
                    rule.begin(namespace, name, list);
                } catch (Exception e) {
                    throw digester.createSAXException(e);
                } catch (Error e) {
                    throw e;
                }
            }
        }
    }

    /**
     * Duplicate the processing that the Digester does when firing the
     * body methods of rules. It would be really nice if the Digester
     * class provided a way for this functionality to just be invoked
     * directly.
     */
    private void fireBodyMethods(List<Rule> rules,
                    String namespaceURI, String name,
                    String text) throws Exception {

        if ((rules != null) && (rules.size() > 0)) {
            Log log = digester.getLogger();
            boolean debug = log.isDebugEnabled();
            for (int i = 0; i < rules.size(); i++) {
                try {
                    Rule rule = rules.get(i);
                    if (debug) {
                        log.debug("  Fire body() for " + rule);
                    }
                    rule.body(namespaceURI, name, text);
                } catch (Exception e) {
                    throw digester.createSAXException(e);
                } catch (Error e) {
                    throw e;
                }
            }
        }
    }
    
    /**
     * Duplicate the processing that the Digester does when firing the
     * end methods of rules. It would be really nice if the Digester
     * class provided a way for this functionality to just be invoked
     * directly.
     */
    public void fireEndMethods(List<Rule> rules,
                    String namespaceURI, String name)
                    throws Exception {

        // Fire "end" events for all relevant rules in reverse order
        if (rules != null) {
            Log log = digester.getLogger();
            boolean debug = log.isDebugEnabled();
            for (int i = 0; i < rules.size(); i++) {
                int j = (rules.size() - i) - 1;
                try {
                    Rule rule = rules.get(j);
                    if (debug) {
                        log.debug("  Fire end() for " + rule);
                    }
                    rule.end(namespaceURI, name);
                } catch (Exception e) {
                    throw digester.createSAXException(e);
                } catch (Error e) {
                    throw e;
                }
            }
        }
    }
}

Other Commons Digester examples (source code examples)

Here is a short list of links related to this Commons Digester PluginCreateRule.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.