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 */
018package org.apache.commons.digester.plugins;
019
020import java.util.List;
021
022import org.apache.commons.digester.Rule;
023import 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 */
032public 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}