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 }