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    }