KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > digester > plugins > PluginCreateRule


1 /* $Id: PluginCreateRule.java 179714 2005-06-03 03:53:39Z skitching $
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  
18 package org.apache.commons.digester.plugins;
19
20 import java.util.List JavaDoc;
21
22 import org.apache.commons.digester.Rule;
23 import org.apache.commons.logging.Log;
24
25 /**
26  * Allows the original rules for parsing the configuration file to define
27  * points at which plugins are allowed, by configuring a PluginCreateRule
28  * with the appropriate pattern.
29  *
30  * @since 1.6
31  */

32 public class PluginCreateRule extends Rule implements InitializableRule {
33
34     // see setPluginClassAttribute
35
private String JavaDoc pluginClassAttrNs = null;
36     private String JavaDoc pluginClassAttr = null;
37     
38     // see setPluginIdAttribute
39
private String JavaDoc pluginIdAttrNs = null;
40     private String JavaDoc pluginIdAttr = null;
41     
42     /**
43      * In order to invoke the addRules method on the plugin class correctly,
44      * we need to know the pattern which this rule is matched by.
45      */

46     private String JavaDoc pattern;
47
48     /** A base class that any plugin must derive from. */
49     private Class JavaDoc baseClass = null;
50
51     /**
52      * Info about optional default plugin to be used if no plugin-id is
53      * specified in the input data. This can simplify the syntax where one
54      * particular plugin is usually used.
55      */

56     private Declaration defaultPlugin;
57
58     /**
59      * Currently, none of the Rules methods allow exceptions to be thrown.
60      * Therefore if this class cannot initialise itself properly, it cannot
61      * cause the digester to stop. Instead, we cache the exception and throw
62      * it the first time the begin() method is called.
63      */

64     private PluginConfigurationException initException;
65
66     //-------------------- constructors -------------------------------------
67

68     /**
69      * Create a plugin rule where the user <i>must</i> specify a plugin-class
70      * or plugin-id.
71      *
72      * @param baseClass is the class which any specified plugin <i>must</i> be
73      * descended from.
74      */

75     public PluginCreateRule(Class JavaDoc baseClass) {
76         this.baseClass = baseClass;
77     }
78
79     /**
80      * Create a plugin rule where the user <i>may</i> specify a plugin.
81      * If the user doesn't specify a plugin, then the default class specified
82      * in this constructor is used.
83      *
84      * @param baseClass is the class which any specified plugin <i>must</i> be
85      * descended from.
86      * @param dfltPluginClass is the class which will be used if the user
87      * doesn't specify any plugin-class or plugin-id. This class will have
88      * custom rules installed for it just like a declared plugin.
89      */

90     public PluginCreateRule(Class JavaDoc baseClass, Class JavaDoc dfltPluginClass) {
91         this.baseClass = baseClass;
92         if (dfltPluginClass != null) {
93             defaultPlugin = new Declaration(dfltPluginClass);
94         }
95     }
96
97     /**
98      * Create a plugin rule where the user <i>may</i> specify a plugin.
99      * 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 JavaDoc baseClass, Class JavaDoc 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 JavaDoc namespaceUri, String JavaDoc 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 JavaDoc namespaceUri, String JavaDoc 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 JavaDoc 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 JavaDoc.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     public void begin(String JavaDoc namespace, String JavaDoc name,
302                       org.xml.sax.Attributes JavaDoc attributes)
303                       throws java.lang.Exception JavaDoc {
304         Log log = digester.getLogger();
305         boolean debug = log.isDebugEnabled();
306         if (debug) {
307             log.debug("PluginCreateRule.begin" + ": pattern=[" + pattern + "]" +
308                   " match=[" + digester.getMatch() + "]");
309         }
310
311         if (initException != null) {
312             // we had a problem during initialisation that we could
313
// not report then; report it now.
314
throw initException;
315         }
316         
317         // load any custom rules associated with the plugin
318
PluginRules oldRules = (PluginRules) digester.getRules();
319         PluginManager pluginManager = oldRules.getPluginManager();
320         Declaration currDeclaration = null;
321             
322         String JavaDoc pluginClassName;
323         if (pluginClassAttrNs == null) {
324             // Yep, this is ugly.
325
//
326
// In a namespace-aware parser, the one-param version will
327
// return attributes with no namespace.
328
//
329
// In a non-namespace-aware parser, the two-param version will
330
// never return any attributes, ever.
331
pluginClassName = attributes.getValue(pluginClassAttr);
332         } else {
333             pluginClassName =
334                 attributes.getValue(pluginClassAttrNs, pluginClassAttr);
335         }
336
337         String JavaDoc pluginId;
338         if (pluginIdAttrNs == null) {
339             pluginId = attributes.getValue(pluginIdAttr);
340         } else {
341             pluginId =
342                 attributes.getValue(pluginIdAttrNs, pluginIdAttr);
343         }
344         
345         if (pluginClassName != null) {
346             // The user is using a plugin "inline", ie without a previous
347
// explicit declaration. If they have used the same plugin class
348
// before, we have already gone to the effort of creating a
349
// Declaration object, so retrieve it. If there is no existing
350
// declaration object for this class, then create one.
351

352             currDeclaration = pluginManager.getDeclarationByClass(
353                 pluginClassName);
354
355             if (currDeclaration == null) {
356                 currDeclaration = new Declaration(pluginClassName);
357                 try {
358                     currDeclaration.init(digester, pluginManager);
359                 } catch(PluginException pwe) {
360                     throw new PluginInvalidInputException(
361                         pwe.getMessage(), pwe.getCause());
362                 }
363                 pluginManager.addDeclaration(currDeclaration);
364             }
365         } else if (pluginId != null) {
366             currDeclaration = pluginManager.getDeclarationById(pluginId);
367                 
368             if (currDeclaration == null) {
369                 throw new PluginInvalidInputException(
370                     "Plugin id [" + pluginId + "] is not defined.");
371             }
372         } else if (defaultPlugin != null) {
373             currDeclaration = defaultPlugin;
374         } else {
375             throw new PluginInvalidInputException(
376                 "No plugin class specified for element " +
377                 pattern);
378         }
379             
380         // get the class of the user plugged-in type
381
Class JavaDoc pluginClass = currDeclaration.getPluginClass();
382         
383         String JavaDoc path = digester.getMatch();
384
385         // create a new Rules object and effectively push it onto a stack of
386
// rules objects. The stack is actually a linked list; using the
387
// PluginRules constructor below causes the new instance to link
388
// to the previous head-of-stack, then the Digester.setRules() makes
389
// the new instance the new head-of-stack.
390
PluginRules newRules = new PluginRules(digester, path, oldRules, pluginClass);
391         digester.setRules(newRules);
392         
393         if (debug) {
394             log.debug("PluginCreateRule.begin: installing new plugin: " +
395                 "oldrules=" + oldRules.toString() +
396                 ", newrules=" + newRules.toString());
397         }
398               
399         // load up the custom rules
400
currDeclaration.configure(digester, pattern);
401
402         // create an instance of the plugin class
403
Object JavaDoc instance = pluginClass.newInstance();
404         getDigester().push(instance);
405         if (debug) {
406             log.debug(
407                 "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" +
408                 " match=[" + digester.getMatch() + "]" +
409                 " pushed instance of plugin [" + pluginClass.getName() + "]");
410         }
411         
412         // and now we have to fire any custom rules which would have
413
// been matched by the same path that matched this rule, had
414
// they been loaded at that time.
415
List JavaDoc rules = newRules.getDecoratedRules().match(namespace, path);
416         fireBeginMethods(rules, namespace, name, attributes);
417     }
418
419     /**
420      * Process the body text of this element.
421      *
422      * @param text The body text of this element
423      */

424     public void body(String JavaDoc namespace, String JavaDoc name, String JavaDoc text)
425         throws Exception JavaDoc {
426
427         // While this class itself has no work to do in the body method,
428
// we do need to fire the body methods of all dynamically-added
429
// rules matching the same path as this rule. During begin, we had
430
// to manually execute the dynamic rules' begin methods because they
431
// didn't exist in the digester's Rules object when the match begin.
432
// So in order to ensure consistent ordering of rule execution, the
433
// PluginRules class deliberately avoids returning any such rules
434
// in later calls to the match method, instead relying on this
435
// object to execute them at the appropriate time.
436
//
437
// Note that this applies only to rules matching exactly the path
438
// which is also matched by this PluginCreateRule.
439

440         String JavaDoc path = digester.getMatch();
441         PluginRules newRules = (PluginRules) digester.getRules();
442         List JavaDoc rules = newRules.getDecoratedRules().match(namespace, path);
443         fireBodyMethods(rules, namespace, name, text);
444     }
445
446     /**
447      * Invoked by the digester when the closing tag matching this Rule's
448      * pattern is encountered.
449      * </p>
450      *
451      * @param namespace Description of the Parameter
452      * @param name Description of the Parameter
453      * @exception Exception Description of the Exception
454      *
455      * @see #begin
456      */

457     public void end(String JavaDoc namespace, String JavaDoc name)
458                     throws Exception JavaDoc {
459
460
461         // see body method for more info
462
String JavaDoc path = digester.getMatch();
463         PluginRules newRules = (PluginRules) digester.getRules();
464         List JavaDoc rules = newRules.getDecoratedRules().match(namespace, path);
465         fireEndMethods(rules, namespace, name);
466         
467         // pop the stack of PluginRules instances, which
468
// discards all custom rules associated with this plugin
469
digester.setRules(newRules.getParent());
470         
471         // and get rid of the instance of the plugin class from the
472
// digester object stack.
473
digester.pop();
474     }
475
476     /**
477      * Return the pattern that this Rule is associated with.
478      * <p>
479      * In general, Rule instances <i>can</i> be associated with multiple
480      * patterns. A PluginCreateRule, however, will only function correctly
481      * when associated with a single pattern. It is possible to fix this, but
482      * I can't be bothered just now because this feature is unlikely to be
483      * used.
484      * </p>
485      *
486      * @return The pattern value
487      */

488     public String JavaDoc getPattern() {
489         return pattern;
490     }
491     
492     /**
493      * Duplicate the processing that the Digester does when firing the
494      * begin methods of rules. It would be really nice if the Digester
495      * class provided a way for this functionality to just be invoked
496      * directly.
497      */

498     public void fireBeginMethods(List JavaDoc rules,
499                       String JavaDoc namespace, String JavaDoc name,
500                       org.xml.sax.Attributes JavaDoc list)
501                       throws java.lang.Exception JavaDoc {
502         
503         if ((rules != null) && (rules.size() > 0)) {
504             Log log = digester.getLogger();
505             boolean debug = log.isDebugEnabled();
506             for (int i = 0; i < rules.size(); i++) {
507                 try {
508                     Rule rule = (Rule) rules.get(i);
509                     if (debug) {
510                         log.debug(" Fire begin() for " + rule);
511                     }
512                     rule.begin(namespace, name, list);
513                 } catch (Exception JavaDoc e) {
514                     throw digester.createSAXException(e);
515                 } catch (Error JavaDoc e) {
516                     throw e;
517                 }
518             }
519         }
520     }
521
522     /**
523      * Duplicate the processing that the Digester does when firing the
524      * body methods of rules. It would be really nice if the Digester
525      * class provided a way for this functionality to just be invoked
526      * directly.
527      */

528     private void fireBodyMethods(List JavaDoc rules,
529                     String JavaDoc namespaceURI, String JavaDoc name,
530                     String JavaDoc text) throws Exception JavaDoc {
531
532         if ((rules != null) && (rules.size() > 0)) {
533             Log log = digester.getLogger();
534             boolean debug = log.isDebugEnabled();
535             for (int i = 0; i < rules.size(); i++) {
536                 try {
537                     Rule rule = (Rule) rules.get(i);
538                     if (debug) {
539                         log.debug(" Fire body() for " + rule);
540                     }
541                     rule.body(namespaceURI, name, text);
542                 } catch (Exception JavaDoc e) {
543                     throw digester.createSAXException(e);
544                 } catch (Error JavaDoc e) {
545                     throw e;
546                 }
547             }
548         }
549     }
550     
551     /**
552      * Duplicate the processing that the Digester does when firing the
553      * end methods of rules. It would be really nice if the Digester
554      * class provided a way for this functionality to just be invoked
555      * directly.
556      */

557     public void fireEndMethods(List JavaDoc rules,
558                     String JavaDoc namespaceURI, String JavaDoc name)
559                     throws Exception JavaDoc {
560
561         // Fire "end" events for all relevant rules in reverse order
562
if (rules != null) {
563             Log log = digester.getLogger();
564             boolean debug = log.isDebugEnabled();
565             for (int i = 0; i < rules.size(); i++) {
566                 int j = (rules.size() - i) - 1;
567                 try {
568                     Rule rule = (Rule) rules.get(j);
569                     if (debug) {
570                         log.debug(" Fire end() for " + rule);
571                     }
572                     rule.end(namespaceURI, name);
573                 } catch (Exception JavaDoc e) {
574                     throw digester.createSAXException(e);
575                 } catch (Error JavaDoc e) {
576                     throw e;
577                 }
578             }
579         }
580     }
581 }
582
Popular Tags