001/* $Id: DigesterRuleParser.java 471661 2006-11-06 08:09:25Z skitching $
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements.  See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License.  You may obtain a copy of the License at
009 * 
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 * 
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */ 
018
019
020package org.apache.commons.digester.xmlrules;
021
022
023import java.io.FileNotFoundException;
024import java.io.IOException;
025import java.net.URL;
026import java.util.ArrayList;
027import java.util.HashSet;
028import java.util.List;
029import java.util.Set;
030import java.util.StringTokenizer;
031
032import org.apache.commons.beanutils.ConvertUtils;
033
034import org.apache.commons.collections.ArrayStack;
035
036import org.apache.commons.digester.AbstractObjectCreationFactory;
037import org.apache.commons.digester.BeanPropertySetterRule;
038import org.apache.commons.digester.CallMethodRule;
039import org.apache.commons.digester.CallParamRule;
040import org.apache.commons.digester.Digester;
041import org.apache.commons.digester.FactoryCreateRule;
042import org.apache.commons.digester.NodeCreateRule;
043import org.apache.commons.digester.ObjectCreateRule;
044import org.apache.commons.digester.Rule;
045import org.apache.commons.digester.RuleSetBase;
046import org.apache.commons.digester.Rules;
047import org.apache.commons.digester.SetNestedPropertiesRule;
048import org.apache.commons.digester.SetNextRule;
049import org.apache.commons.digester.SetPropertiesRule;
050import org.apache.commons.digester.SetPropertyRule;
051import org.apache.commons.digester.SetRootRule;
052import org.apache.commons.digester.SetTopRule;
053import org.apache.commons.digester.ObjectParamRule;
054
055import org.w3c.dom.Node;
056import org.xml.sax.Attributes;
057import org.xml.sax.SAXException;
058
059
060/**
061 * This is a RuleSet that parses XML into Digester rules, and then
062 * adds those rules to a 'target' Digester.
063 *
064 * @since 1.2
065 */
066
067public class DigesterRuleParser extends RuleSetBase {
068    
069    public static final String DIGESTER_PUBLIC_ID = "-//Jakarta Apache //DTD digester-rules XML V1.0//EN";
070    
071    /**
072     * path to the DTD
073     */
074    private String digesterDtdUrl;
075    
076    /**
077     * This is the digester to which we are adding the rules that we parse
078     * from the Rules XML document.
079     */
080    protected Digester targetDigester;
081
082    /** See {@link #setBasePath}. */
083    protected String basePath = "";
084    
085    /**
086     * A stack whose toString method returns a '/'-separated concatenation
087     * of all the elements in the stack.
088     */
089    protected class PatternStack extends ArrayStack {
090        public String toString() {
091            StringBuffer str = new StringBuffer();
092            for (int i = 0; i < size(); i++) {
093                String elem = get(i).toString();
094                if (elem.length() > 0) {
095                    if (str.length() > 0) {
096                        str.append('/');
097                    }
098                    str.append(elem);
099                }
100            }
101            return str.toString();
102        }
103    }
104    
105    /**
106     * A stack used to maintain the current pattern. The Rules XML document
107     * type allows nesting of patterns. If an element defines a matching
108     * pattern, the resulting pattern is a concatenation of that pattern with
109     * all the ancestor elements' patterns. Hence the need for a stack.
110     */
111    protected PatternStack patternStack;
112    
113    /**
114     * Used to detect circular includes
115     */
116    private Set includedFiles = new HashSet();
117    
118    /**
119     * Constructs a DigesterRuleParser. This object will be inoperable
120     * until the target digester is set, via <code>setTarget(Digester)</code>
121     */
122    public DigesterRuleParser() {
123        patternStack = new PatternStack();
124    }
125    
126    /**
127     * Constructs a rule set for converting XML digester rule descriptions
128     * into Rule objects, and adding them to the given Digester
129     * @param targetDigester the Digester to add the rules to
130     */
131    public DigesterRuleParser(Digester targetDigester) {
132        this.targetDigester = targetDigester;
133        patternStack = new PatternStack();
134    }
135    
136    /**
137     * Constructs a rule set for parsing an XML digester rule file that
138     * has been included within an outer XML digester rule file. In this
139     * case, we must pass the pattern stack and the target digester
140     * to the rule set, as well as the list of files that have already
141     * been included, for cycle detection.
142     * @param targetDigester the Digester to add the rules to
143     * @param stack Stack containing the prefix pattern string to be prepended
144     * to any pattern parsed by this rule set.
145     */
146    private DigesterRuleParser(Digester targetDigester,
147                                PatternStack stack, Set includedFiles) {
148        this.targetDigester = targetDigester;
149        patternStack = stack;
150        this.includedFiles = includedFiles;
151    }
152    
153    /**
154     * Sets the digester into which to add the parsed rules
155     * @param d the Digester to add the rules to
156     */
157    public void setTarget(Digester d) {
158        targetDigester = d;
159    }
160    
161    /**
162     * Set a base pattern beneath which all the rules loaded by this
163     * object will be registered. If this string is not empty, and does
164     * not end in a "/", then one will be added.
165     *
166     * @since 1.6
167     */
168    public void setBasePath(String path) {
169        if (path == null) {
170            basePath = "";
171        }
172        else if ((path.length() > 0) && !path.endsWith("/")) {
173            basePath = path + "/";
174        } else {
175            basePath = path;
176        }
177    }
178
179    /**
180     * Sets the location of the digester rules DTD. This is the DTD used
181     * to validate the rules XML file.
182     */
183    public void setDigesterRulesDTD(String dtdURL) {
184        digesterDtdUrl = dtdURL;
185    }
186    
187    /**
188     * Returns the location of the DTD used to validate the digester rules
189     * XML document.
190     */
191    protected String getDigesterRulesDTD() {
192        //ClassLoader classLoader = getClass().getClassLoader();
193        //URL url = classLoader.getResource(DIGESTER_DTD_PATH);
194        //return url.toString();
195        return digesterDtdUrl;
196    }
197    
198    /**
199     * Adds a rule the the target digester. After a rule has been created by
200     * parsing the XML, it is added to the digester by calling this method.
201     * Typically, this method is called via reflection, when executing
202     * a SetNextRule, from the Digester that is parsing the rules XML.
203     * @param rule a Rule to add to the target digester.
204     */
205    public void add(Rule rule) {
206        targetDigester.addRule(
207            basePath + patternStack.toString(), rule);
208    }
209    
210    
211    /**
212     * Add to the given digester the set of Rule instances used to parse an XML
213     * document defining Digester rules. When the digester parses an XML file,
214     * it will add the resulting rules & patterns to the 'target digester'
215     * that was passed in this RuleSet's constructor.<P>
216     * If you extend this class to support additional rules, your implementation
217     * should of this method should call this implementation first: i.e.
218     * <code>super.addRuleInstances(digester);</code>
219     */
220    public void addRuleInstances(Digester digester) {
221        final String ruleClassName = Rule.class.getName();
222        digester.register(DIGESTER_PUBLIC_ID, getDigesterRulesDTD());
223        
224        digester.addRule("*/pattern", new PatternRule("value"));
225        
226        digester.addRule("*/include", new IncludeRule());
227        
228        digester.addFactoryCreate("*/bean-property-setter-rule", new BeanPropertySetterRuleFactory());
229        digester.addRule("*/bean-property-setter-rule", new PatternRule("pattern"));
230        digester.addSetNext("*/bean-property-setter-rule", "add", ruleClassName);
231        
232        digester.addFactoryCreate("*/call-method-rule", new CallMethodRuleFactory());
233        digester.addRule("*/call-method-rule", new PatternRule("pattern"));
234        digester.addSetNext("*/call-method-rule", "add", ruleClassName);
235
236        digester.addFactoryCreate("*/object-param-rule", new ObjectParamRuleFactory());
237        digester.addRule("*/object-param-rule", new PatternRule("pattern"));
238        digester.addSetNext("*/object-param-rule", "add", ruleClassName);
239        
240        digester.addFactoryCreate("*/call-param-rule", new CallParamRuleFactory());
241        digester.addRule("*/call-param-rule", new PatternRule("pattern"));
242        digester.addSetNext("*/call-param-rule", "add", ruleClassName);
243        
244        digester.addFactoryCreate("*/factory-create-rule", new FactoryCreateRuleFactory());
245        digester.addRule("*/factory-create-rule", new PatternRule("pattern"));
246        digester.addSetNext("*/factory-create-rule", "add", ruleClassName);
247        
248        digester.addFactoryCreate("*/object-create-rule", new ObjectCreateRuleFactory());
249        digester.addRule("*/object-create-rule", new PatternRule("pattern"));
250        digester.addSetNext("*/object-create-rule", "add", ruleClassName);
251        
252        digester.addFactoryCreate("*/node-create-rule", new NodeCreateRuleFactory());
253        digester.addRule("*/node-create-rule", new PatternRule("pattern"));
254        digester.addSetNext("*/node-create-rule", "add", ruleClassName);
255        
256        digester.addFactoryCreate("*/set-properties-rule", new SetPropertiesRuleFactory());
257        digester.addRule("*/set-properties-rule", new PatternRule("pattern"));
258        digester.addSetNext("*/set-properties-rule", "add", ruleClassName);
259        
260        digester.addRule("*/set-properties-rule/alias", new SetPropertiesAliasRule());
261        
262        digester.addFactoryCreate("*/set-property-rule", new SetPropertyRuleFactory());
263        digester.addRule("*/set-property-rule", new PatternRule("pattern"));
264        digester.addSetNext("*/set-property-rule", "add", ruleClassName);
265        
266        digester.addFactoryCreate("*/set-nested-properties-rule", new SetNestedPropertiesRuleFactory());
267        digester.addRule("*/set-nested-properties-rule", new PatternRule("pattern"));
268        digester.addSetNext("*/set-nested-properties-rule", "add", ruleClassName);
269        
270        digester.addRule("*/set-nested-properties-rule/alias", new SetNestedPropertiesAliasRule());
271        
272        digester.addFactoryCreate("*/set-top-rule", new SetTopRuleFactory());
273        digester.addRule("*/set-top-rule", new PatternRule("pattern"));
274        digester.addSetNext("*/set-top-rule", "add", ruleClassName);
275        
276        digester.addFactoryCreate("*/set-next-rule", new SetNextRuleFactory());
277        digester.addRule("*/set-next-rule", new PatternRule("pattern"));
278        digester.addSetNext("*/set-next-rule", "add", ruleClassName);
279        digester.addFactoryCreate("*/set-root-rule", new SetRootRuleFactory());
280        digester.addRule("*/set-root-rule", new PatternRule("pattern"));
281        digester.addSetNext("*/set-root-rule", "add", ruleClassName);
282    }
283    
284    
285    /**
286     * A rule for extracting the pattern matching strings from the rules XML.
287     * In the digester-rules document type, a pattern can either be declared
288     * in the 'value' attribute of a <pattern> element (in which case the pattern
289     * applies to all rules elements contained within the <pattern> element),
290     * or it can be declared in the optional 'pattern' attribute of a rule
291     * element.
292     */
293    private class PatternRule extends Rule {
294        
295        private String attrName;
296        private String pattern = null;
297        
298        /**
299         * @param attrName The name of the attribute containing the pattern
300         */
301        public PatternRule(String attrName) {
302            super();
303            this.attrName = attrName;
304        }
305        
306        /**
307         * If a pattern is defined for the attribute, push it onto the
308         * pattern stack.
309         */
310        public void begin(Attributes attributes) {
311            pattern = attributes.getValue(attrName);
312            if (pattern != null) {
313                patternStack.push(pattern);
314            }
315        }
316        
317        /**
318         * If there was a pattern for this element, pop it off the pattern
319         * stack.
320         */
321        public void end() {
322            if (pattern != null) {
323                patternStack.pop();
324            }
325        }
326    }
327    
328    /**
329     * A rule for including one rules XML file within another. Included files
330     * behave as if they are 'macro-expanded' within the includer. This means
331     * that the values of the pattern stack are prefixed to every pattern
332     * in the included rules. <p>This rule will detect 'circular' includes,
333     * which would result in infinite recursion. It throws a
334     * CircularIncludeException when a cycle is detected, which will terminate
335     * the parse.
336     */
337    private class IncludeRule extends Rule {
338        public IncludeRule() {
339            super();
340        }
341        
342        /**
343         * To include a rules xml file, we instantiate another Digester, and
344         * another DigesterRulesRuleSet. We pass the
345         * pattern stack and the target Digester to the new rule set, and
346         * tell the Digester to parse the file.
347         */
348        public void begin(Attributes attributes) throws Exception {
349            // The path attribute gives the URI to another digester rules xml file
350            String fileName = attributes.getValue("path");
351            if (fileName != null && fileName.length() > 0) {
352                includeXMLRules(fileName);
353            }
354            
355            // The class attribute gives the name of a class that implements
356            // the DigesterRulesSource interface
357            String className = attributes.getValue("class");
358            if (className != null && className.length() > 0) {
359                includeProgrammaticRules(className);
360            }
361        }
362        
363        /**
364         * Creates another DigesterRuleParser, and uses it to extract the rules
365         * out of the give XML file. The contents of the current pattern stack
366         * will be prepended to all of the pattern strings parsed from the file.
367         */
368        private void includeXMLRules(String fileName)
369                        throws IOException, SAXException, CircularIncludeException {
370            ClassLoader cl = Thread.currentThread().getContextClassLoader();
371            if (cl == null) {
372                cl = DigesterRuleParser.this.getClass().getClassLoader();
373            }
374            URL fileURL = cl.getResource(fileName);
375            if (fileURL == null) {
376                throw new FileNotFoundException("File \"" + fileName + "\" not found.");
377            }
378            fileName = fileURL.toExternalForm();
379            if (includedFiles.add(fileName) == false) {
380                // circular include detected
381                throw new CircularIncludeException(fileName);
382            }
383            // parse the included xml file
384            DigesterRuleParser includedSet =
385                        new DigesterRuleParser(targetDigester, patternStack, includedFiles);
386            includedSet.setDigesterRulesDTD(getDigesterRulesDTD());
387            Digester digester = new Digester();
388            digester.addRuleSet(includedSet);
389            digester.push(DigesterRuleParser.this);
390            digester.parse(fileName);
391            includedFiles.remove(fileName);
392        }
393        
394        /**
395         * Creates an instance of the indicated class. The class must implement
396         * the DigesterRulesSource interface. Passes the target digester to
397         * that instance. The DigesterRulesSource instance is supposed to add
398         * rules into the digester. The contents of the current pattern stack
399         * will be automatically prepended to all of the pattern strings added
400         * by the DigesterRulesSource instance.
401         */
402        private void includeProgrammaticRules(String className)
403                        throws ClassNotFoundException, ClassCastException,
404                        InstantiationException, IllegalAccessException {
405            
406            Class cls = Class.forName(className);
407            DigesterRulesSource rulesSource = (DigesterRulesSource) cls.newInstance();
408            
409            // wrap the digester's Rules object, to prepend pattern
410            Rules digesterRules = targetDigester.getRules();
411            Rules prefixWrapper =
412                    new RulesPrefixAdapter(patternStack.toString(), digesterRules);
413            
414            targetDigester.setRules(prefixWrapper);
415            try {
416                rulesSource.getRules(targetDigester);
417            } finally {
418                // Put the unwrapped rules back
419                targetDigester.setRules(digesterRules);
420            }
421        }
422    }
423    
424    
425    /**
426     * Wraps a Rules object. Delegates all the Rules interface methods
427     * to the underlying Rules object. Overrides the add method to prepend
428     * a prefix to the pattern string.
429     */
430    private class RulesPrefixAdapter implements Rules {
431        
432        private Rules delegate;
433        private String prefix;
434        
435        /**
436         * @param patternPrefix the pattern string to prepend to the pattern
437         * passed to the add method.
438         * @param rules The wrapped Rules object. All of this class's methods
439         * pass through to this object.
440         */
441        public RulesPrefixAdapter(String patternPrefix, Rules rules) {
442            prefix = patternPrefix;
443            delegate = rules;
444        }
445        
446        /**
447         * Register a new Rule instance matching a pattern which is constructed
448         * by concatenating the pattern prefix with the given pattern.
449         */
450        public void add(String pattern, Rule rule) {
451            StringBuffer buffer = new StringBuffer();
452            buffer.append(prefix);
453            if (!pattern.startsWith("/")) {
454                buffer.append('/'); 
455            }
456            buffer.append(pattern);
457            delegate.add(buffer.toString(), rule);
458        }
459        
460        /**
461         * This method passes through to the underlying Rules object.
462         */
463        public void clear() {
464            delegate.clear();
465        }
466        
467        /**
468         * This method passes through to the underlying Rules object.
469         */
470        public Digester getDigester() {
471            return delegate.getDigester();
472        }
473        
474        /**
475         * This method passes through to the underlying Rules object.
476         */
477        public String getNamespaceURI() {
478            return delegate.getNamespaceURI();
479        }
480        
481        /**
482         * @deprecated Call match(namespaceURI,pattern) instead.
483         */
484        public List match(String pattern) {
485            return delegate.match(pattern);
486        }
487        
488        /**
489         * This method passes through to the underlying Rules object.
490         */
491        public List match(String namespaceURI, String pattern) {
492            return delegate.match(namespaceURI, pattern);
493        }
494        
495        /**
496         * This method passes through to the underlying Rules object.
497         */
498        public List rules() {
499            return delegate.rules();
500        }
501        
502        /**
503         * This method passes through to the underlying Rules object.
504         */
505        public void setDigester(Digester digester) {
506            delegate.setDigester(digester);
507        }
508        
509        /**
510         * This method passes through to the underlying Rules object.
511         */
512        public void setNamespaceURI(String namespaceURI) {
513            delegate.setNamespaceURI(namespaceURI);
514        }
515    }
516    
517    
518    ///////////////////////////////////////////////////////////////////////
519    // Classes beyond this point are ObjectCreationFactory implementations,
520    // used to create Rule objects and initialize them from SAX attributes.
521    ///////////////////////////////////////////////////////////////////////
522    
523    /**
524     * Factory for creating a BeanPropertySetterRule.
525     */
526    private class BeanPropertySetterRuleFactory extends AbstractObjectCreationFactory {
527        public Object createObject(Attributes attributes) throws Exception {
528            Rule beanPropertySetterRule = null;
529            String propertyname = attributes.getValue("propertyname");
530                
531            if (propertyname == null) {
532                // call the setter method corresponding to the element name.
533                beanPropertySetterRule = new BeanPropertySetterRule();
534            } else {
535                beanPropertySetterRule = new BeanPropertySetterRule(propertyname);
536            }
537            
538            return beanPropertySetterRule;
539        }
540        
541    }
542
543    /**
544     * Factory for creating a CallMethodRule.
545     */
546    protected class CallMethodRuleFactory extends AbstractObjectCreationFactory {
547        public Object createObject(Attributes attributes) {
548            Rule callMethodRule = null;
549            String methodName = attributes.getValue("methodname");
550
551            // Select which element is to be the target. Default to zero,
552            // ie the top object on the stack.
553            int targetOffset = 0;
554            String targetOffsetStr = attributes.getValue("targetoffset");
555            if (targetOffsetStr != null) {
556                targetOffset = Integer.parseInt(targetOffsetStr);
557            }
558
559            if (attributes.getValue("paramcount") == null) {
560                // call against empty method
561                callMethodRule = new CallMethodRule(targetOffset, methodName);
562            
563            } else {
564                int paramCount = Integer.parseInt(attributes.getValue("paramcount"));
565                
566                String paramTypesAttr = attributes.getValue("paramtypes");
567                if (paramTypesAttr == null || paramTypesAttr.length() == 0) {
568                    callMethodRule = new CallMethodRule(targetOffset, methodName, paramCount);
569                } else {
570                    String[] paramTypes = getParamTypes(paramTypesAttr);
571                    callMethodRule = new CallMethodRule(
572                        targetOffset, methodName, paramCount, paramTypes);
573                }
574            }
575            return callMethodRule;
576        }
577
578        /**
579         * Process the comma separated list of paramTypes
580         * into an array of String class names
581         */
582        private String[] getParamTypes(String paramTypes) {
583            String[] paramTypesArray;
584            if( paramTypes != null ) {
585                ArrayList paramTypesList = new ArrayList();
586                StringTokenizer tokens = new StringTokenizer(
587                        paramTypes, " \t\n\r,");
588                while (tokens.hasMoreTokens()) {
589                    paramTypesList.add(tokens.nextToken());
590                }
591                paramTypesArray = (String[])paramTypesList.toArray(new String[0]);
592            } else {
593                paramTypesArray = new String[0];
594            }
595            return paramTypesArray;
596        }
597    }
598    
599    /**
600     * Factory for creating a CallParamRule.
601     */
602    protected class CallParamRuleFactory extends AbstractObjectCreationFactory {
603    
604        public Object createObject(Attributes attributes) {
605            // create callparamrule
606            int paramIndex = Integer.parseInt(attributes.getValue("paramnumber"));
607            String attributeName = attributes.getValue("attrname");
608            String fromStack = attributes.getValue("from-stack");
609            String stackIndex = attributes.getValue("stack-index");
610            Rule callParamRule = null;
611
612            if (attributeName == null) {
613                if (stackIndex != null) {                    
614                    callParamRule = new CallParamRule(
615                        paramIndex, Integer.parseInt(stackIndex));                
616                } else if (fromStack != null) {                
617                    callParamRule = new CallParamRule(
618                        paramIndex, Boolean.valueOf(fromStack).booleanValue());                
619                } else {
620                    callParamRule = new CallParamRule(paramIndex);     
621                }
622            } else {
623                if (fromStack == null) {
624                    callParamRule = new CallParamRule(paramIndex, attributeName);                    
625                } else {
626                    // specifying both from-stack and attribute name is not allowed
627                    throw new RuntimeException(
628                        "Attributes from-stack and attrname cannot both be present.");
629                }
630            }
631            return callParamRule;
632        }
633    }
634    
635    /**
636     * Factory for creating a ObjectParamRule
637     */
638    protected class ObjectParamRuleFactory extends AbstractObjectCreationFactory {
639        public Object createObject(Attributes attributes) throws Exception {
640            // create callparamrule
641            int paramIndex = Integer.parseInt(attributes.getValue("paramnumber"));
642            String attributeName = attributes.getValue("attrname");
643            String type = attributes.getValue("type");
644            String value = attributes.getValue("value");
645
646            Rule objectParamRule = null;
647
648            // type name is requried
649            if (type == null) {
650                throw new RuntimeException("Attribute 'type' is required.");
651            }
652
653            // create object instance
654            Object param = null;
655            Class clazz = Class.forName(type);
656            if (value == null) {
657                param = clazz.newInstance();
658            } else {
659                param = ConvertUtils.convert(value, clazz);
660            }
661
662            if (attributeName == null) {
663                objectParamRule = new ObjectParamRule(paramIndex, param);
664            } else {
665                objectParamRule = new ObjectParamRule(paramIndex, attributeName, param);
666            }
667            return objectParamRule;
668        }
669     }
670    
671        /**
672         * Factory for creating a NodeCreateRule
673         */
674    protected class NodeCreateRuleFactory extends AbstractObjectCreationFactory {
675
676        public Object createObject(Attributes attributes) throws Exception {
677
678            String nodeType = attributes.getValue("type");
679            if (nodeType == null || "".equals(nodeType)) {
680
681                // uses Node.ELEMENT_NODE
682                return new NodeCreateRule();
683            } else if ("element".equals(nodeType)) {
684
685                return new NodeCreateRule(Node.ELEMENT_NODE);
686            } else if ("fragment".equals(nodeType)) {
687
688                return new NodeCreateRule(Node.DOCUMENT_FRAGMENT_NODE);
689            } else {
690
691                throw new RuntimeException(
692                        "Unrecognized node type: "
693                                + nodeType
694                                + ".  This attribute is optional or can have a value of element|fragment.");
695            }
696        }
697    }    
698    
699    /**
700     * Factory for creating a FactoryCreateRule
701     */
702    protected class FactoryCreateRuleFactory extends AbstractObjectCreationFactory {
703        public Object createObject(Attributes attributes) {
704            String className = attributes.getValue("classname");
705            String attrName = attributes.getValue("attrname");
706            boolean ignoreExceptions = 
707                "true".equalsIgnoreCase(attributes.getValue("ignore-exceptions"));
708            return (attrName == null || attrName.length() == 0) ?
709                new FactoryCreateRule( className, ignoreExceptions) :
710                new FactoryCreateRule( className, attrName, ignoreExceptions);
711        }
712    }
713    
714    /**
715     * Factory for creating a ObjectCreateRule
716     */
717    protected class ObjectCreateRuleFactory extends AbstractObjectCreationFactory {
718        public Object createObject(Attributes attributes) {
719            String className = attributes.getValue("classname");
720            String attrName = attributes.getValue("attrname");
721            return (attrName == null || attrName.length() == 0) ?
722                new ObjectCreateRule( className) :
723                new ObjectCreateRule( className, attrName);
724        }
725    }
726    
727    /**
728     * Factory for creating a SetPropertiesRule
729     */
730    protected class SetPropertiesRuleFactory extends AbstractObjectCreationFactory {
731        public Object createObject(Attributes attributes) {
732                return new SetPropertiesRule();
733        }
734    }
735    
736    /**
737     * Factory for creating a SetPropertyRule
738     */
739    protected class SetPropertyRuleFactory extends AbstractObjectCreationFactory {
740        public Object createObject(Attributes attributes) {
741            String name = attributes.getValue("name");
742            String value = attributes.getValue("value");
743            return new SetPropertyRule( name, value);
744        }
745    }
746    
747    /**
748     * Factory for creating a SetNestedPropertiesRule
749     */
750    protected class SetNestedPropertiesRuleFactory extends AbstractObjectCreationFactory {
751        public Object createObject(Attributes attributes) {
752           boolean allowUnknownChildElements = 
753                "true".equalsIgnoreCase(attributes.getValue("allow-unknown-child-elements"));
754                SetNestedPropertiesRule snpr = new SetNestedPropertiesRule();
755                snpr.setAllowUnknownChildElements( allowUnknownChildElements );
756                return snpr;
757        }
758    }
759    
760    /**
761     * Factory for creating a SetTopRuleFactory
762     */
763    protected class SetTopRuleFactory extends AbstractObjectCreationFactory {
764        public Object createObject(Attributes attributes) {
765            String methodName = attributes.getValue("methodname");
766            String paramType = attributes.getValue("paramtype");
767            return (paramType == null || paramType.length() == 0) ?
768                new SetTopRule( methodName) :
769                new SetTopRule( methodName, paramType);
770        }
771    }
772    
773    /**
774     * Factory for creating a SetNextRuleFactory
775     */
776    protected class SetNextRuleFactory extends AbstractObjectCreationFactory {
777        public Object createObject(Attributes attributes) {
778            String methodName = attributes.getValue("methodname");
779            String paramType = attributes.getValue("paramtype");
780            return (paramType == null || paramType.length() == 0) ?
781                new SetNextRule( methodName) :
782                new SetNextRule( methodName, paramType);
783        }
784    }
785    
786    /**
787     * Factory for creating a SetRootRuleFactory
788     */
789    protected class SetRootRuleFactory extends AbstractObjectCreationFactory {
790        public Object createObject(Attributes attributes) {
791            String methodName = attributes.getValue("methodname");
792            String paramType = attributes.getValue("paramtype");
793            return (paramType == null || paramType.length() == 0) ?
794                new SetRootRule( methodName) :
795                new SetRootRule( methodName, paramType);
796        }
797    }
798    
799    /**
800     * A rule for adding a attribute-property alias to the custom alias mappings of
801     * the containing SetPropertiesRule rule.
802     */
803    protected class SetPropertiesAliasRule extends Rule {
804        
805        /**
806         * <p>Base constructor.</p>
807         */
808        public SetPropertiesAliasRule() {
809            super();
810        }
811        
812        /**
813         * Add the alias to the SetPropertiesRule object created by the
814         * enclosing <set-properties-rule> tag.
815         */
816        public void begin(Attributes attributes) {
817            String attrName = attributes.getValue("attr-name");
818            String propName = attributes.getValue("prop-name");
819    
820            SetPropertiesRule rule = (SetPropertiesRule) digester.peek();
821            rule.addAlias(attrName, propName);
822        }
823    }
824
825    /**
826     * A rule for adding a attribute-property alias to the custom alias mappings of
827     * the containing SetNestedPropertiesRule rule.
828     */
829    protected class SetNestedPropertiesAliasRule extends Rule {
830        
831        /**
832         * <p>Base constructor.</p>
833         */
834        public SetNestedPropertiesAliasRule() {
835            super();
836        }
837        
838        /**
839         * Add the alias to the SetNestedPropertiesRule object created by the
840         * enclosing <set-nested-properties-rule> tag.
841         */
842        public void begin(Attributes attributes) {
843            String attrName = attributes.getValue("attr-name");
844            String propName = attributes.getValue("prop-name");
845    
846            SetNestedPropertiesRule rule = (SetNestedPropertiesRule) digester.peek();
847            rule.addAlias(attrName, propName);
848        }
849    }
850        
851}