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    }