View Javadoc

1   package org.apache.commons.digester3.plugins;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.List;
23  
24  import org.apache.commons.digester3.Digester;
25  import org.apache.commons.digester3.Rule;
26  import org.apache.commons.digester3.Rules;
27  import org.apache.commons.digester3.RulesBase;
28  import org.apache.commons.logging.Log;
29  import org.xml.sax.Attributes;
30  
31  /**
32   * A custom digester Rules manager which must be used as the Rules object when using the plugins module functionality.
33   * <p>
34   * During parsing, a linked list of PluginCreateRule instances develop, and this list also acts like a stack. The
35   * original instance that was set before the Digester started parsing is always at the tail of the list, and the
36   * Digester always holds a reference to the instance at the head of the list in the rules member. Initially, this
37   * list/stack holds just one instance, ie head and tail are the same object.
38   * <p>
39   * When the start of an xml element causes a PluginCreateRule to fire, a new PluginRules instance is created and
40   * inserted at the head of the list (ie pushed onto the stack of Rules objects). Digester.getRules() therefore returns
41   * this new Rules object, and any custom rules associated with that plugin are added to that instance.
42   * <p>
43   * When the end of the xml element is encountered (and therefore the PluginCreateRule end method fires), the stack of
44   * Rules objects is popped, so that Digester.getRules returns the previous Rules object.
45   * 
46   * @since 1.6
47   */
48  public class PluginRules
49      implements Rules
50  {
51  
52      /**
53       * The Digester instance with which this Rules instance is associated.
54       */
55      protected Digester digester = null;
56  
57      /**
58       * The (optional) object which generates new rules instances.
59       */
60      private RulesFactory rulesFactory;
61  
62      /**
63       * The rules implementation that we are "enhancing" with plugins functionality, as per the Decorator pattern.
64       */
65      private Rules decoratedRules;
66  
67      /** Object which contains information about all known plugins. */
68      private PluginManager pluginManager;
69  
70      /**
71       * The path below which this rules object has responsibility. For paths shorter than or equal the mountpoint, the
72       * parent's match is called.
73       */
74      private String mountPoint = null;
75  
76      /**
77       * The Rules object that holds rules applying "above" the mountpoint, ie the next Rules object down in the stack.
78       */
79      private PluginRules parent = null;
80  
81      /**
82       * A reference to the object that holds all data which should only exist once per digester instance.
83       */
84      private PluginContext pluginContext = null;
85  
86      // ------------------------------------------------------------- Constructor
87  
88      /**
89       * Constructor for top-level Rules objects. Exactly one of these must be created and installed into the Digester
90       * instance as the Rules object before parsing starts.
91       */
92      public PluginRules()
93      {
94          this( new RulesBase() );
95      }
96  
97      /**
98       * Constructor for top-level Rules object which handles rule-matching using the specified implementation.
99       *
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 }