blob: bbb9d2c48a150c28b7be074397455b9c5814cac0 [file] [log] [blame]
/* $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;
}
}
}
}
}