001 /* $Id: PluginCreateRule.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
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<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 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<Rule> 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<Rule> 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<Rule> 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<Rule> 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<Rule> 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 }