001package org.apache.commons.digester3.plugins;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.List;
023
024import org.apache.commons.digester3.Digester;
025import org.apache.commons.digester3.Rule;
026import org.apache.commons.digester3.Rules;
027import org.apache.commons.digester3.RulesBase;
028import org.apache.commons.logging.Log;
029import org.xml.sax.Attributes;
030
031/**
032 * A custom digester Rules manager which must be used as the Rules object when using the plugins module functionality.
033 * <p>
034 * During parsing, a linked list of PluginCreateRule instances develop, and this list also acts like a stack. The
035 * original instance that was set before the Digester started parsing is always at the tail of the list, and the
036 * Digester always holds a reference to the instance at the head of the list in the rules member. Initially, this
037 * list/stack holds just one instance, ie head and tail are the same object.
038 * <p>
039 * When the start of an xml element causes a PluginCreateRule to fire, a new PluginRules instance is created and
040 * inserted at the head of the list (ie pushed onto the stack of Rules objects). Digester.getRules() therefore returns
041 * this new Rules object, and any custom rules associated with that plugin are added to that instance.
042 * <p>
043 * When the end of the xml element is encountered (and therefore the PluginCreateRule end method fires), the stack of
044 * Rules objects is popped, so that Digester.getRules returns the previous Rules object.
045 * 
046 * @since 1.6
047 */
048public class PluginRules
049    implements Rules
050{
051
052    /**
053     * The Digester instance with which this Rules instance is associated.
054     */
055    protected Digester digester = null;
056
057    /**
058     * The (optional) object which generates new rules instances.
059     */
060    private RulesFactory rulesFactory;
061
062    /**
063     * The rules implementation that we are "enhancing" with plugins functionality, as per the Decorator pattern.
064     */
065    private Rules decoratedRules;
066
067    /** Object which contains information about all known plugins. */
068    private PluginManager pluginManager;
069
070    /**
071     * The path below which this rules object has responsibility. For paths shorter than or equal the mountpoint, the
072     * parent's match is called.
073     */
074    private String mountPoint = null;
075
076    /**
077     * The Rules object that holds rules applying "above" the mountpoint, ie the next Rules object down in the stack.
078     */
079    private PluginRules parent = null;
080
081    /**
082     * A reference to the object that holds all data which should only exist once per digester instance.
083     */
084    private PluginContext pluginContext = null;
085
086    // ------------------------------------------------------------- Constructor
087
088    /**
089     * Constructor for top-level Rules objects. Exactly one of these must be created and installed into the Digester
090     * instance as the Rules object before parsing starts.
091     */
092    public PluginRules()
093    {
094        this( new RulesBase() );
095    }
096
097    /**
098     * Constructor for top-level Rules object which handles rule-matching using the specified implementation.
099     *
100     * @param decoratedRules The top-level Rules object which handles rule-matching using the specified implementation.
101     */
102    public PluginRules( Rules decoratedRules )
103    {
104        this.decoratedRules = decoratedRules;
105
106        pluginContext = new PluginContext();
107        pluginManager = new PluginManager( pluginContext );
108    }
109
110    /**
111     * Constructs a Rules instance which has a parent Rules object (which is different from having a delegate rules
112     * object).
113     * <p>
114     * One of these is created each time a PluginCreateRule's begin method fires, in order to manage the custom rules
115     * associated with whatever concrete plugin class the user has specified.
116     * 
117     * @param digester is the object this rules will be associated with.
118     * @param mountPoint is the digester match path for the element matching a PluginCreateRule which caused this
119     *            "nested parsing scope" to begin. This is expected to be equal to digester.getMatch().
120     * @param parent must be non-null.
121     * @param pluginClass is the plugin class whose custom rules will be loaded into this new PluginRules object.
122     * @throws PluginException if any error occurs
123     */
124    PluginRules( Digester digester, String mountPoint, PluginRules parent, Class<?> pluginClass )
125        throws PluginException
126    {
127        // no need to set digester or decoratedRules.digester,
128        // because when Digester.setRules is called, the setDigester
129        // method on this object will be called.
130
131        this.digester = digester;
132        this.mountPoint = mountPoint;
133        this.parent = parent;
134        this.rulesFactory = parent.rulesFactory;
135
136        if ( rulesFactory == null )
137        {
138            decoratedRules = new RulesBase();
139        }
140        else
141        {
142            decoratedRules = rulesFactory.newRules( digester, pluginClass );
143        }
144
145        pluginContext = parent.pluginContext;
146        pluginManager = new PluginManager( parent.pluginManager );
147    }
148
149    // ------------------------------------------------------------- Properties
150
151    /**
152     * Return the parent Rules object.
153     *
154     * @return the parent Rules object.
155     */
156    public Rules getParent()
157    {
158        return parent;
159    }
160
161    /**
162     * Return the Digester instance with which this instance is associated.
163     *
164     * @return the Digester instance with which this instance is associated.
165     */
166    public Digester getDigester()
167    {
168        return digester;
169    }
170
171    /**
172     * Set the Digester instance with which this Rules instance is associated.
173     * 
174     * @param digester The newly associated Digester instance
175     */
176    public void setDigester( Digester digester )
177    {
178        this.digester = digester;
179        decoratedRules.setDigester( digester );
180    }
181
182    /**
183     * Return the namespace URI that will be applied to all subsequently added <code>Rule</code> objects.
184     *
185     * @return the namespace URI that will be applied to all subsequently added <code>Rule</code> objects.
186     */
187    public String getNamespaceURI()
188    {
189        return decoratedRules.getNamespaceURI();
190    }
191
192    /**
193     * Set the namespace URI that will be applied to all subsequently added <code>Rule</code> objects.
194     * 
195     * @param namespaceURI Namespace URI that must match on all subsequently added rules, or <code>null</code> for
196     *            matching regardless of the current namespace URI
197     */
198    public void setNamespaceURI( String namespaceURI )
199    {
200        decoratedRules.setNamespaceURI( namespaceURI );
201    }
202
203    /**
204     * Return the object which "knows" about all declared plugins.
205     * 
206     * @return The pluginManager value
207     */
208    public PluginManager getPluginManager()
209    {
210        return pluginManager;
211    }
212
213    /**
214     * See {@link PluginContext#getRuleFinders}.
215     *
216     * @return the list of RuleFinder objects
217     */
218    public List<RuleFinder> getRuleFinders()
219    {
220        return pluginContext.getRuleFinders();
221    }
222
223    /**
224     * See {@link PluginContext#setRuleFinders}.
225     *
226     * @param ruleFinders the list of RuleFinder objects
227     */
228    public void setRuleFinders( List<RuleFinder> ruleFinders )
229    {
230        pluginContext.setRuleFinders( ruleFinders );
231    }
232
233    /**
234     * Return the rules factory object (or null if one has not been specified).
235     *
236     * @return the rules factory object.
237     */
238    public RulesFactory getRulesFactory()
239    {
240        return rulesFactory;
241    }
242
243    /**
244     * Set the object which is used to generate the new Rules instances created to hold and process the rules associated
245     * with each plugged-in class.
246     *
247     * @param factory the rules factory object
248     */
249    public void setRulesFactory( RulesFactory factory )
250    {
251        rulesFactory = factory;
252    }
253
254    // --------------------------------------------------------- Public Methods
255
256    /**
257     * This package-scope method is used by the PluginCreateRule class to get direct access to the rules that were
258     * dynamically added by the plugin. No other class should need access to this object.
259     *
260     * @return The decorated Rule instance
261     */
262    Rules getDecoratedRules()
263    {
264        return decoratedRules;
265    }
266
267    /**
268     * Return the list of rules registered with this object, in the order they were registered with this object.
269     * <p>
270     * Note that Rule objects stored in parent Rules objects are not returned by this method.
271     * 
272     * @return list of all Rule objects known to this Rules instance.
273     */
274    public List<Rule> rules()
275    {
276        return decoratedRules.rules();
277    }
278
279    /**
280     * Register a new Rule instance matching the specified pattern.
281     * 
282     * @param pattern Nesting pattern to be matched for this Rule. This parameter treats equally patterns that begin
283     *            with and without a leading slash ('/').
284     * @param rule Rule instance to be registered
285     */
286    public void add( String pattern, Rule rule )
287    {
288        Log log = LogUtils.getLogger( digester );
289        boolean debug = log.isDebugEnabled();
290
291        if ( debug )
292        {
293            log.debug( "add entry" + ": mapping pattern [" + pattern + "]" + " to rule of type ["
294                + rule.getClass().getName() + "]" );
295        }
296
297        // allow patterns with a leading slash character
298        if ( pattern.startsWith( "/" ) )
299        {
300            pattern = pattern.substring( 1 );
301        }
302
303        if ( mountPoint != null && !pattern.equals( mountPoint ) && !pattern.startsWith( mountPoint + "/" ) )
304        {
305            // This can only occur if a plugin attempts to add a
306            // rule with a pattern that doesn't start with the
307            // prefix passed to the addRules method. Plugins mustn't
308            // add rules outside the scope of the tag they were specified
309            // on, so refuse this.
310
311            // alas, can't throw exception
312            log.warn( "An attempt was made to add a rule with a pattern that"
313                + "is not at or below the mountpoint of the current" + " PluginRules object." + " Rule pattern: "
314                + pattern + ", mountpoint: " + mountPoint + ", rule type: " + rule.getClass().getName() );
315            return;
316        }
317
318        decoratedRules.add( pattern, rule );
319
320        if ( rule instanceof InitializableRule )
321        {
322            try
323            {
324                ( (InitializableRule) rule ).postRegisterInit( pattern );
325            }
326            catch ( PluginConfigurationException e )
327            {
328                // Currently, Digester doesn't handle exceptions well
329                // from the add method. The workaround is for the
330                // initialisable rule to remember that its initialisation
331                // failed, and to throw the exception when begin is
332                // called for the first time.
333                if ( debug )
334                {
335                    log.debug( "Rule initialisation failed", e );
336                }
337                // throw e; -- alas, can't do this
338                return;
339            }
340        }
341
342        if ( debug )
343        {
344            log.debug( "add exit" + ": mapped pattern [" + pattern + "]" + " to rule of type ["
345                + rule.getClass().getName() + "]" );
346        }
347    }
348
349    /**
350     * Clear all rules.
351     */
352    public void clear()
353    {
354        decoratedRules.clear();
355    }
356
357    /**
358     * {@inheritDoc}
359     */
360    public List<Rule> match( String namespaceURI, String path, String name, Attributes attributes )
361    {
362        Log log = LogUtils.getLogger( digester );
363        boolean debug = log.isDebugEnabled();
364
365        if ( debug )
366        {
367            log.debug( "Matching path [" + path + "] on rules object " + this.toString() );
368        }
369
370        List<Rule> matches;
371        if ( ( mountPoint != null ) && ( path.length() <= mountPoint.length() ) )
372        {
373            if ( debug )
374            {
375                log.debug( "Path [" + path + "] delegated to parent." );
376            }
377
378            matches = parent.match( namespaceURI, path, name, attributes );
379
380            // Note that in the case where path equals mountPoint,
381            // we deliberately return only the rules from the parent,
382            // even though this object may hold some rules matching
383            // this same path. See PluginCreateRule's begin, body and end
384            // methods for the reason.
385        }
386        else
387        {
388            log.debug( "delegating to decorated rules." );
389            matches = decoratedRules.match( namespaceURI, path, name, attributes );
390        }
391
392        return matches;
393    }
394
395    /**
396     * See {@link PluginContext#setPluginClassAttribute}.
397     *
398     * @param namespaceUri is the namespace uri that the specified attribute is in. If the attribute is in no namespace,
399     *            then this should be null. Note that if a namespace is used, the attrName value should <i>not</i>
400     *            contain any kind of namespace-prefix. Note also that if you are using a non-namespace-aware parser,
401     *            this parameter <i>must</i> be null.
402     * @param attrName is the attribute whose value contains the name of the class to be instantiated.
403     * */
404    public void setPluginClassAttribute( String namespaceUri, String attrName )
405    {
406        pluginContext.setPluginClassAttribute( namespaceUri, attrName );
407    }
408
409    /**
410     * See {@link PluginContext#setPluginIdAttribute}.
411     * 
412     * @param namespaceUri is the namespace uri that the specified attribute is in. If the attribute is in no namespace,
413     *            then this should be null. Note that if a namespace is used, the attrName value should <i>not</i>
414     *            contain any kind of namespace-prefix. Note also that if you are using a non-namespace-aware parser,
415     *            this parameter <i>must</i> be null.
416     * @param attrName is the attribute whose value contains the id of the plugin declaration to be used when
417     *            instantiating an object.
418     **/
419    public void setPluginIdAttribute( String namespaceUri, String attrName )
420    {
421        pluginContext.setPluginIdAttribute( namespaceUri, attrName );
422    }
423
424    /**
425     * See {@link PluginContext#getPluginClassAttrNs}.
426     *
427     * @return the namespace for the xml attribute which indicates which class is to be plugged in.
428     */
429    public String getPluginClassAttrNs()
430    {
431        return pluginContext.getPluginClassAttrNs();
432    }
433
434    /**
435     * See {@link PluginContext#getPluginClassAttr}.
436     *
437     * @return the namespace for the xml attribute which indicates which class is to be plugged in.
438     */
439    public String getPluginClassAttr()
440    {
441        return pluginContext.getPluginClassAttr();
442    }
443
444    /**
445     * See {@link PluginContext#getPluginIdAttrNs}.
446     *
447     * @return the namespace for the xml attribute which indicates which previous plugin declaration should be used.
448     */
449    public String getPluginIdAttrNs()
450    {
451        return pluginContext.getPluginIdAttrNs();
452    }
453
454    /**
455     * See {@link PluginContext#getPluginIdAttr}.
456     *
457     * @return the namespace for the xml attribute which indicates which previous plugin declaration should be used.
458     */
459    public String getPluginIdAttr()
460    {
461        return pluginContext.getPluginIdAttr();
462    }
463
464}