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