rdonkin     2003/10/04 05:28:44

  Added:       digester/src/java/org/apache/commons/digester/plugins
                        PluginCreateRule.java
  Log:
  Added plugins module. Submitted by Simon Kitching.
  
  Revision  Changes    Path
  1.1                  
jakarta-commons/digester/src/java/org/apache/commons/digester/plugins/PluginCreateRule.java
  
  Index: PluginCreateRule.java
  ===================================================================
  /*
   *  ====================================================================
   *
   *  The Apache Software License, Version 1.1
   *
   *  Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
   *  reserved.
   *
   *  Redistribution and use in source and binary forms, with or without
   *  modification, are permitted provided that the following conditions
   *  are met:
   *
   *  1. Redistributions of source code must retain the above copyright
   *  notice, this list of conditions and the following disclaimer.
   *
   *  2. Redistributions in binary form must reproduce the above copyright
   *  notice, this list of conditions and the following disclaimer in
   *  the documentation and/or other materials provided with the
   *  distribution.
   *
   *  3. The end-user documentation included with the redistribution, if
   *  any, must include the following acknowlegement:
   *  "This product includes software developed by the
   *  Apache Software Foundation (http://www.apache.org/)."
   *  Alternately, this acknowlegement may appear in the software itself,
   *  if and wherever such third-party acknowlegements normally appear.
   *
   *  4. The names "The Jakarta Project", "Commons", and "Apache Software
   *  Foundation" must not be used to endorse or promote products derived
   *  from this software without prior written permission. For written
   *  permission, please contact [EMAIL PROTECTED]
   *
   *  5. Products derived from this software may not be called "Apache"
   *  nor may "Apache" appear in their names without prior written
   *  permission of the Apache Group.
   *
   *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   *  SUCH DAMAGE.
   *  ====================================================================
   *
   *  This software consists of voluntary contributions made by many
   *  individuals on behalf of the Apache Software Foundation.  For more
   *  information on the Apache Software Foundation, please see
   *  <http://www.apache.org/>.
   *
   */
   
  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;
  import org.apache.commons.logging.LogFactory;
  
  /**
   * 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.
   * 
   * @author Simon Kitching
   */
  public class PluginCreateRule extends Rule implements InitializableRule {
  
      private static Log log = LogFactory.getLog(PluginCreateRule.class);
  
      private static final String PLUGIN_CLASS_ATTR = "plugin-class";
      private static final String PLUGIN_ID_ATTR = "plugin-id";
      
      /**
       * 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_;
  
      /**
       * Our private set of rules associated with the concrete class that
       * the user requested to be instantiated. This object is only valid
       * between a call to begin() and the corresponding call to end().
       */
      private PluginRules localRules_; 
      
      //-------------------- 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) {
          super();
          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) {
          super();
          baseClass_ = baseClass;
          if (dfltPluginClass != null) {
              defaultPlugin_ = new Declaration(dfltPluginClass);
          }
      }
  
      //------------------- properties ---------------------------------------
  
      public void setDefaultRuleMethod(String dfltPluginRuleMethod) {
          if (defaultPlugin_ != null) {
              defaultPlugin_.setRuleMethod(dfltPluginRuleMethod);
          }
      }
      
      public void setDefaultRuleClass(Class dfltPluginRuleClass) {
          if (defaultPlugin_ != null) {
              defaultPlugin_.setRuleClass(dfltPluginRuleClass);
          }
      }
      
      public void setDefaultRuleResource(String dfltPluginRuleResource) {
          if (defaultPlugin_ != null) {
              defaultPlugin_.setRuleResource(dfltPluginRuleResource);
          }
      }
      
      public void setDefaultRuleFile(String dfltPluginRuleFile) {
          if (defaultPlugin_ != null) {
              defaultPlugin_.setRuleFile(new File(dfltPluginRuleFile));
          }
      }
  
      public void setDefaultRuleAutoSetProperties(boolean enabled) {
          if (defaultPlugin_ != null) {
              defaultPlugin_.setAutoSetProperties(enabled);
          }
      }
      
      //------------------- 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 pattern is the digester match pattern that is associated with
       * this rule instance, eg "root/widget".
       * @exception PluginConfigurationException
       */
      public void postRegisterInit(String pattern)
      throws PluginConfigurationException {
          log.debug("PluginCreateRule.postRegisterInit" + 
                    ": rule registered for pattern [" + pattern + "]");
  
          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 (pattern.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 [" + pattern + "]." + 
                   " This pattern includes a wildcard character." + 
                   " This is not supported by the plugin architecture.");
              throw initException_;
          }
  
          if (baseClass_ == null) {
              baseClass_ = Object.class;
          }
          
          // 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);
              }
              catch(PluginWrappedException pwe) {
                  throw new PluginConfigurationException(
                      pwe.getMessage(), pwe.getCause());
              }
          }
  
          // remember the pattern for later
          pattern_ = pattern;
      }
  
      /**
       * 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.
       * <p>
       * Because a PluginCreateRule is also a Delegate, this method is also
       * called on the start of any element occurring below the pattern
       * associated with this rule. In this case, this method acts like the
       * Digester's startElement method: it fires the begin() method of every
       * custom rule associated with the plugin class that matches that pattern.
       * See [EMAIL PROTECTED] #delegateBegin}.
       * 
       * @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 {
          if (log.isDebugEnabled()) {
              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 currMatch = digester.getMatch();
          if (currMatch.length() == pattern_.length()) {
              // ok here we are actually instantiating a new plugin object,
              // and storing its rules into a new Rules object
              if (localRules_ != null) {
                  throw new PluginAssertionError(
                      "Begin called when localRules_ is not null.");
              }
                        
              PluginRules oldRules = (PluginRules) digester.getRules();
              localRules_ = new PluginRules(this, oldRules);
              PluginManager pluginManager = localRules_.getPluginManager();
              Declaration currDeclaration = null;
              
              if (log.isDebugEnabled()) {
                  log.debug("PluginCreateRule.begin: installing new plugin: " 
                      + "oldrules=" + oldRules.toString()
                      + ", localrules=" + localRules_.toString());
              }
                
              String pluginClassName = attributes.getValue(PLUGIN_CLASS_ATTR);
              String pluginId = attributes.getValue(PLUGIN_ID_ATTR);
              
              if (pluginClassName != null) {
                  currDeclaration = pluginManager.getDeclarationByClass(
                      pluginClassName);
      
                  if (currDeclaration == null) {
                      currDeclaration = new Declaration(pluginClassName);
                      try {
                          currDeclaration.init(digester);
                      }
                      catch(PluginWrappedException pwe) {
                          throw new PluginInvalidInputException(
                              pwe.getMessage(), pwe.getCause());
                      }
                      pluginManager.addDeclaration(currDeclaration);
                  }
              }
              else if (pluginId != null) {
                  currDeclaration = pluginManager.getDeclarationById(pluginId);
                  
                  if (currDeclaration == null) {
                      throw new Exception(
                          "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 into a private Rules instance
              digester.setRules(localRules_);
              {
                  currDeclaration.configure(digester, pattern_);
          
                  Class pluginClass = currDeclaration.getPluginClass();
                  
                  Object instance = pluginClass.newInstance();
                  getDigester().push(instance);
                  if (log.isDebugEnabled()) {
                      log.debug(
                          "PluginCreateRule.begin" + ": pattern=[" + pattern_ + "]" + 
                          " match=[" + digester.getMatch() + "]" + 
                          " pushed instance of plugin [" + pluginClass.getName() + 
"]");
                  }
              }
              digester.setRules(oldRules);
  
              ((PluginRules) oldRules).beginPlugin(this);
          }
          
          // fire the begin method of all custom rules
          Rules oldRules = digester.getRules();
          
          if (log.isDebugEnabled()) {
              log.debug("PluginCreateRule.begin: firing nested rules: " 
                  + "oldrules=" + oldRules.toString()
                  + ", localrules=" + localRules_.toString());
          }
  
          // assert oldRules = localRules_.oldRules
          digester.setRules(localRules_);
          delegateBegin(namespace, name, attributes);
          digester.setRules(oldRules);
  
          if (log.isDebugEnabled()) {
              log.debug("PluginCreateRule.begin: restored old rules to " 
                  + "oldrules=" + oldRules.toString());
          }
      }
  
      /**
       * Invoked by the digester when the closing tag matching this Rule's
       * pattern is encountered. See [EMAIL PROTECTED] #delegateBody}.
       *
       * @see #begin
       */
      public void body(String namespace, String name, String text)
          throws Exception {
              
          Rules oldRules = digester.getRules();
          // assert oldRules == localRules_.oldRules
          digester.setRules(localRules_);
          delegateBody(namespace, name, text);
          digester.setRules(oldRules);
      }
      
      /**
       * Invoked by the digester when the closing tag matching this Rule's
       * pattern is encountered.
       * </p>
       * As noted on method begin, because PluginCreateRule is a Delegate,
       * this method is also called at the end tag of every pattern that
       * is "below" the pattern associated with this rule. In this case, we
       * fire the end method of every custom rule associated with the 
       * current plugin class. See [EMAIL PROTECTED] #delegateEnd}.
       * <p>
       * If we are really encountering the end tag associated with this rule
       * (rather than the end of an element "below" that tag), then we
       * remove the object we pushed onto the digester stack when the
       * opening tag was encountered.
       * 
       * @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 {
              
          Rules oldRules = digester.getRules();
          // assert oldRules == localRules_.parentRules
          digester.setRules(localRules_);
          delegateEnd(namespace, name);
          digester.setRules(oldRules);
  
          String currMatch = digester.getMatch();
          if (currMatch.length() == pattern_.length()) {
              // the end of the element on which the PluginCreateRule has
              // been mounted has been reached.
              localRules_ = null;
              ((PluginRules) oldRules).endPlugin(this);
              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_;
      }
      
      /**
       * Here we act like Digester.begin, finding a match for the pattern
       * in our private rules object, then executing the begin method of
       * each matching rule.
       */
      public void delegateBegin(
      String namespace, String name, 
      org.xml.sax.Attributes attributes)
      throws java.lang.Exception {
          
          // Fire "begin" events for all relevant rules
          boolean debug = log.isDebugEnabled();
          String match = digester.getMatch();
          List rules = digester.getRules().match(namespace, match);
          Iterator ri = rules.iterator();
          while (ri.hasNext()) {
              Rule rule = (Rule) ri.next();
              if (debug) {
                  log.debug("  Fire begin() for " + rule);
              }
              rule.begin(namespace, name, attributes);
          }
      }
      
      /**
       * Here we act like Digester.body, except against our private rules.
       */
      public void delegateBody(String namespace, String name, String text)
      throws Exception {
          // Fire "body" events for all relevant rules
          boolean debug = log.isDebugEnabled();
          String match = digester.getMatch();
          List rules = digester.getRules().match(namespace, match);
          Iterator ri = rules.iterator();
          while (ri.hasNext()) {
              Rule rule = (Rule) ri.next();
              if (debug) {
                  log.debug("  Fire body() for " + rule);
              }
              rule.body(namespace, name, text);
          }
      }
      
      /**
       * Here we act like Digester.end.
       */
      public void delegateEnd(String namespace, String name)
      throws Exception {
          // Fire "end" events for all relevant rules in reverse order
          boolean debug = log.isDebugEnabled();
          String match = digester.getMatch();
          List rules = digester.getRules().match(namespace, match);
          ListIterator ri = rules.listIterator();
          while (ri.hasNext()) {
              ri.next();
          }
          
          while (ri.hasPrevious()) {
              Rule rule = (Rule) ri.previous();
              if (debug) {
                  log.debug("  Fire end() for " + rule);
              }
              rule.end(namespace, name);
          }
      }
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to