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 }