| package org.apache.commons.digester3.plugins; |
| |
| /* |
| * 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. |
| */ |
| |
| import java.util.List; |
| |
| import org.apache.commons.digester3.Rule; |
| import org.apache.commons.logging.Log; |
| import org.xml.sax.Attributes; |
| |
| /** |
| * 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 initialize |
| * 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( final 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( final Class<?> baseClass, final 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( final Class<?> baseClass, final Class<?> dfltPluginClass, final 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. |
| * |
| * @param namespaceUri is the namespace uri that the specified attribute is in. If the attribute is in no namespace, |
| * then this should be null. Note that if a namespace is used, the attrName value should <i>not</i> |
| * contain any kind of namespace-prefix. Note also that if you are using a non-namespace-aware parser, |
| * this parameter <i>must</i> be null. |
| * @param attrName is the attribute whose value contains the name of the class to be instantiated. |
| */ |
| public void setPluginClassAttribute( final String namespaceUri, final 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. |
| * |
| * @param namespaceUri is the namespace uri that the specified attribute is in. If the attribute is in no namespace, |
| * then this should be null. Note that if a namespace is used, the attrName value should <i>not</i> |
| * contain any kind of namespace-prefix. Note also that if you are using a non-namespace-aware parser, |
| * this parameter <i>must</i> be null. |
| * @param attrName is the attribute whose value contains the id of the plugin declaration to be used when |
| * instantiating an object. |
| */ |
| public void setPluginIdAttribute( final String namespaceUri, final String attrName ) |
| { |
| pluginIdAttrNs = namespaceUri; |
| pluginIdAttr = attrName; |
| } |
| |
| // ------------------- methods -------------------------------------------- |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void postRegisterInit( final String matchPattern ) |
| { |
| final Log log = LogUtils.getLogger( getDigester() ); |
| final boolean debug = log.isDebugEnabled(); |
| if ( debug ) |
| { |
| log.debug( "PluginCreateRule.postRegisterInit" + ": rule registered for pattern [" + matchPattern + "]" ); |
| } |
| |
| if ( getDigester() == 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; |
| } |
| |
| final PluginRules rules = (PluginRules) getDigester().getRules(); |
| final 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( getDigester(), pm ); |
| |
| } |
| catch ( final 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 the namespace URI of the matching element, or an empty string if the parser is not namespace |
| * aware or the element has no namespace |
| * @param name the local name if the parser is namespace aware, or just the element name otherwise |
| * @param attributes The attribute list of this element |
| * @throws Exception if any error occurs |
| */ |
| @Override |
| public void begin( final String namespace, final String name, final org.xml.sax.Attributes attributes ) |
| throws Exception |
| { |
| final Log log = getDigester().getLogger(); |
| final boolean debug = log.isDebugEnabled(); |
| if ( debug ) |
| { |
| log.debug( "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + " match=[" + getDigester().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 |
| final PluginRules oldRules = (PluginRules) getDigester().getRules(); |
| final 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( getDigester(), pluginManager ); |
| } |
| catch ( final 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 |
| final Class<?> pluginClass = currDeclaration.getPluginClass(); |
| |
| final String path = getDigester().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. |
| final PluginRules newRules = new PluginRules( getDigester(), path, oldRules, pluginClass ); |
| getDigester().setRules( newRules ); |
| |
| if ( debug ) |
| { |
| log.debug( "PluginCreateRule.begin: installing new plugin: " + "oldrules=" + oldRules.toString() |
| + ", newrules=" + newRules.toString() ); |
| } |
| |
| // load up the custom rules |
| currDeclaration.configure( getDigester(), pattern ); |
| |
| // create an instance of the plugin class |
| final Object instance = pluginClass.newInstance(); |
| getDigester().push( instance ); |
| if ( debug ) |
| { |
| log.debug( "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + " match=[" + getDigester().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. |
| final List<Rule> rules = newRules.getDecoratedRules().match( namespace, path, name, attributes ); |
| fireBeginMethods( rules, namespace, name, attributes ); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void body( final String namespace, final String name, final 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. |
| |
| final String path = getDigester().getMatch(); |
| final PluginRules newRules = (PluginRules) getDigester().getRules(); |
| final List<Rule> rules = newRules.getDecoratedRules().match( namespace, path, name, null ); |
| fireBodyMethods( rules, namespace, name, text ); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void end( final String namespace, final String name ) |
| throws Exception |
| { |
| // see body method for more info |
| final String path = getDigester().getMatch(); |
| final PluginRules newRules = (PluginRules) getDigester().getRules(); |
| final List<Rule> rules = newRules.getDecoratedRules().match( namespace, path, name, null ); |
| fireEndMethods( rules, namespace, name ); |
| |
| // pop the stack of PluginRules instances, which |
| // discards all custom rules associated with this plugin |
| getDigester().setRules( newRules.getParent() ); |
| |
| // and get rid of the instance of the plugin class from the |
| // digester object stack. |
| getDigester().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. |
| * |
| * @param rules The rules which {@link Rule#begin(String, String, Attributes)} method has to be fired |
| * @param namespace the namespace URI of the matching element, or an empty string if the parser is not namespace |
| * aware or the element has no namespace |
| * @param name the local name if the parser is namespace aware, or just the element name otherwise |
| * @param list The attribute list of this element |
| * @throws Exception if any error occurs |
| */ |
| public void fireBeginMethods( final List<Rule> rules, final String namespace, final String name, final Attributes list ) |
| throws Exception |
| { |
| |
| if ( ( rules != null ) && ( !rules.isEmpty() ) ) |
| { |
| final Log log = getDigester().getLogger(); |
| final boolean debug = log.isDebugEnabled(); |
| for ( final Rule rule : rules ) |
| { |
| if ( debug ) |
| { |
| log.debug( " Fire begin() for " + rule ); |
| } |
| try |
| { |
| rule.begin( namespace, name, list ); |
| } |
| catch ( final Exception e ) |
| { |
| throw getDigester().createSAXException( e ); |
| } |
| catch ( final Error e ) |
| { |
| throw e; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Duplicate the processing that the Digester does when firing the {@link Rule#body(String, String, String)} methods |
| * of rules. |
| * |
| * It would be really nice if the Digester class provided a way for this functionality to just be invoked directly. |
| * |
| * @param rules The rules which {@link Rule#body(String, String, String)} method has to be fired |
| * @param namespace the namespace URI of the matching element, or an empty string if the parser is not namespace |
| * aware or the element has no namespace |
| * @param name the local name if the parser is namespace aware, or just the element name otherwise |
| * @param text The text of the body of this element |
| * @throws Exception if any error occurs |
| */ |
| private void fireBodyMethods( final List<Rule> rules, final String namespaceURI, final String name, final String text ) |
| throws Exception |
| { |
| if ( ( rules != null ) && ( !rules.isEmpty() ) ) |
| { |
| final Log log = getDigester().getLogger(); |
| final boolean debug = log.isDebugEnabled(); |
| for ( final Rule rule : rules ) |
| { |
| if ( debug ) |
| { |
| log.debug( " Fire body() for " + rule ); |
| } |
| try |
| { |
| rule.body( namespaceURI, name, text ); |
| } |
| catch ( final Exception e ) |
| { |
| throw getDigester().createSAXException( e ); |
| } |
| catch ( final 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. |
| * |
| * @param rules The rules which {@link Rule#end(String, String)} method has to be fired |
| * @param namespaceURI the namespace URI of the matching element, or an empty string if the parser is not namespace |
| * aware or the element has no namespace |
| * @param name the local name if the parser is namespace aware, or just the element name otherwise |
| * @throws Exception if any error occurs |
| */ |
| public void fireEndMethods( final List<Rule> rules, final String namespaceURI, final String name ) |
| throws Exception |
| { |
| // Fire "end" events for all relevant rules in reverse order |
| if ( rules != null ) |
| { |
| final Log log = getDigester().getLogger(); |
| final boolean debug = log.isDebugEnabled(); |
| for ( int i = 0; i < rules.size(); i++ ) |
| { |
| final int j = ( rules.size() - i ) - 1; |
| final Rule rule = rules.get( j ); |
| if ( debug ) |
| { |
| log.debug( " Fire end() for " + rule ); |
| } |
| try |
| { |
| rule.end( namespaceURI, name ); |
| } |
| catch ( final Exception e ) |
| { |
| throw getDigester().createSAXException( e ); |
| } |
| catch ( final Error e ) |
| { |
| throw e; |
| } |
| } |
| } |
| } |
| |
| } |