2
* ====================================================================
4
* The Apache Software License, Version 1.1
6
* Copyright (c) 1999-2003 The Apache Software Foundation. All rights
9
* Redistribution and use in source and binary forms, with or without
10
* modification, are permitted provided that the following conditions
13
* 1. Redistributions of source code must retain the above copyright
14
* notice, this list of conditions and the following disclaimer.
16
* 2. Redistributions in binary form must reproduce the above copyright
17
* notice, this list of conditions and the following disclaimer in
18
* the documentation and/or other materials provided with the
21
* 3. The end-user documentation included with the redistribution, if
22
* any, must include the following acknowlegement:
23
* "This product includes software developed by the
24
* Apache Software Foundation (http://www.apache.org/)."
25
* Alternately, this acknowlegement may appear in the software itself,
26
* if and wherever such third-party acknowlegements normally appear.
28
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
29
* Foundation" must not be used to endorse or promote products derived
30
* from this software without prior written permission. For written
31
* permission, please contact apache@apache.org.
33
* 5. Products derived from this software may not be called "Apache"
34
* nor may "Apache" appear in their names without prior written
35
* permission of the Apache Group.
37
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
49
* ====================================================================
51
* This software consists of voluntary contributions made by many
52
* individuals on behalf of the Apache Software Foundation. For more
53
* information on the Apache Software Foundation, please see
54
* <http://www.apache.org/>.
59
package org.apache.commons.digester.xmlrules;
62
import java.io.FileNotFoundException;
63
import java.io.IOException;
65
import java.util.ArrayList;
66
import java.util.HashSet;
67
import java.util.List;
69
import java.util.StringTokenizer;
71
import org.apache.commons.collections.ArrayStack;
72
import org.apache.commons.digester.AbstractObjectCreationFactory;
73
import org.apache.commons.digester.BeanPropertySetterRule;
74
import org.apache.commons.digester.CallMethodRule;
75
import org.apache.commons.digester.CallParamRule;
76
import org.apache.commons.digester.Digester;
77
import org.apache.commons.digester.FactoryCreateRule;
78
import org.apache.commons.digester.ObjectCreateRule;
79
import org.apache.commons.digester.Rule;
80
import org.apache.commons.digester.RuleSetBase;
81
import org.apache.commons.digester.Rules;
82
import org.apache.commons.digester.SetNextRule;
83
import org.apache.commons.digester.SetPropertiesRule;
84
import org.apache.commons.digester.SetPropertyRule;
85
import org.apache.commons.digester.SetTopRule;
86
import org.xml.sax.Attributes;
87
import org.xml.sax.SAXException;
91
* This is a RuleSet that parses XML into Digester rules, and then
92
* adds those rules to a 'target' Digester.
94
* @author David H. Martin - Initial Contribution
95
* @author Scott Sanders - Added ASL, removed external dependencies
96
* @author Bradley M. Handy - Bean Property Setter Rule addition
100
public class DigesterRuleParser extends RuleSetBase {
102
public static final String DIGESTER_PUBLIC_ID = "-//Jakarta Apache //DTD digester-rules XML V1.0//EN";
107
private String digesterDtdUrl;
110
* This is the digester to which we are adding the rules that we parse
111
* from the Rules XML document.
113
protected Digester targetDigester;
117
* A stack whose toString method returns a '/'-separated concatenation
118
* of all the elements in the stack.
120
protected class PatternStack extends ArrayStack {
121
public String toString() {
122
StringBuffer str = new StringBuffer();
123
for (int i = 0; i < size(); i++) {
124
String elem = get(i).toString();
125
if (elem.length() > 0) {
126
if (str.length() > 0) {
132
return str.toString();
137
* A stack used to maintain the current pattern. The Rules XML document
138
* type allows nesting of patterns. If an element defines a matching
139
* pattern, the resulting pattern is a concatenation of that pattern with
140
* all the ancestor elements' patterns. Hence the need for a stack.
142
protected PatternStack patternStack;
145
* Used to detect circular includes
147
private Set includedFiles = new HashSet();
150
* Constructs a DigesterRuleParser. This object will be inoperable
151
* until the target digester is set, via <code>setTarget(Digester)</code>
153
public DigesterRuleParser() {
154
patternStack = new PatternStack();
158
* Constructs a rule set for converting XML digester rule descriptions
159
* into Rule objects, and adding them to the given Digester
160
* @param targetDigester the Digester to add the rules to
162
public DigesterRuleParser(Digester targetDigester) {
163
this.targetDigester = targetDigester;
164
patternStack = new PatternStack();
168
* Constructs a rule set for parsing an XML digester rule file that
169
* has been included within an outer XML digester rule file. In this
170
* case, we must pass the pattern stack and the target digester
171
* to the rule set, as well as the list of files that have already
172
* been included, for cycle detection.
173
* @param targetDigester the Digester to add the rules to
174
* @param stack Stack containing the prefix pattern string to be prepended
175
* to any pattern parsed by this rule set.
177
private DigesterRuleParser(Digester targetDigester,
178
PatternStack stack, Set includedFiles) {
179
this.targetDigester = targetDigester;
180
patternStack = stack;
181
this.includedFiles = includedFiles;
185
* Sets the digester into which to add the parsed rules
186
* @param d the Digester to add the rules to
188
public void setTarget(Digester d) {
193
* Sets the location of the digester rules DTD. This is the DTD used
194
* to validate the rules XML file.
196
public void setDigesterRulesDTD(String dtdURL) {
197
digesterDtdUrl = dtdURL;
201
* Returns the location of the DTD used to validate the digester rules
204
protected String getDigesterRulesDTD() {
205
//ClassLoader classLoader = getClass().getClassLoader();
206
//URL url = classLoader.getResource(DIGESTER_DTD_PATH);
207
//return url.toString();
208
return digesterDtdUrl;
212
* Adds a rule the the target digester. After a rule has been created by
213
* parsing the XML, it is added to the digester by calling this method.
214
* Typically, this method is called via reflection, when executing
215
* a SetNextRule, from the Digester that is parsing the rules XML.
216
* @param rule a Rule to add to the target digester.
218
public void add(Rule rule) {
219
targetDigester.addRule(patternStack.toString(), rule);
224
* Add to the given digester the set of Rule instances used to parse an XML
225
* document defining Digester rules. When the digester parses an XML file,
226
* it will add the resulting rules & patterns to the 'target digester'
227
* that was passed in this RuleSet's constructor.<P>
228
* If you extend this class to support additional rules, your implementation
229
* should of this method should call this implementation first: i.e.
230
* <code>super.addRuleInstances(digester);</code>
232
public void addRuleInstances(Digester digester) {
233
final String ruleClassName = Rule.class.getName();
234
digester.register(DIGESTER_PUBLIC_ID, getDigesterRulesDTD());
236
digester.addRule("*/pattern", new PatternRule("value"));
238
digester.addRule("*/include", new IncludeRule());
240
digester.addFactoryCreate("*/bean-property-setter-rule", new BeanPropertySetterRuleFactory());
241
digester.addRule("*/bean-property-setter-rule", new PatternRule("pattern"));
242
digester.addSetNext("*/bean-property-setter-rule", "add", ruleClassName);
244
digester.addFactoryCreate("*/call-method-rule", new CallMethodRuleFactory());
245
digester.addRule("*/call-method-rule", new PatternRule("pattern"));
246
digester.addSetNext("*/call-method-rule", "add", ruleClassName);
248
digester.addFactoryCreate("*/call-param-rule", new CallParamRuleFactory());
249
digester.addRule("*/call-param-rule", new PatternRule("pattern"));
250
digester.addSetNext("*/call-param-rule", "add", ruleClassName);
252
digester.addFactoryCreate("*/factory-create-rule", new FactoryCreateRuleFactory());
253
digester.addRule("*/factory-create-rule", new PatternRule("pattern"));
254
digester.addSetNext("*/factory-create-rule", "add", ruleClassName);
256
digester.addFactoryCreate("*/object-create-rule", new ObjectCreateRuleFactory());
257
digester.addRule("*/object-create-rule", new PatternRule("pattern"));
258
digester.addSetNext("*/object-create-rule", "add", ruleClassName);
260
digester.addFactoryCreate("*/set-properties-rule", new SetPropertiesRuleFactory());
261
digester.addRule("*/set-properties-rule", new PatternRule("pattern"));
262
digester.addSetNext("*/set-properties-rule", "add", ruleClassName);
264
digester.addRule("*/set-properties-rule/alias", new SetPropertiesAliasRule());
266
digester.addFactoryCreate("*/set-property-rule", new SetPropertyRuleFactory());
267
digester.addRule("*/set-property-rule", new PatternRule("pattern"));
268
digester.addSetNext("*/set-property-rule", "add", ruleClassName);
270
digester.addFactoryCreate("*/set-top-rule", new SetTopRuleFactory());
271
digester.addRule("*/set-top-rule", new PatternRule("pattern"));
272
digester.addSetNext("*/set-top-rule", "add", ruleClassName);
274
digester.addFactoryCreate("*/set-next-rule", new SetNextRuleFactory());
275
digester.addRule("*/set-next-rule", new PatternRule("pattern"));
276
digester.addSetNext("*/set-next-rule", "add", ruleClassName);
281
* A rule for extracting the pattern matching strings from the rules XML.
282
* In the digester-rules document type, a pattern can either be declared
283
* in the 'value' attribute of a <pattern> element (in which case the pattern
284
* applies to all rules elements contained within the <pattern> element),
285
* or it can be declared in the optional 'pattern' attribute of a rule
288
private class PatternRule extends Rule {
290
private String attrName;
291
private String pattern = null;
294
* @param digester the Digester used to parse the rules XML file
295
* @param attrName The name of the attribute containing the pattern
297
public PatternRule(String attrName) {
299
this.attrName = attrName;
303
* If a pattern is defined for the attribute, push it onto the
306
public void begin(Attributes attributes) {
307
pattern = attributes.getValue(attrName);
308
if (pattern != null) {
309
patternStack.push(pattern);
314
* If there was a pattern for this element, pop it off the pattern
318
if (pattern != null) {
325
* A rule for including one rules XML file within another. Included files
326
* behave as if they are 'macro-expanded' within the includer. This means
327
* that the values of the pattern stack are prefixed to every pattern
328
* in the included rules. <p>This rule will detect 'circular' includes,
329
* which would result in infinite recursion. It throws a
330
* CircularIncludeException when a cycle is detected, which will terminate
333
private class IncludeRule extends Rule {
334
public IncludeRule() {
339
* To include a rules xml file, we instantiate another Digester, and
340
* another DigesterRulesRuleSet. We pass the
341
* pattern stack and the target Digester to the new rule set, and
342
* tell the Digester to parse the file.
344
public void begin(Attributes attributes) throws Exception {
345
// The path attribute gives the URI to another digester rules xml file
346
String fileName = attributes.getValue("path");
347
if (fileName != null && fileName.length() > 0) {
348
includeXMLRules(fileName);
351
// The class attribute gives the name of a class that implements
352
// the DigesterRulesSource interface
353
String className = attributes.getValue("class");
354
if (className != null && className.length() > 0) {
355
includeProgrammaticRules(className);
360
* Creates another DigesterRuleParser, and uses it to extract the rules
361
* out of the give XML file. The contents of the current pattern stack
362
* will be prepended to all of the pattern strings parsed from the file.
364
private void includeXMLRules(String fileName)
365
throws IOException, SAXException, CircularIncludeException {
366
URL fileURL = DigesterRuleParser.this.getClass().getClassLoader().getResource(fileName);
367
if (fileURL == null) {
368
throw new FileNotFoundException("File \"" + fileName + "\" not found.");
370
fileName = fileURL.toExternalForm();
371
if (includedFiles.add(fileName) == false) {
372
// circular include detected
373
throw new CircularIncludeException(fileName);
375
// parse the included xml file
376
DigesterRuleParser includedSet =
377
new DigesterRuleParser(targetDigester, patternStack, includedFiles);
378
includedSet.setDigesterRulesDTD(getDigesterRulesDTD());
379
Digester digester = new Digester();
380
digester.addRuleSet(includedSet);
381
digester.push(DigesterRuleParser.this);
382
digester.parse(fileName);
383
includedFiles.remove(fileName);
387
* Creates an instance of the indicated class. The class must implement
388
* the DigesterRulesSource interface. Passes the target digester to
389
* that instance. The DigesterRulesSource instance is supposed to add
390
* rules into the digester. The contents of the current pattern stack
391
* will be automatically prepended to all of the pattern strings added
392
* by the DigesterRulesSource instance.
394
private void includeProgrammaticRules(String className)
395
throws ClassNotFoundException, ClassCastException,
396
InstantiationException, IllegalAccessException {
398
Class cls = Class.forName(className);
399
DigesterRulesSource rulesSource = (DigesterRulesSource) cls.newInstance();
401
// wrap the digester's Rules object, to prepend pattern
402
Rules digesterRules = targetDigester.getRules();
403
Rules prefixWrapper =
404
new RulesPrefixAdapter(patternStack.toString(), digesterRules);
406
targetDigester.setRules(prefixWrapper);
408
rulesSource.getRules(targetDigester);
410
// Put the unwrapped rules back
411
targetDigester.setRules(digesterRules);
418
* Wraps a Rules object. Delegates all the Rules interface methods
419
* to the underlying Rules object. Overrides the add method to prepend
420
* a prefix to the pattern string.
422
private class RulesPrefixAdapter implements Rules {
424
private Rules delegate;
425
private String prefix;
428
* @param patternPrefix the pattern string to prepend to the pattern
429
* passed to the add method.
430
* @param rules The wrapped Rules object. All of this class's methods
431
* pass through to this object.
433
public RulesPrefixAdapter(String patternPrefix, Rules rules) {
434
prefix = patternPrefix;
439
* Register a new Rule instance matching a pattern which is constructed
440
* by concatenating the pattern prefix with the given pattern.
442
public void add(String pattern, Rule rule) {
443
delegate.add(prefix + pattern, rule);
447
* This method passes through to the underlying Rules object.
449
public void clear() {
454
* This method passes through to the underlying Rules object.
456
public Digester getDigester() {
457
return delegate.getDigester();
461
* This method passes through to the underlying Rules object.
463
public String getNamespaceURI() {
464
return delegate.getNamespaceURI();
468
* @deprecated Call match(namespaceURI,pattern) instead.
470
public List match(String pattern) {
471
return delegate.match(pattern);
475
* This method passes through to the underlying Rules object.
477
public List match(String namespaceURI, String pattern) {
478
return delegate.match(namespaceURI, pattern);
482
* This method passes through to the underlying Rules object.
484
public List rules() {
485
return delegate.rules();
489
* This method passes through to the underlying Rules object.
491
public void setDigester(Digester digester) {
492
delegate.setDigester(digester);
496
* This method passes through to the underlying Rules object.
498
public void setNamespaceURI(String namespaceURI) {
499
delegate.setNamespaceURI(namespaceURI);
504
///////////////////////////////////////////////////////////////////////
505
// Classes beyond this point are ObjectCreationFactory implementations,
506
// used to create Rule objects and initialize them from SAX attributes.
507
///////////////////////////////////////////////////////////////////////
510
* Factory for creating a BeanPropertySetterRule.
512
private class BeanPropertySetterRuleFactory extends AbstractObjectCreationFactory {
513
public Object createObject(Attributes attributes) throws Exception {
514
Rule beanPropertySetterRule = null;
515
String propertyname = attributes.getValue("propertyname");
517
if (propertyname == null) {
518
// call the setter method corresponding to the element name.
519
beanPropertySetterRule = new BeanPropertySetterRule();
521
beanPropertySetterRule = new BeanPropertySetterRule(propertyname);
524
return beanPropertySetterRule;
530
* Factory for creating a CallMethodRule.
532
protected class CallMethodRuleFactory extends AbstractObjectCreationFactory {
533
public Object createObject(Attributes attributes) {
534
Rule callMethodRule = null;
535
String methodName = attributes.getValue("methodname");
536
if (attributes.getValue("paramcount") == null) {
537
// call against empty method
538
callMethodRule = new CallMethodRule(methodName);
541
int paramCount = Integer.parseInt(attributes.getValue("paramcount"));
543
String paramTypesAttr = attributes.getValue("paramtypes");
544
if (paramTypesAttr == null || paramTypesAttr.length() == 0) {
545
callMethodRule = new CallMethodRule(methodName, paramCount);
547
// Process the comma separated list or paramTypes
548
// into an array of String class names
549
ArrayList paramTypes = new ArrayList();
550
StringTokenizer tokens = new StringTokenizer(paramTypesAttr, " \t\n\r,");
551
while (tokens.hasMoreTokens()) {
552
paramTypes.add(tokens.nextToken());
554
callMethodRule = new CallMethodRule( methodName,
556
(String[])paramTypes.toArray(new String[0]));
559
return callMethodRule;
564
* Factory for creating a CallParamRule.
566
protected class CallParamRuleFactory extends AbstractObjectCreationFactory {
568
public Object createObject(Attributes attributes) {
569
// create callparamrule
570
int paramIndex = Integer.parseInt(attributes.getValue("paramnumber"));
571
String attributeName = attributes.getValue("attrname");
572
String fromStack = attributes.getValue("from-stack");
573
Rule callParamRule = null;
574
if (attributeName == null) {
575
if (fromStack == null) {
577
callParamRule = new CallParamRule( paramIndex );
581
callParamRule = new CallParamRule( paramIndex, Boolean.valueOf(fromStack).booleanValue());
585
if (fromStack == null) {
587
callParamRule = new CallParamRule( paramIndex, attributeName );
591
// specifying both from-stack and attribute name is not allowed
592
throw new RuntimeException("Attributes from-stack and attrname cannot both be present.");
595
return callParamRule;
600
* Factory for creating a FactoryCreateRule
602
protected class FactoryCreateRuleFactory extends AbstractObjectCreationFactory {
603
public Object createObject(Attributes attributes) {
604
String className = attributes.getValue("classname");
605
String attrName = attributes.getValue("attrname");
606
boolean ignoreExceptions =
607
"true".equalsIgnoreCase(attributes.getValue("ignore-exceptions"));
608
return (attrName == null || attrName.length() == 0) ?
609
new FactoryCreateRule( className, ignoreExceptions)
611
new FactoryCreateRule( className, attrName, ignoreExceptions);
616
* Factory for creating a ObjectCreateRule
618
protected class ObjectCreateRuleFactory extends AbstractObjectCreationFactory {
619
public Object createObject(Attributes attributes) {
620
String className = attributes.getValue("classname");
621
String attrName = attributes.getValue("attrname");
622
return (attrName == null || attrName.length() == 0) ?
623
new ObjectCreateRule( className)
625
new ObjectCreateRule( className, attrName);
630
* Factory for creating a SetPropertiesRule
632
protected class SetPropertiesRuleFactory extends AbstractObjectCreationFactory {
633
public Object createObject(Attributes attributes) {
634
return new SetPropertiesRule();
639
* Factory for creating a SetPropertyRule
641
protected class SetPropertyRuleFactory extends AbstractObjectCreationFactory {
642
public Object createObject(Attributes attributes) {
643
String name = attributes.getValue("name");
644
String value = attributes.getValue("value");
645
return new SetPropertyRule( name, value);
650
* Factory for creating a SetTopRuleFactory
652
protected class SetTopRuleFactory extends AbstractObjectCreationFactory {
653
public Object createObject(Attributes attributes) {
654
String methodName = attributes.getValue("methodname");
655
String paramType = attributes.getValue("paramtype");
656
return (paramType == null || paramType.length() == 0) ?
657
new SetTopRule( methodName)
659
new SetTopRule( methodName, paramType);
664
* Factory for creating a SetNextRuleFactory
666
protected class SetNextRuleFactory extends AbstractObjectCreationFactory {
667
public Object createObject(Attributes attributes) {
668
String methodName = attributes.getValue("methodname");
669
String paramType = attributes.getValue("paramtype");
670
return (paramType == null || paramType.length() == 0) ?
671
new SetNextRule( methodName)
673
new SetNextRule( methodName, paramType);
678
* A rule for adding a attribute-property alias to the custom alias mappings of
679
* the containing SetPropertiesRule rule.
681
protected class SetPropertiesAliasRule extends Rule {
684
* <p>Base constructor.
686
* @param digester the Digester used to parse the rules XML file
688
public SetPropertiesAliasRule() {
693
* Add the alias to the SetPropertiesRule object created by the
694
* enclosing <set-properties-rule> tag.
696
public void begin(Attributes attributes) {
697
String attrName = attributes.getValue("attr-name");
698
String propName = attributes.getValue("prop-name");
700
SetPropertiesRule rule = (SetPropertiesRule) digester.peek();
701
rule.addAlias(attrName, propName);