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 }