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