blob: a0c31d1e9f3e71162237579d1c1020a6725ba642 [file] [log] [blame]
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.Digester;
import org.apache.commons.digester3.Rule;
import org.apache.commons.digester3.Rules;
import org.apache.commons.digester3.RulesBase;
import org.apache.commons.logging.Log;
import org.xml.sax.Attributes;
/**
* A custom digester Rules manager which must be used as the Rules object when using the plugins module functionality.
* <p>
* During parsing, a linked list of PluginCreateRule instances develop, and this list also acts like a stack. The
* original instance that was set before the Digester started parsing is always at the tail of the list, and the
* Digester always holds a reference to the instance at the head of the list in the rules member. Initially, this
* list/stack holds just one instance, ie head and tail are the same object.
* <p>
* When the start of an xml element causes a PluginCreateRule to fire, a new PluginRules instance is created and
* inserted at the head of the list (ie pushed onto the stack of Rules objects). Digester.getRules() therefore returns
* this new Rules object, and any custom rules associated with that plugin are added to that instance.
* <p>
* When the end of the xml element is encountered (and therefore the PluginCreateRule end method fires), the stack of
* Rules objects is popped, so that Digester.getRules returns the previous Rules object.
*
* @since 1.6
*/
public class PluginRules
implements Rules
{
/**
* The Digester instance with which this Rules instance is associated.
*/
protected Digester digester = null;
/**
* The (optional) object which generates new rules instances.
*/
private RulesFactory rulesFactory;
/**
* The rules implementation that we are "enhancing" with plugins functionality, as per the Decorator pattern.
*/
private final Rules decoratedRules;
/** Object which contains information about all known plugins. */
private final PluginManager pluginManager;
/**
* The path below which this rules object has responsibility. For paths shorter than or equal the mountpoint, the
* parent's match is called.
*/
private String mountPoint = null;
/**
* The Rules object that holds rules applying "above" the mountpoint, ie the next Rules object down in the stack.
*/
private PluginRules parent = null;
/**
* A reference to the object that holds all data which should only exist once per digester instance.
*/
private PluginContext pluginContext = null;
// ------------------------------------------------------------- Constructor
/**
* Constructor for top-level Rules objects. Exactly one of these must be created and installed into the Digester
* instance as the Rules object before parsing starts.
*/
public PluginRules()
{
this( new RulesBase() );
}
/**
* Constructor for top-level Rules object which handles rule-matching using the specified implementation.
*
* @param decoratedRules The top-level Rules object which handles rule-matching using the specified implementation.
*/
public PluginRules( final Rules decoratedRules )
{
this.decoratedRules = decoratedRules;
pluginContext = new PluginContext();
pluginManager = new PluginManager( pluginContext );
}
/**
* Constructs a Rules instance which has a parent Rules object (which is different from having a delegate rules
* object).
* <p>
* One of these is created each time a PluginCreateRule's begin method fires, in order to manage the custom rules
* associated with whatever concrete plugin class the user has specified.
*
* @param digester is the object this rules will be associated with.
* @param mountPoint is the digester match path for the element matching a PluginCreateRule which caused this
* "nested parsing scope" to begin. This is expected to be equal to digester.getMatch().
* @param parent must be non-null.
* @param pluginClass is the plugin class whose custom rules will be loaded into this new PluginRules object.
* @throws PluginException if any error occurs
*/
PluginRules( final Digester digester, final String mountPoint, final PluginRules parent, final Class<?> pluginClass )
throws PluginException
{
// no need to set digester or decoratedRules.digester,
// because when Digester.setRules is called, the setDigester
// method on this object will be called.
this.digester = digester;
this.mountPoint = mountPoint;
this.parent = parent;
this.rulesFactory = parent.rulesFactory;
if ( rulesFactory == null )
{
decoratedRules = new RulesBase();
}
else
{
decoratedRules = rulesFactory.newRules( digester, pluginClass );
}
pluginContext = parent.pluginContext;
pluginManager = new PluginManager( parent.pluginManager );
}
// ------------------------------------------------------------- Properties
/**
* Return the parent Rules object.
*
* @return the parent Rules object.
*/
public Rules getParent()
{
return parent;
}
/**
* Return the Digester instance with which this instance is associated.
*
* @return the Digester instance with which this instance is associated.
*/
@Override
public Digester getDigester()
{
return digester;
}
/**
* Set the Digester instance with which this Rules instance is associated.
*
* @param digester The newly associated Digester instance
*/
@Override
public void setDigester( final Digester digester )
{
this.digester = digester;
decoratedRules.setDigester( digester );
}
/**
* Return the namespace URI that will be applied to all subsequently added <code>Rule</code> objects.
*
* @return the namespace URI that will be applied to all subsequently added <code>Rule</code> objects.
*/
@Override
public String getNamespaceURI()
{
return decoratedRules.getNamespaceURI();
}
/**
* Set the namespace URI that will be applied to all subsequently added <code>Rule</code> objects.
*
* @param namespaceURI Namespace URI that must match on all subsequently added rules, or <code>null</code> for
* matching regardless of the current namespace URI
*/
@Override
public void setNamespaceURI( final String namespaceURI )
{
decoratedRules.setNamespaceURI( namespaceURI );
}
/**
* Return the object which "knows" about all declared plugins.
*
* @return The pluginManager value
*/
public PluginManager getPluginManager()
{
return pluginManager;
}
/**
* See {@link PluginContext#getRuleFinders}.
*
* @return the list of RuleFinder objects
*/
public List<RuleFinder> getRuleFinders()
{
return pluginContext.getRuleFinders();
}
/**
* See {@link PluginContext#setRuleFinders}.
*
* @param ruleFinders the list of RuleFinder objects
*/
public void setRuleFinders( final List<RuleFinder> ruleFinders )
{
pluginContext.setRuleFinders( ruleFinders );
}
/**
* Return the rules factory object (or null if one has not been specified).
*
* @return the rules factory object.
*/
public RulesFactory getRulesFactory()
{
return rulesFactory;
}
/**
* Set the object which is used to generate the new Rules instances created to hold and process the rules associated
* with each plugged-in class.
*
* @param factory the rules factory object
*/
public void setRulesFactory( final RulesFactory factory )
{
rulesFactory = factory;
}
// --------------------------------------------------------- Public Methods
/**
* This package-scope method is used by the PluginCreateRule class to get direct access to the rules that were
* dynamically added by the plugin. No other class should need access to this object.
*
* @return The decorated Rule instance
*/
Rules getDecoratedRules()
{
return decoratedRules;
}
/**
* Return the list of rules registered with this object, in the order they were registered with this object.
* <p>
* Note that Rule objects stored in parent Rules objects are not returned by this method.
*
* @return list of all Rule objects known to this Rules instance.
*/
@Override
public List<Rule> rules()
{
return decoratedRules.rules();
}
/**
* Register a new Rule instance matching the specified pattern.
*
* @param pattern Nesting pattern to be matched for this Rule. This parameter treats equally patterns that begin
* with and without a leading slash ('/').
* @param rule Rule instance to be registered
*/
@Override
public void add( String pattern, final Rule rule )
{
final Log log = LogUtils.getLogger( digester );
final boolean debug = log.isDebugEnabled();
if ( debug )
{
log.debug( "add entry" + ": mapping pattern [" + pattern + "]" + " to rule of type ["
+ rule.getClass().getName() + "]" );
}
// allow patterns with a leading slash character
if ( pattern.startsWith( "/" ) )
{
pattern = pattern.substring( 1 );
}
if ( mountPoint != null && !pattern.equals( mountPoint ) && !pattern.startsWith( mountPoint + "/" ) )
{
// This can only occur if a plugin attempts to add a
// rule with a pattern that doesn't start with the
// prefix passed to the addRules method. Plugins mustn't
// add rules outside the scope of the tag they were specified
// on, so refuse this.
// alas, can't throw exception
log.warn( "An attempt was made to add a rule with a pattern that"
+ "is not at or below the mountpoint of the current" + " PluginRules object." + " Rule pattern: "
+ pattern + ", mountpoint: " + mountPoint + ", rule type: " + rule.getClass().getName() );
return;
}
decoratedRules.add( pattern, rule );
if ( rule instanceof InitializableRule )
{
try
{
( (InitializableRule) rule ).postRegisterInit( pattern );
}
catch ( final PluginConfigurationException e )
{
// Currently, Digester doesn't handle exceptions well
// from the add method. The workaround is for the
// initialisable rule to remember that its initialisation
// failed, and to throw the exception when begin is
// called for the first time.
if ( debug )
{
log.debug( "Rule initialisation failed", e );
}
// throw e; -- alas, can't do this
return;
}
}
if ( debug )
{
log.debug( "add exit" + ": mapped pattern [" + pattern + "]" + " to rule of type ["
+ rule.getClass().getName() + "]" );
}
}
/**
* Clear all rules.
*/
@Override
public void clear()
{
decoratedRules.clear();
}
/**
* {@inheritDoc}
*/
@Override
public List<Rule> match( final String namespaceURI, final String path, final String name, final Attributes attributes )
{
final Log log = LogUtils.getLogger( digester );
final boolean debug = log.isDebugEnabled();
if ( debug )
{
log.debug( "Matching path [" + path + "] on rules object " + this.toString() );
}
List<Rule> matches;
if ( ( mountPoint != null ) && ( path.length() <= mountPoint.length() ) )
{
if ( debug )
{
log.debug( "Path [" + path + "] delegated to parent." );
}
matches = parent.match( namespaceURI, path, name, attributes );
// Note that in the case where path equals mountPoint,
// we deliberately return only the rules from the parent,
// even though this object may hold some rules matching
// this same path. See PluginCreateRule's begin, body and end
// methods for the reason.
}
else
{
log.debug( "delegating to decorated rules." );
matches = decoratedRules.match( namespaceURI, path, name, attributes );
}
return matches;
}
/**
* See {@link PluginContext#setPluginClassAttribute}.
*
* @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 )
{
pluginContext.setPluginClassAttribute( namespaceUri, attrName );
}
/**
* See {@link PluginContext#setPluginIdAttribute}.
*
* @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 )
{
pluginContext.setPluginIdAttribute( namespaceUri, attrName );
}
/**
* See {@link PluginContext#getPluginClassAttrNs}.
*
* @return the namespace for the xml attribute which indicates which class is to be plugged in.
*/
public String getPluginClassAttrNs()
{
return pluginContext.getPluginClassAttrNs();
}
/**
* See {@link PluginContext#getPluginClassAttr}.
*
* @return the namespace for the xml attribute which indicates which class is to be plugged in.
*/
public String getPluginClassAttr()
{
return pluginContext.getPluginClassAttr();
}
/**
* See {@link PluginContext#getPluginIdAttrNs}.
*
* @return the namespace for the xml attribute which indicates which previous plugin declaration should be used.
*/
public String getPluginIdAttrNs()
{
return pluginContext.getPluginIdAttrNs();
}
/**
* See {@link PluginContext#getPluginIdAttr}.
*
* @return the namespace for the xml attribute which indicates which previous plugin declaration should be used.
*/
public String getPluginIdAttr()
{
return pluginContext.getPluginIdAttr();
}
}