001    package 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    
022    import java.util.List;
023    
024    import org.apache.commons.digester3.Digester;
025    import org.apache.commons.digester3.Rule;
026    import org.apache.commons.digester3.Rules;
027    import org.apache.commons.digester3.RulesBase;
028    import org.apache.commons.logging.Log;
029    import 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     */
048    public 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    }