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