1. /* $Id: PluginCreateRule.java,v 1.19 2004/05/10 06:44:13 skitching Exp $
  2. *
  3. * Copyright 2003-2004 The Apache Software Foundation.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package org.apache.commons.digester.plugins;
  18. import java.util.Iterator;
  19. import java.util.ListIterator;
  20. import java.util.List;
  21. import java.io.File;
  22. import org.apache.commons.digester.Digester;
  23. import org.apache.commons.digester.Rule;
  24. import org.apache.commons.digester.Rules;
  25. import org.apache.commons.logging.Log;
  26. /**
  27. * Allows the original rules for parsing the configuration file to define
  28. * points at which plugins are allowed, by configuring a PluginCreateRule
  29. * with the appropriate pattern.
  30. *
  31. * @since 1.6
  32. */
  33. public class PluginCreateRule extends Rule implements InitializableRule {
  34. // see setPluginClassAttribute
  35. private String pluginClassAttrNs = null;
  36. private String pluginClassAttr = null;
  37. // see setPluginIdAttribute
  38. private String pluginIdAttrNs = null;
  39. private String pluginIdAttr = null;
  40. /**
  41. * In order to invoke the addRules method on the plugin class correctly,
  42. * we need to know the pattern which this rule is matched by.
  43. */
  44. private String pattern;
  45. /** A base class that any plugin must derive from. */
  46. private Class baseClass = null;
  47. /**
  48. * Info about optional default plugin to be used if no plugin-id is
  49. * specified in the input data. This can simplify the syntax where one
  50. * particular plugin is usually used.
  51. */
  52. private Declaration defaultPlugin;
  53. /**
  54. * Currently, none of the Rules methods allow exceptions to be thrown.
  55. * Therefore if this class cannot initialise itself properly, it cannot
  56. * cause the digester to stop. Instead, we cache the exception and throw
  57. * it the first time the begin() method is called.
  58. */
  59. private PluginConfigurationException initException;
  60. //-------------------- constructors -------------------------------------
  61. /**
  62. * Create a plugin rule where the user <i>must</i> specify a plugin-class
  63. * or plugin-id.
  64. *
  65. * @param baseClass is the class which any specified plugin <i>must</i> be
  66. * descended from.
  67. */
  68. public PluginCreateRule(Class baseClass) {
  69. this.baseClass = baseClass;
  70. }
  71. /**
  72. * Create a plugin rule where the user <i>may</i> specify a plugin.
  73. * If the user doesn't specify a plugin, then the default class specified
  74. * in this constructor is used.
  75. *
  76. * @param baseClass is the class which any specified plugin <i>must</i> be
  77. * descended from.
  78. * @param dfltPluginClass is the class which will be used if the user
  79. * doesn't specify any plugin-class or plugin-id. This class will have
  80. * custom rules installed for it just like a declared plugin.
  81. */
  82. public PluginCreateRule(Class baseClass, Class dfltPluginClass) {
  83. this.baseClass = baseClass;
  84. if (dfltPluginClass != null) {
  85. defaultPlugin = new Declaration(dfltPluginClass);
  86. }
  87. }
  88. /**
  89. * Create a plugin rule where the user <i>may</i> specify a plugin.
  90. * If the user doesn't specify a plugin, then the default class specified
  91. * in this constructor is used.
  92. *
  93. * @param baseClass is the class which any specified plugin <i>must</i> be
  94. * descended from.
  95. * @param dfltPluginClass is the class which will be used if the user
  96. * doesn't specify any plugin-class or plugin-id. This class will have
  97. * custom rules installed for it just like a declared plugin.
  98. * @param dfltPluginRuleLoader is a RuleLoader instance which knows how
  99. * to load the custom rules associated with this default plugin.
  100. */
  101. public PluginCreateRule(Class baseClass, Class dfltPluginClass,
  102. RuleLoader dfltPluginRuleLoader) {
  103. this.baseClass = baseClass;
  104. if (dfltPluginClass != null) {
  105. defaultPlugin =
  106. new Declaration(dfltPluginClass, dfltPluginRuleLoader);
  107. }
  108. }
  109. //------------------- properties ---------------------------------------
  110. /**
  111. * Sets the xml attribute which the input xml uses to indicate to a
  112. * PluginCreateRule which class should be instantiated.
  113. * <p>
  114. * See {@link PluginRules#setPluginClassAttribute} for more info.
  115. */
  116. public void setPluginClassAttribute(String namespaceUri, String attrName) {
  117. pluginClassAttrNs = namespaceUri;
  118. pluginClassAttr = attrName;
  119. }
  120. /**
  121. * Sets the xml attribute which the input xml uses to indicate to a
  122. * PluginCreateRule which plugin declaration is being referenced.
  123. * <p>
  124. * See {@link PluginRules#setPluginIdAttribute} for more info.
  125. */
  126. public void setPluginIdAttribute(String namespaceUri, String attrName) {
  127. pluginIdAttrNs = namespaceUri;
  128. pluginIdAttr = attrName;
  129. }
  130. //------------------- methods --------------------------------------------
  131. /**
  132. * Invoked after this rule has been added to the set of digester rules,
  133. * associated with the specified pattern. Check all configuration data is
  134. * valid and remember the pattern for later.
  135. *
  136. * @param matchPattern is the digester match pattern that is associated
  137. * with this rule instance, eg "root/widget".
  138. * @exception PluginConfigurationException
  139. */
  140. public void postRegisterInit(String matchPattern)
  141. throws PluginConfigurationException {
  142. Log log = LogUtils.getLogger(digester);
  143. boolean debug = log.isDebugEnabled();
  144. if (debug) {
  145. log.debug("PluginCreateRule.postRegisterInit" +
  146. ": rule registered for pattern [" + matchPattern + "]");
  147. }
  148. if (digester == null) {
  149. // We require setDigester to be called before this method.
  150. // Note that this means that PluginCreateRule cannot be added
  151. // to a Rules object which has not yet been added to a
  152. // Digester object.
  153. initException = new PluginConfigurationException(
  154. "Invalid invocation of postRegisterInit" +
  155. ": digester not set.");
  156. throw initException;
  157. }
  158. if (pattern != null) {
  159. // We have been called twice, ie a single instance has been
  160. // associated with multiple patterns.
  161. //
  162. // Generally, Digester Rule instances can be associated with
  163. // multiple patterns. However for plugins, this creates some
  164. // complications. Some day this may be supported; however for
  165. // now we just reject this situation.
  166. initException = new PluginConfigurationException(
  167. "A single PluginCreateRule instance has been mapped to" +
  168. " multiple patterns; this is not supported.");
  169. throw initException;
  170. }
  171. if (matchPattern.indexOf('*') != -1) {
  172. // having wildcards in patterns is extremely difficult to
  173. // deal with. For now, we refuse to allow this.
  174. //
  175. // TODO: check for any chars not valid in xml element name
  176. // rather than just *.
  177. //
  178. // Reasons include:
  179. // (a) handling recursive plugins, and
  180. // (b) determining whether one pattern is "below" another,
  181. // as done by PluginRules. Without wildcards, "below"
  182. // just means startsWith, which is easy to check.
  183. initException = new PluginConfigurationException(
  184. "A PluginCreateRule instance has been mapped to" +
  185. " pattern [" + matchPattern + "]." +
  186. " This pattern includes a wildcard character." +
  187. " This is not supported by the plugin architecture.");
  188. throw initException;
  189. }
  190. if (baseClass == null) {
  191. baseClass = Object.class;
  192. }
  193. PluginRules rules = (PluginRules) digester.getRules();
  194. PluginManager pm = rules.getPluginManager();
  195. // check default class is valid
  196. if (defaultPlugin != null) {
  197. if (!baseClass.isAssignableFrom(defaultPlugin.getPluginClass())) {
  198. initException = new PluginConfigurationException(
  199. "Default class [" +
  200. defaultPlugin.getPluginClass().getName() +
  201. "] does not inherit from [" +
  202. baseClass.getName() + "].");
  203. throw initException;
  204. }
  205. try {
  206. defaultPlugin.init(digester, pm);
  207. } catch(PluginException pwe) {
  208. throw new PluginConfigurationException(
  209. pwe.getMessage(), pwe.getCause());
  210. }
  211. }
  212. // remember the pattern for later
  213. pattern = matchPattern;
  214. if (pluginClassAttr == null) {
  215. // the user hasn't set explicit xml attr names on this rule,
  216. // so fetch the default values
  217. pluginClassAttrNs = rules.getPluginClassAttrNs();
  218. pluginClassAttr = rules.getPluginClassAttr();
  219. if (debug) {
  220. log.debug(
  221. "init: pluginClassAttr set to per-digester values ["
  222. + "ns=" + pluginClassAttrNs
  223. + ", name=" + pluginClassAttr + "]");
  224. }
  225. } else {
  226. if (debug) {
  227. log.debug(
  228. "init: pluginClassAttr set to rule-specific values ["
  229. + "ns=" + pluginClassAttrNs
  230. + ", name=" + pluginClassAttr + "]");
  231. }
  232. }
  233. if (pluginIdAttr == null) {
  234. // the user hasn't set explicit xml attr names on this rule,
  235. // so fetch the default values
  236. pluginIdAttrNs = rules.getPluginIdAttrNs();
  237. pluginIdAttr = rules.getPluginIdAttr();
  238. if (debug) {
  239. log.debug(
  240. "init: pluginIdAttr set to per-digester values ["
  241. + "ns=" + pluginIdAttrNs
  242. + ", name=" + pluginIdAttr + "]");
  243. }
  244. } else {
  245. if (debug) {
  246. log.debug(
  247. "init: pluginIdAttr set to rule-specific values ["
  248. + "ns=" + pluginIdAttrNs
  249. + ", name=" + pluginIdAttr + "]");
  250. }
  251. }
  252. }
  253. /**
  254. * Invoked when the Digester matches this rule against an xml element.
  255. * <p>
  256. * A new instance of the target class is created, and pushed onto the
  257. * stack. A new "private" PluginRules object is then created and set as
  258. * the digester's default Rules object. Any custom rules associated with
  259. * the plugin class are then loaded into that new Rules object.
  260. * Finally, any custom rules that are associated with the current pattern
  261. * (such as SetPropertiesRules) have their begin methods executed.
  262. *
  263. * @param namespace
  264. * @param name
  265. * @param attributes
  266. *
  267. * @throws ClassNotFoundException
  268. * @throws PluginInvalidInputException
  269. * @throws PluginConfigurationException
  270. */
  271. public void begin(String namespace, String name,
  272. org.xml.sax.Attributes attributes)
  273. throws java.lang.Exception {
  274. Log log = digester.getLogger();
  275. boolean debug = log.isDebugEnabled();
  276. if (debug) {
  277. log.debug("PluginCreateRule.begin" + ": pattern=[" + pattern + "]" +
  278. " match=[" + digester.getMatch() + "]");
  279. }
  280. if (initException != null) {
  281. // we had a problem during initialisation that we could
  282. // not report then; report it now.
  283. throw initException;
  284. }
  285. String path = digester.getMatch();
  286. // create a new Rules object and effectively push it onto a stack of
  287. // rules objects. The stack is actually a linked list; using the
  288. // PluginRules constructor below causes the new instance to link
  289. // to the previous head-of-stack, then the Digester.setRules() makes
  290. // the new instance the new head-of-stack.
  291. PluginRules oldRules = (PluginRules) digester.getRules();
  292. PluginRules newRules = new PluginRules(path, oldRules);
  293. digester.setRules(newRules);
  294. // load any custom rules associated with the plugin
  295. PluginManager pluginManager = newRules.getPluginManager();
  296. Declaration currDeclaration = null;
  297. if (debug) {
  298. log.debug("PluginCreateRule.begin: installing new plugin: " +
  299. "oldrules=" + oldRules.toString() +
  300. ", newrules=" + newRules.toString());
  301. }
  302. String pluginClassName;
  303. if (pluginClassAttrNs == null) {
  304. // Yep, this is ugly.
  305. //
  306. // In a namespace-aware parser, the one-param version will
  307. // return attributes with no namespace.
  308. //
  309. // In a non-namespace-aware parser, the two-param version will
  310. // never return any attributes, ever.
  311. pluginClassName = attributes.getValue(pluginClassAttr);
  312. } else {
  313. pluginClassName =
  314. attributes.getValue(pluginClassAttrNs, pluginClassAttr);
  315. }
  316. String pluginId;
  317. if (pluginIdAttrNs == null) {
  318. pluginId = attributes.getValue(pluginIdAttr);
  319. } else {
  320. pluginId =
  321. attributes.getValue(pluginIdAttrNs, pluginIdAttr);
  322. }
  323. if (pluginClassName != null) {
  324. // The user is using a plugin "inline", ie without a previous
  325. // explicit declaration. If they have used the same plugin class
  326. // before, we have already gone to the effort of creating a
  327. // Declaration object, so retrieve it. If there is no existing
  328. // declaration object for this class, then create one.
  329. currDeclaration = pluginManager.getDeclarationByClass(
  330. pluginClassName);
  331. if (currDeclaration == null) {
  332. currDeclaration = new Declaration(pluginClassName);
  333. try {
  334. currDeclaration.init(digester, pluginManager);
  335. } catch(PluginException pwe) {
  336. throw new PluginInvalidInputException(
  337. pwe.getMessage(), pwe.getCause());
  338. }
  339. pluginManager.addDeclaration(currDeclaration);
  340. }
  341. } else if (pluginId != null) {
  342. currDeclaration = pluginManager.getDeclarationById(pluginId);
  343. if (currDeclaration == null) {
  344. throw new PluginInvalidInputException(
  345. "Plugin id [" + pluginId + "] is not defined.");
  346. }
  347. } else if (defaultPlugin != null) {
  348. currDeclaration = defaultPlugin;
  349. } else {
  350. throw new PluginInvalidInputException(
  351. "No plugin class specified for element " +
  352. pattern);
  353. }
  354. // now load up the custom rules
  355. currDeclaration.configure(digester, pattern);
  356. // and now create an instance of the plugin class
  357. Class pluginClass = currDeclaration.getPluginClass();
  358. Object instance = pluginClass.newInstance();
  359. getDigester().push(instance);
  360. if (debug) {
  361. log.debug(
  362. "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" +
  363. " match=[" + digester.getMatch() + "]" +
  364. " pushed instance of plugin [" + pluginClass.getName() + "]");
  365. }
  366. // and now we have to fire any custom rules which would have
  367. // been matched by the same path that matched this rule, had
  368. // they been loaded at that time.
  369. List rules = newRules.getDecoratedRules().match(namespace, path);
  370. fireBeginMethods(rules, namespace, name, attributes);
  371. }
  372. /**
  373. * Process the body text of this element.
  374. *
  375. * @param text The body text of this element
  376. */
  377. public void body(String namespace, String name, String text)
  378. throws Exception {
  379. // While this class itself has no work to do in the body method,
  380. // we do need to fire the body methods of all dynamically-added
  381. // rules matching the same path as this rule. During begin, we had
  382. // to manually execute the dynamic rules' begin methods because they
  383. // didn't exist in the digester's Rules object when the match begin.
  384. // So in order to ensure consistent ordering of rule execution, the
  385. // PluginRules class deliberately avoids returning any such rules
  386. // in later calls to the match method, instead relying on this
  387. // object to execute them at the appropriate time.
  388. //
  389. // Note that this applies only to rules matching exactly the path
  390. // which is also matched by this PluginCreateRule.
  391. String path = digester.getMatch();
  392. PluginRules newRules = (PluginRules) digester.getRules();
  393. List rules = newRules.getDecoratedRules().match(namespace, path);
  394. fireBodyMethods(rules, namespace, name, text);
  395. }
  396. /**
  397. * Invoked by the digester when the closing tag matching this Rule's
  398. * pattern is encountered.
  399. * </p>
  400. *
  401. * @param namespace Description of the Parameter
  402. * @param name Description of the Parameter
  403. * @exception Exception Description of the Exception
  404. *
  405. * @see #begin
  406. */
  407. public void end(String namespace, String name)
  408. throws Exception {
  409. // see body method for more info
  410. String path = digester.getMatch();
  411. PluginRules newRules = (PluginRules) digester.getRules();
  412. List rules = newRules.getDecoratedRules().match(namespace, path);
  413. fireEndMethods(rules, namespace, name);
  414. // pop the stack of PluginRules instances, which
  415. // discards all custom rules associated with this plugin
  416. digester.setRules(newRules.getParent());
  417. // and get rid of the instance of the plugin class from the
  418. // digester object stack.
  419. digester.pop();
  420. }
  421. /**
  422. * Return the pattern that this Rule is associated with.
  423. * <p>
  424. * In general, Rule instances <i>can</i> be associated with multiple
  425. * patterns. A PluginCreateRule, however, will only function correctly
  426. * when associated with a single pattern. It is possible to fix this, but
  427. * I can't be bothered just now because this feature is unlikely to be
  428. * used.
  429. * </p>
  430. *
  431. * @return The pattern value
  432. */
  433. public String getPattern() {
  434. return pattern;
  435. }
  436. /**
  437. * Duplicate the processing that the Digester does when firing the
  438. * begin methods of rules. It would be really nice if the Digester
  439. * class provided a way for this functionality to just be invoked
  440. * directly.
  441. */
  442. public void fireBeginMethods(List rules,
  443. String namespace, String name,
  444. org.xml.sax.Attributes list)
  445. throws java.lang.Exception {
  446. if ((rules != null) && (rules.size() > 0)) {
  447. Log log = digester.getLogger();
  448. boolean debug = log.isDebugEnabled();
  449. for (int i = 0; i < rules.size(); i++) {
  450. try {
  451. Rule rule = (Rule) rules.get(i);
  452. if (debug) {
  453. log.debug(" Fire begin() for " + rule);
  454. }
  455. rule.begin(namespace, name, list);
  456. } catch (Exception e) {
  457. throw digester.createSAXException(e);
  458. } catch (Error e) {
  459. throw e;
  460. }
  461. }
  462. }
  463. }
  464. /**
  465. * Duplicate the processing that the Digester does when firing the
  466. * body methods of rules. It would be really nice if the Digester
  467. * class provided a way for this functionality to just be invoked
  468. * directly.
  469. */
  470. private void fireBodyMethods(List rules,
  471. String namespaceURI, String name,
  472. String text) throws Exception {
  473. if ((rules != null) && (rules.size() > 0)) {
  474. Log log = digester.getLogger();
  475. boolean debug = log.isDebugEnabled();
  476. for (int i = 0; i < rules.size(); i++) {
  477. try {
  478. Rule rule = (Rule) rules.get(i);
  479. if (debug) {
  480. log.debug(" Fire body() for " + rule);
  481. }
  482. rule.body(namespaceURI, name, text);
  483. } catch (Exception e) {
  484. throw digester.createSAXException(e);
  485. } catch (Error e) {
  486. throw e;
  487. }
  488. }
  489. }
  490. }
  491. /**
  492. * Duplicate the processing that the Digester does when firing the
  493. * end methods of rules. It would be really nice if the Digester
  494. * class provided a way for this functionality to just be invoked
  495. * directly.
  496. */
  497. public void fireEndMethods(List rules,
  498. String namespaceURI, String name)
  499. throws Exception {
  500. // Fire "end" events for all relevant rules in reverse order
  501. if (rules != null) {
  502. Log log = digester.getLogger();
  503. boolean debug = log.isDebugEnabled();
  504. for (int i = 0; i < rules.size(); i++) {
  505. int j = (rules.size() - i) - 1;
  506. try {
  507. Rule rule = (Rule) rules.get(j);
  508. if (debug) {
  509. log.debug(" Fire end() for " + rule);
  510. }
  511. rule.end(namespaceURI, name);
  512. } catch (Exception e) {
  513. throw digester.createSAXException(e);
  514. } catch (Error e) {
  515. throw e;
  516. }
  517. }
  518. }
  519. }
  520. }