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 }