/* $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
/** * 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.
/** * 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.
/** 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.
/** * 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.
/** * 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 must specify a plugin-class or plugin-id.
Params:
  • baseClass – is the class which any specified plugin must be descended from.
/** * Create a plugin rule where the user <i>must</i> specify a plugin-class * or plugin-id. * * @param baseClass is the class which any specified plugin <i>must</i> be * descended from. */
public PluginCreateRule(Class<?> baseClass) { this.baseClass = baseClass; }
Create a plugin rule where the user may specify a plugin. If the user doesn't specify a plugin, then the default class specified in this constructor is used.
Params:
  • baseClass – is the class which any specified plugin must be descended from.
  • 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.
/** * Create a plugin rule where the user <i>may</i> 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</i> 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 may specify a plugin. If the user doesn't specify a plugin, then the default class specified in this constructor is used.
Params:
  • baseClass – is the class which any specified plugin must be descended from.
  • 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.
  • dfltPluginRuleLoader – is a RuleLoader instance which knows how to load the custom rules associated with this default plugin.
/** * Create a plugin rule where the user <i>may</i> 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</i> 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.

See PluginRules.setPluginClassAttribute for more info.

/** * 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.

See PluginRules.setPluginIdAttribute for more info.

/** * 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.
Params:
  • matchPattern – is the digester match pattern that is associated with this rule instance, eg "root/widget".
Throws:
/** * 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.

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.

Params:
  • namespace –
  • name –
  • attributes –
Throws:
/** * 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.
Params:
  • text – The body text of this element
/** * 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.

Params:
  • namespace – Description of the Parameter
  • name – Description of the Parameter
Throws:
See Also:
/** * 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.

In general, Rule instances 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.

Returns:The pattern value
/** * Return the pattern that this Rule is associated with. * <p> * In general, Rule instances <i>can</i> 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.
/** * 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.
/** * 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.
/** * 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; } } } } }