001 /* $Id: PluginCreateRule.java 471661 2006-11-06 08:09:25Z skitching $ 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 019 package org.apache.commons.digester.plugins; 020 021 import java.util.List; 022 023 import org.apache.commons.digester.Rule; 024 import org.apache.commons.logging.Log; 025 026 /** 027 * Allows the original rules for parsing the configuration file to define 028 * points at which plugins are allowed, by configuring a PluginCreateRule 029 * with the appropriate pattern. 030 * 031 * @since 1.6 032 */ 033 public class PluginCreateRule extends Rule implements InitializableRule { 034 035 // see setPluginClassAttribute 036 private String pluginClassAttrNs = null; 037 private String pluginClassAttr = null; 038 039 // see setPluginIdAttribute 040 private String pluginIdAttrNs = null; 041 private String pluginIdAttr = null; 042 043 /** 044 * In order to invoke the addRules method on the plugin class correctly, 045 * we need to know the pattern which this rule is matched by. 046 */ 047 private String pattern; 048 049 /** A base class that any plugin must derive from. */ 050 private Class baseClass = null; 051 052 /** 053 * Info about optional default plugin to be used if no plugin-id is 054 * specified in the input data. This can simplify the syntax where one 055 * particular plugin is usually used. 056 */ 057 private Declaration defaultPlugin; 058 059 /** 060 * Currently, none of the Rules methods allow exceptions to be thrown. 061 * Therefore if this class cannot initialise itself properly, it cannot 062 * cause the digester to stop. Instead, we cache the exception and throw 063 * it the first time the begin() method is called. 064 */ 065 private PluginConfigurationException initException; 066 067 //-------------------- constructors ------------------------------------- 068 069 /** 070 * Create a plugin rule where the user <i>must</i> specify a plugin-class 071 * or plugin-id. 072 * 073 * @param baseClass is the class which any specified plugin <i>must</i> be 074 * descended from. 075 */ 076 public PluginCreateRule(Class baseClass) { 077 this.baseClass = baseClass; 078 } 079 080 /** 081 * Create a plugin rule where the user <i>may</i> specify a plugin. 082 * If the user doesn't specify a plugin, then the default class specified 083 * in this constructor is used. 084 * 085 * @param baseClass is the class which any specified plugin <i>must</i> be 086 * descended from. 087 * @param dfltPluginClass is the class which will be used if the user 088 * doesn't specify any plugin-class or plugin-id. This class will have 089 * custom rules installed for it just like a declared plugin. 090 */ 091 public PluginCreateRule(Class baseClass, Class dfltPluginClass) { 092 this.baseClass = baseClass; 093 if (dfltPluginClass != null) { 094 defaultPlugin = new Declaration(dfltPluginClass); 095 } 096 } 097 098 /** 099 * Create a plugin rule where the user <i>may</i> specify a plugin. 100 * If the user doesn't specify a plugin, then the default class specified 101 * in this constructor is used. 102 * 103 * @param baseClass is the class which any specified plugin <i>must</i> be 104 * descended from. 105 * @param dfltPluginClass is the class which will be used if the user 106 * doesn't specify any plugin-class or plugin-id. This class will have 107 * custom rules installed for it just like a declared plugin. 108 * @param dfltPluginRuleLoader is a RuleLoader instance which knows how 109 * to load the custom rules associated with this default plugin. 110 */ 111 public PluginCreateRule(Class baseClass, Class dfltPluginClass, 112 RuleLoader dfltPluginRuleLoader) { 113 114 this.baseClass = baseClass; 115 if (dfltPluginClass != null) { 116 defaultPlugin = 117 new Declaration(dfltPluginClass, dfltPluginRuleLoader); 118 } 119 } 120 121 //------------------- properties --------------------------------------- 122 123 /** 124 * Sets the xml attribute which the input xml uses to indicate to a 125 * PluginCreateRule which class should be instantiated. 126 * <p> 127 * See {@link PluginRules#setPluginClassAttribute} for more info. 128 */ 129 public void setPluginClassAttribute(String namespaceUri, String attrName) { 130 pluginClassAttrNs = namespaceUri; 131 pluginClassAttr = attrName; 132 } 133 134 /** 135 * Sets the xml attribute which the input xml uses to indicate to a 136 * PluginCreateRule which plugin declaration is being referenced. 137 * <p> 138 * See {@link PluginRules#setPluginIdAttribute} for more info. 139 */ 140 public void setPluginIdAttribute(String namespaceUri, String attrName) { 141 pluginIdAttrNs = namespaceUri; 142 pluginIdAttr = attrName; 143 } 144 145 //------------------- methods -------------------------------------------- 146 147 /** 148 * Invoked after this rule has been added to the set of digester rules, 149 * associated with the specified pattern. Check all configuration data is 150 * valid and remember the pattern for later. 151 * 152 * @param matchPattern is the digester match pattern that is associated 153 * with this rule instance, eg "root/widget". 154 * @exception PluginConfigurationException 155 */ 156 public void postRegisterInit(String matchPattern) 157 throws PluginConfigurationException { 158 Log log = LogUtils.getLogger(digester); 159 boolean debug = log.isDebugEnabled(); 160 if (debug) { 161 log.debug("PluginCreateRule.postRegisterInit" + 162 ": rule registered for pattern [" + matchPattern + "]"); 163 } 164 165 if (digester == null) { 166 // We require setDigester to be called before this method. 167 // Note that this means that PluginCreateRule cannot be added 168 // to a Rules object which has not yet been added to a 169 // Digester object. 170 initException = new PluginConfigurationException( 171 "Invalid invocation of postRegisterInit" + 172 ": digester not set."); 173 throw initException; 174 } 175 176 if (pattern != null) { 177 // We have been called twice, ie a single instance has been 178 // associated with multiple patterns. 179 // 180 // Generally, Digester Rule instances can be associated with 181 // multiple patterns. However for plugins, this creates some 182 // complications. Some day this may be supported; however for 183 // now we just reject this situation. 184 initException = new PluginConfigurationException( 185 "A single PluginCreateRule instance has been mapped to" + 186 " multiple patterns; this is not supported."); 187 throw initException; 188 } 189 190 if (matchPattern.indexOf('*') != -1) { 191 // having wildcards in patterns is extremely difficult to 192 // deal with. For now, we refuse to allow this. 193 // 194 // TODO: check for any chars not valid in xml element name 195 // rather than just *. 196 // 197 // Reasons include: 198 // (a) handling recursive plugins, and 199 // (b) determining whether one pattern is "below" another, 200 // as done by PluginRules. Without wildcards, "below" 201 // just means startsWith, which is easy to check. 202 initException = new PluginConfigurationException( 203 "A PluginCreateRule instance has been mapped to" + 204 " pattern [" + matchPattern + "]." + 205 " This pattern includes a wildcard character." + 206 " This is not supported by the plugin architecture."); 207 throw initException; 208 } 209 210 if (baseClass == null) { 211 baseClass = Object.class; 212 } 213 214 PluginRules rules = (PluginRules) digester.getRules(); 215 PluginManager pm = rules.getPluginManager(); 216 217 // check default class is valid 218 if (defaultPlugin != null) { 219 if (!baseClass.isAssignableFrom(defaultPlugin.getPluginClass())) { 220 initException = new PluginConfigurationException( 221 "Default class [" + 222 defaultPlugin.getPluginClass().getName() + 223 "] does not inherit from [" + 224 baseClass.getName() + "]."); 225 throw initException; 226 } 227 228 try { 229 defaultPlugin.init(digester, pm); 230 231 } catch(PluginException pwe) { 232 233 throw new PluginConfigurationException( 234 pwe.getMessage(), pwe.getCause()); 235 } 236 } 237 238 // remember the pattern for later 239 pattern = matchPattern; 240 241 if (pluginClassAttr == null) { 242 // the user hasn't set explicit xml attr names on this rule, 243 // so fetch the default values 244 pluginClassAttrNs = rules.getPluginClassAttrNs(); 245 pluginClassAttr = rules.getPluginClassAttr(); 246 247 if (debug) { 248 log.debug( 249 "init: pluginClassAttr set to per-digester values [" 250 + "ns=" + pluginClassAttrNs 251 + ", name=" + pluginClassAttr + "]"); 252 } 253 } else { 254 if (debug) { 255 log.debug( 256 "init: pluginClassAttr set to rule-specific values [" 257 + "ns=" + pluginClassAttrNs 258 + ", name=" + pluginClassAttr + "]"); 259 } 260 } 261 262 if (pluginIdAttr == null) { 263 // the user hasn't set explicit xml attr names on this rule, 264 // so fetch the default values 265 pluginIdAttrNs = rules.getPluginIdAttrNs(); 266 pluginIdAttr = rules.getPluginIdAttr(); 267 268 if (debug) { 269 log.debug( 270 "init: pluginIdAttr set to per-digester values [" 271 + "ns=" + pluginIdAttrNs 272 + ", name=" + pluginIdAttr + "]"); 273 } 274 } else { 275 if (debug) { 276 log.debug( 277 "init: pluginIdAttr set to rule-specific values [" 278 + "ns=" + pluginIdAttrNs 279 + ", name=" + pluginIdAttr + "]"); 280 } 281 } 282 } 283 284 /** 285 * Invoked when the Digester matches this rule against an xml element. 286 * <p> 287 * A new instance of the target class is created, and pushed onto the 288 * stack. A new "private" PluginRules object is then created and set as 289 * the digester's default Rules object. Any custom rules associated with 290 * the plugin class are then loaded into that new Rules object. 291 * Finally, any custom rules that are associated with the current pattern 292 * (such as SetPropertiesRules) have their begin methods executed. 293 * 294 * @param namespace 295 * @param name 296 * @param attributes 297 * 298 * @throws ClassNotFoundException 299 * @throws PluginInvalidInputException 300 * @throws PluginConfigurationException 301 */ 302 public void begin(String namespace, String name, 303 org.xml.sax.Attributes attributes) 304 throws java.lang.Exception { 305 Log log = digester.getLogger(); 306 boolean debug = log.isDebugEnabled(); 307 if (debug) { 308 log.debug("PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + 309 " match=[" + digester.getMatch() + "]"); 310 } 311 312 if (initException != null) { 313 // we had a problem during initialisation that we could 314 // not report then; report it now. 315 throw initException; 316 } 317 318 // load any custom rules associated with the plugin 319 PluginRules oldRules = (PluginRules) digester.getRules(); 320 PluginManager pluginManager = oldRules.getPluginManager(); 321 Declaration currDeclaration = null; 322 323 String pluginClassName; 324 if (pluginClassAttrNs == null) { 325 // Yep, this is ugly. 326 // 327 // In a namespace-aware parser, the one-param version will 328 // return attributes with no namespace. 329 // 330 // In a non-namespace-aware parser, the two-param version will 331 // never return any attributes, ever. 332 pluginClassName = attributes.getValue(pluginClassAttr); 333 } else { 334 pluginClassName = 335 attributes.getValue(pluginClassAttrNs, pluginClassAttr); 336 } 337 338 String pluginId; 339 if (pluginIdAttrNs == null) { 340 pluginId = attributes.getValue(pluginIdAttr); 341 } else { 342 pluginId = 343 attributes.getValue(pluginIdAttrNs, pluginIdAttr); 344 } 345 346 if (pluginClassName != null) { 347 // The user is using a plugin "inline", ie without a previous 348 // explicit declaration. If they have used the same plugin class 349 // before, we have already gone to the effort of creating a 350 // Declaration object, so retrieve it. If there is no existing 351 // declaration object for this class, then create one. 352 353 currDeclaration = pluginManager.getDeclarationByClass( 354 pluginClassName); 355 356 if (currDeclaration == null) { 357 currDeclaration = new Declaration(pluginClassName); 358 try { 359 currDeclaration.init(digester, pluginManager); 360 } catch(PluginException pwe) { 361 throw new PluginInvalidInputException( 362 pwe.getMessage(), pwe.getCause()); 363 } 364 pluginManager.addDeclaration(currDeclaration); 365 } 366 } else if (pluginId != null) { 367 currDeclaration = pluginManager.getDeclarationById(pluginId); 368 369 if (currDeclaration == null) { 370 throw new PluginInvalidInputException( 371 "Plugin id [" + pluginId + "] is not defined."); 372 } 373 } else if (defaultPlugin != null) { 374 currDeclaration = defaultPlugin; 375 } else { 376 throw new PluginInvalidInputException( 377 "No plugin class specified for element " + 378 pattern); 379 } 380 381 // get the class of the user plugged-in type 382 Class pluginClass = currDeclaration.getPluginClass(); 383 384 String path = digester.getMatch(); 385 386 // create a new Rules object and effectively push it onto a stack of 387 // rules objects. The stack is actually a linked list; using the 388 // PluginRules constructor below causes the new instance to link 389 // to the previous head-of-stack, then the Digester.setRules() makes 390 // the new instance the new head-of-stack. 391 PluginRules newRules = new PluginRules(digester, path, oldRules, pluginClass); 392 digester.setRules(newRules); 393 394 if (debug) { 395 log.debug("PluginCreateRule.begin: installing new plugin: " + 396 "oldrules=" + oldRules.toString() + 397 ", newrules=" + newRules.toString()); 398 } 399 400 // load up the custom rules 401 currDeclaration.configure(digester, pattern); 402 403 // create an instance of the plugin class 404 Object instance = pluginClass.newInstance(); 405 getDigester().push(instance); 406 if (debug) { 407 log.debug( 408 "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + 409 " match=[" + digester.getMatch() + "]" + 410 " pushed instance of plugin [" + pluginClass.getName() + "]"); 411 } 412 413 // and now we have to fire any custom rules which would have 414 // been matched by the same path that matched this rule, had 415 // they been loaded at that time. 416 List rules = newRules.getDecoratedRules().match(namespace, path); 417 fireBeginMethods(rules, namespace, name, attributes); 418 } 419 420 /** 421 * Process the body text of this element. 422 * 423 * @param text The body text of this element 424 */ 425 public void body(String namespace, String name, String text) 426 throws Exception { 427 428 // While this class itself has no work to do in the body method, 429 // we do need to fire the body methods of all dynamically-added 430 // rules matching the same path as this rule. During begin, we had 431 // to manually execute the dynamic rules' begin methods because they 432 // didn't exist in the digester's Rules object when the match begin. 433 // So in order to ensure consistent ordering of rule execution, the 434 // PluginRules class deliberately avoids returning any such rules 435 // in later calls to the match method, instead relying on this 436 // object to execute them at the appropriate time. 437 // 438 // Note that this applies only to rules matching exactly the path 439 // which is also matched by this PluginCreateRule. 440 441 String path = digester.getMatch(); 442 PluginRules newRules = (PluginRules) digester.getRules(); 443 List rules = newRules.getDecoratedRules().match(namespace, path); 444 fireBodyMethods(rules, namespace, name, text); 445 } 446 447 /** 448 * Invoked by the digester when the closing tag matching this Rule's 449 * pattern is encountered. 450 * </p> 451 * 452 * @param namespace Description of the Parameter 453 * @param name Description of the Parameter 454 * @exception Exception Description of the Exception 455 * 456 * @see #begin 457 */ 458 public void end(String namespace, String name) 459 throws Exception { 460 461 462 // see body method for more info 463 String path = digester.getMatch(); 464 PluginRules newRules = (PluginRules) digester.getRules(); 465 List rules = newRules.getDecoratedRules().match(namespace, path); 466 fireEndMethods(rules, namespace, name); 467 468 // pop the stack of PluginRules instances, which 469 // discards all custom rules associated with this plugin 470 digester.setRules(newRules.getParent()); 471 472 // and get rid of the instance of the plugin class from the 473 // digester object stack. 474 digester.pop(); 475 } 476 477 /** 478 * Return the pattern that this Rule is associated with. 479 * <p> 480 * In general, Rule instances <i>can</i> be associated with multiple 481 * patterns. A PluginCreateRule, however, will only function correctly 482 * when associated with a single pattern. It is possible to fix this, but 483 * I can't be bothered just now because this feature is unlikely to be 484 * used. 485 * </p> 486 * 487 * @return The pattern value 488 */ 489 public String getPattern() { 490 return pattern; 491 } 492 493 /** 494 * Duplicate the processing that the Digester does when firing the 495 * begin methods of rules. It would be really nice if the Digester 496 * class provided a way for this functionality to just be invoked 497 * directly. 498 */ 499 public void fireBeginMethods(List rules, 500 String namespace, String name, 501 org.xml.sax.Attributes list) 502 throws java.lang.Exception { 503 504 if ((rules != null) && (rules.size() > 0)) { 505 Log log = digester.getLogger(); 506 boolean debug = log.isDebugEnabled(); 507 for (int i = 0; i < rules.size(); i++) { 508 try { 509 Rule rule = (Rule) rules.get(i); 510 if (debug) { 511 log.debug(" Fire begin() for " + rule); 512 } 513 rule.begin(namespace, name, list); 514 } catch (Exception e) { 515 throw digester.createSAXException(e); 516 } catch (Error e) { 517 throw e; 518 } 519 } 520 } 521 } 522 523 /** 524 * Duplicate the processing that the Digester does when firing the 525 * body methods of rules. It would be really nice if the Digester 526 * class provided a way for this functionality to just be invoked 527 * directly. 528 */ 529 private void fireBodyMethods(List rules, 530 String namespaceURI, String name, 531 String text) throws Exception { 532 533 if ((rules != null) && (rules.size() > 0)) { 534 Log log = digester.getLogger(); 535 boolean debug = log.isDebugEnabled(); 536 for (int i = 0; i < rules.size(); i++) { 537 try { 538 Rule rule = (Rule) rules.get(i); 539 if (debug) { 540 log.debug(" Fire body() for " + rule); 541 } 542 rule.body(namespaceURI, name, text); 543 } catch (Exception e) { 544 throw digester.createSAXException(e); 545 } catch (Error e) { 546 throw e; 547 } 548 } 549 } 550 } 551 552 /** 553 * Duplicate the processing that the Digester does when firing the 554 * end methods of rules. It would be really nice if the Digester 555 * class provided a way for this functionality to just be invoked 556 * directly. 557 */ 558 public void fireEndMethods(List rules, 559 String namespaceURI, String name) 560 throws Exception { 561 562 // Fire "end" events for all relevant rules in reverse order 563 if (rules != null) { 564 Log log = digester.getLogger(); 565 boolean debug = log.isDebugEnabled(); 566 for (int i = 0; i < rules.size(); i++) { 567 int j = (rules.size() - i) - 1; 568 try { 569 Rule rule = (Rule) rules.get(j); 570 if (debug) { 571 log.debug(" Fire end() for " + rule); 572 } 573 rule.end(namespaceURI, name); 574 } catch (Exception e) { 575 throw digester.createSAXException(e); 576 } catch (Error e) { 577 throw e; 578 } 579 } 580 } 581 } 582 }