| /* $Id: PluginCreateRule.java,v 1.19 2004/05/10 06:44:13 skitching Exp $ |
| * |
| * Copyright 2003-2004 The Apache Software Foundation. |
| * |
| * Licensed 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.Iterator; |
| import java.util.ListIterator; |
| import java.util.List; |
| import java.io.File; |
| |
| import org.apache.commons.digester.Digester; |
| import org.apache.commons.digester.Rule; |
| import org.apache.commons.digester.Rules; |
| 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</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 <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 <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. |
| * <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 |
| */ |
| 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; |
| } |
| |
| 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 oldRules = (PluginRules) digester.getRules(); |
| PluginRules newRules = new PluginRules(path, oldRules); |
| digester.setRules(newRules); |
| |
| // load any custom rules associated with the plugin |
| PluginManager pluginManager = newRules.getPluginManager(); |
| Declaration currDeclaration = null; |
| |
| if (debug) { |
| log.debug("PluginCreateRule.begin: installing new plugin: " + |
| "oldrules=" + oldRules.toString() + |
| ", newrules=" + newRules.toString()); |
| } |
| |
| 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); |
| } |
| |
| // now load up the custom rules |
| currDeclaration.configure(digester, pattern); |
| |
| // and now create an instance of the plugin class |
| Class pluginClass = currDeclaration.getPluginClass(); |
| |
| 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 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 |
| */ |
| 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 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 |
| */ |
| 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 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</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. |
| */ |
| public void fireBeginMethods(List 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 = (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 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 = (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 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 = (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; |
| } |
| } |
| } |
| } |
| } |