3

I have a following XML document which I'm going to parse into an object model with Apache Digester parser(via Digester annotations):

<?xml version="1.0" encoding="UTF-8"?>
<Decision>
    <Name>Antivirus software for Windows</Name>
    <Description>Description 1</Description>
    <Url>http://yahoo.com</Url>
    <ImageUrl>http://yahoo.com/img.jpg</ImageUrl>
    <CriterionGroups>
        <CriterionGroup>
            <Name>Windows</Name>
            <Description>Description 1</Description>
            <Criteria>
                <Criterion>
                    <Name>Heuristics</Name>
                    <Description>Description 1</Description>
                </Criterion>
            </Criteria>
        </CriterionGroup>
    </CriterionGroups>
    <Criteria>
        <Criterion>
            <Name>On-demand scan</Name>
            <Description>Description 1</Description>
        </Criterion>
    </Criteria>
    <CharacteristicGroups>
        <CharacteristicGroup>
            <Name>Windows</Name>
            <Description>Description 1</Description>
            <Characteristics>
                <Characteristic>
                    <Name>Country of origin</Name>
                    <Description>Description 1</Description>
                    <ValueType>String</ValueType>
                    <VisualMode>SelectBox</VisualMode>
                    <Sortable>true</Sortable>
                    <Options>
                        <Option>
                            <Value>Shareware</Value>
                            <Description>Description 1</Description>
                        </Option>
                    </Options>
                </Characteristic>
            </Characteristics>
        </CharacteristicGroup>
    </CharacteristicGroups>
    <Characteristics>
        <Characteristic>
            <Name>License</Name>
            <Description>Description 1</Description>
            <ValueType>Integer</ValueType>
            <VisualMode>Slider</VisualMode>
            <Sortable>false</Sortable>
        </Characteristic>
    </Characteristics>
    <Decisions>
        <Decision>
            <Name>Avast Free Antivirus</Name>
            <Description>Description 1</Description>
            <Url>http://google.com</Url>
            <ImageUrl>http://google.com/img.jpg</ImageUrl>
            <Votes>
                <Vote>
                    <CriterionName>On-demand scan</CriterionName>
                    <Weight>4.3</Weight>
                </Vote>
                <Vote>
                    <CriterionName>Heuristics</CriterionName>
                    <CriterionName>On-demand scan</CriterionName>
                    <Weight>4.3</Weight>
                    <Description>Description 1</Description>
                </Vote>
            </Votes>
            <Values>
                <Value>
                    <CharacteristicName>License</CharacteristicName>
                    <Value>Proprietary</Value>
                    <Description>Description 1</Description>
                </Value>
            </Values>
        </Decision>
    </Decisions>
</Decision>

As you can see from this XML there are two Criterion nodes by two different paths:

  1. Decision/Criteria/Criterion
  2. Decision/CriterionGroups/CriterionGroup/Criteria/Criterion

This my object model:

@ObjectCreate(pattern = "Decision")
public class DecisionNode {

    @BeanPropertySetter(pattern = "Decision/Name")
    private String name;
    @BeanPropertySetter(pattern = "Decision/Description")
    private String description;
    @BeanPropertySetter(pattern = "Decision/Url")
    private String url;
    @BeanPropertySetter(pattern = "Decision/ImageUrl")
    private String imageUrl;

    private List<CriterionGroupNode> criterionGroupNodes = new ArrayList<>();
    private List<CriterionNode> criterionNodes = new ArrayList<>();
    private List<CharacteristicGroupNode> characteristicGroupNodes = new ArrayList<>();
    private List<CharacteristicNode> characteristicNodes = new ArrayList<>();
    private List<DecisionNode> decisionNodes = new ArrayList<>();
    private List<VoteNode> voteNodes = new ArrayList<>();
    private List<ValueNode> valueNodes = new ArrayList<>();

    ....

    @SetNext
    public boolean addCriterionGroupNode(CriterionGroupNode criterionGroupNode) {
        return criterionGroupNodes.add(criterionGroupNode);
    }

    ....

}

@ObjectCreate(pattern = "Decision/CriterionGroups/CriterionGroup")
public class CriterionGroupNode {

    @BeanPropertySetter(pattern = "Decision/CriterionGroups/CriterionGroup/Name")
    private String name;
    @BeanPropertySetter(pattern = "Decision/CriterionGroups/CriterionGroup/Description")
    private String description;

    private List<CriterionNode> criterionNodes = new ArrayList<>();

    ....

    @SetNext
    public boolean addCriterionNode(CriterionNode criterionNode) {
        return criterionNodes.add(criterionNode);
    }

    ....

}

@ObjectCreate(pattern = "Decision/Criteria/Criterion")
public class CriterionNode {

    @BeanPropertySetter(pattern = "Decision/Criteria/Criterion/Name")
    private String name;
    @BeanPropertySetter(pattern = "Decision/Criteria/Criterion/Description")
    private String description;

    public CriterionNode() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

}

