Apache Digester XML анализатор аннотаций и составная модель

У меня есть следующий XML-документ, который я собираюсь проанализировать в объектную модель с помощью анализатора Apache Digester (с помощью аннотаций Digester):

<?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>

Как вы можете видеть из этого XML, есть два Criterion узлы двумя разными путями:

  1. Решение / Критерии / Criterion
  2. Решение / CriterionGroups / CriterionGroup / Критерии / Criterion

Это моя объектная модель:

@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;
    }

}

Прямо сейчас я могу только разобрать Decision/Criteria/Criterion но Decision/CriterionGroups/CriterionGroup/Criteria/Criterion все еще NULL, Как настроить мою модель и изменить аннотации, чтобы иметь возможность разбирать CriterionNode с двумя разными локациями?

Кроме того, я не понимаю, почему парсер находит два Criterion узлы вместо одного Decision/Criteria/Criterion:

введите описание изображения здесь

1 ответ

Решение

Я вижу две проблемы:

Во-первых, размещенный вами код соответствует только критериям, которые являются прямыми потомками решения. то есть вы выбрали "Решение / Критерии / Критерий", но не "Решение / Группа критериев / Группа критериев / Критерий / Критерий", поэтому более глубокие элементы никогда не создаются. Самое простое решение для этого просто использовать подстановочный знак:

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

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

Вторая проблема связана с SetNext Правило для CriterionNodeи этот немного сложнее. Чтобы продолжить, я думаю, что этот код должен работать для вас:

@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);
  }

}

Причиной этого является то, что аннотации создают правило set next.

Для следующего правила нужно три вещи:

  1. Шаблон.
  2. Имя метода.
  3. Тип параметра.

Так что эта аннотация пытается достичь, это эквивалент:

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

Обратите внимание, что ни владеющий DecisionNode ни CriterionGroupNode упоминаются в любом месте этого правила.

Название метода и тип параметра просты - они просто взяты из аннотированного метода - но шаблон менее ясен. Обработка аннотаций просматривает аннотации, соответствующие параметру, чтобы вывести шаблон, поэтому в этом случае параметр является CriterionNodeи это соответствует ObjectCreate аннотация для "*/ Критерии / Критерий", поэтому он создает желаемое правило.

Причина, по которой вам не нужна секунда SetNextRule в CriterionGroupNode класс будет повторять ту же самую обработку, поэтому будет добавлено дублирующее правило.

Примечание к аннотациям дигестора

Я добавлю к этому свой стандартный отказ от ответственности в отношении аннотаций Digester: лично я их не люблю и никогда не использую по двум причинам:

  1. Один из них более общий: я думаю, что аннотации следует использовать только в том случае, если они говорят что-то о самом классе, а не о том, как он используется, но это лишь мое личное мнение о чрезмерном использовании аннотаций.
  2. Специально для аннотаций Digester: они могут обслуживать только очень простые случаи, и даже такие простые вещи, как это вызывает проблемы, подобные приведенной выше.

Я считаю, что конфигурация на основе правил является самой простой для быстрых отображений, а также самой мощной, если вам нужно ее расширить. В этом случае что-то вроде:

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(...);

Обратите внимание, что расширенная версия BeanPropertySetterRule требуется только потому, что ваши сущности XML не соответствуют соглашениям Java Bean (свойство должно быть верблюжьим, поэтому getName и setName определяют свойство "name", а не "Name"). Так что, если в вашем XML используются строчные буквы, такие как "имя" и "описание", вы можете использовать более короткое:

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

Нет абсолютно никакой причины, по которой ваш XML должен следовать соглашениям Java Bean - я просто укажу на это, если вам интересно, зачем нужны расширенные версии.

ура

Другие вопросы по тегам