001    /* $Id: PluginRules.java 729122 2008-12-23 21:14:13Z rahul $
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     */ 
018    package org.apache.commons.digester.plugins;
019    
020    import java.util.List;
021    
022    import org.apache.commons.digester.Digester;
023    import org.apache.commons.digester.Rule;
024    import org.apache.commons.digester.Rules;
025    import org.apache.commons.digester.RulesBase;
026    import 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    
052    public 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                if (!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            
307            decoratedRules.add(pattern, rule);
308    
309            if (rule instanceof InitializableRule) {
310                try {
311                    ((InitializableRule)rule).postRegisterInit(pattern);
312                } catch (PluginConfigurationException e) {
313                    // Currently, Digester doesn't handle exceptions well
314                    // from the add method. The workaround is for the
315                    // initialisable rule to remember that its initialisation
316                    // failed, and to throw the exception when begin is
317                    // called for the first time.
318                    if (debug) {
319                        log.debug("Rule initialisation failed", e);
320                    }
321                    // throw e; -- alas, can't do this
322                    return;
323                }
324            }
325            
326            if (debug) {
327                log.debug("add exit" + ": mapped pattern [" + pattern + "]" + 
328                      " to rule of type [" + rule.getClass().getName() + "]");
329            }
330        }
331    
332        /**
333         * Clear all rules.
334         */
335        public void clear() {
336            decoratedRules.clear();
337        }
338        
339        /**
340         * Return a List of all registered Rule instances that match the specified
341         * nesting pattern, or a zero-length List if there are no matches.  If more
342         * than one Rule instance matches, they <strong>must</strong> be returned
343         * in the order originally registered through the <code>add()</code>
344         * method.
345         *
346         * @param path the path to the xml nodes to be matched.
347         *
348         * @deprecated Call match(namespaceURI,pattern) instead.
349         */
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    }