Right now I'm only able to parse Decision/Criteria/Criterion but Decision/CriterionGroups/CriterionGroup/Criteria/Criterion are still NULL. How to configure my model and change annotations in order to be able to parse CriterionNode with two different locations ?

Also, I don't understand why parser finds two Criterion nodes instead a single one by Decision/Criteria/Criterion:

enter image description here

4

1 回答 1

2

Two problems I can see:

Firstly, the code you have posted only matches Criteria which are direct children of a Decision. i.e. you have matched "Decision/Criteria/Criterion", but not "Decision/CriterionGroups/CriterionGroup/Criteria/Criterion", so the deeper elements never get created. The simplest solution to this is just to use a wildcard:

@ObjectCreate(pattern = "*/Criteria/Criterion")
public static class CriterionNode {

  @BeanPropertySetter(pattern = "*/Criteria/Criterion/Name")
  private String name;
  @BeanPropertySetter(pattern = "*/Criteria/Criterion/Description")
  private String description;

The second problem is with the SetNext rule for the CriterionNode, and this one is a bit trickier. To cut to the chase, I think this code should work for you:

@ObjectCreate(pattern = "Decision")
public class DecisionNode {

  ...

  @SetNext
  public boolean addCriterionNode(CriterionNode criterionNode) {
    return criterionNodes.add(criterionNode);
  }

}

@ObjectCreate(pattern = "Decision/CriterionGroups/CriterionGroup")
public class CriterionGroupNode {

  ...

  // no SetNext rule on this method
  public boolean addCriterionNode(CriterionNode criterionNode) {
    return criterionNodes.add(criterionNode);
  }

}

The reason this works is the way the annotations build the set next rule.

A set next rule needs three things:

  1. A pattern.
  2. A method name.
  3. A parameter type.

So what this annotation is trying to achieve is the equivalent of:

digester.addSetNext("*/Criteria/Criterion", "addCriterionNode", "CriterionNode")

Note that neither the owning DecisionNode nor CriterionGroupNode are mentioned anywhere in this rule.

The method name and parameter type are easy - they just come straight from the annotated method - but the pattern is less clear. The annotation processing looks at the annotations matching the parameter to deduce the pattern, so in this case the parameter is a CriterionNode, and that has a matching ObjectCreate annotation for "*/Criteria/Criterion", so it creates the desired rule.

The reason you don't need a second SetNextRule in the CriterionGroupNode class is it would replicate the exact same processing, so a duplicate rule would be added.

A Note on Digester Annotations

I'll add my standard disclaimer to this regarding Digester annotations: personally I don't like them and never use them for two reasons:

  1. One is more general: I think annotations should only be used where they say something about the class itself not about how it is used, but that's just my personal view on the over-use of annotations.
  2. Specifically to Digester annotations: they can only cater for extremely simple cases, and even something as easy as this causes problems like the one above.

I find the rules based configuration the simplest for quick mappings, and also the most powerful if you need to extend it. In this case, something like:

RulesModule rules = new AbstractRulesModule() {
  @Override
  public void configure() {

    forPattern("Decision")
        .createObject().ofType(DecisionNode.class);

    forPattern("Decision/Name").addRule(new BeanPropertySetterRule("name"));
    forPattern("Decision/Description").addRule(new BeanPropertySetterRule("description"));
    forPattern("Decision/Url").addRule(new BeanPropertySetterRule("url"));
    forPattern("Decision/ImageUrl").addRule(new BeanPropertySetterRule("imageUrl"));

    forPattern("Decision/CriterionGroups/CriterionGroup")
        .createObject().ofType(CriterionGroupNode.class)
        .then().setNext("addCriterionGroupNode");

    forPattern("Decision/CriterionGroups/CriterionGroup/Name").addRule(new BeanPropertySetterRule("name"));
    forPattern("Decision/CriterionGroups/CriterionGroup/Description").addRule(new BeanPropertySetterRule("description"));

    forPattern("*/Criterion")
        .createObject().ofType(CriterionNode.class)
        .then().setNext("addCriterionNode");

    forPattern("*/Criterion/Name").addRule(new BeanPropertySetterRule("name"));
    forPattern("*/Criterion/Description").addRule(new BeanPropertySetterRule("description"));

  }
};

DigesterLoader loader = DigesterLoader.newLoader(rules);
Digester digester = loader.newDigester();

DecisionNode dn = digester.parse(...);

Note that the extended version of the BeanPropertySetterRule is only required because your XML entities do not follow Java Bean conventions (the property must be lower-camel - so getName and setName define a property "name" not "Name"). So if your XML used lower case entities such as "name" and "description" then you could use the shorter:

forPattern("*/Criterion/Name").setBeanProperty();
forPattern("*/Criterion/Description").setBeanProperty();

There's absolutely no reason your XML should follow Java Bean conventions - I'm just pointing this out if you wondered why the extended versions were necessary.

Cheers

于 2016-12-30T03:00:04.853 回答