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