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
узлы двумя разными путями:
- Решение / Критерии / Criterion
- Решение / 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.
Для следующего правила нужно три вещи:
- Шаблон.
- Имя метода.
- Тип параметра.
Так что эта аннотация пытается достичь, это эквивалент:
digester.addSetNext("*/Criteria/Criterion", "addCriterionNode", "CriterionNode")
Обратите внимание, что ни владеющий DecisionNode
ни CriterionGroupNode
упоминаются в любом месте этого правила.
Название метода и тип параметра просты - они просто взяты из аннотированного метода - но шаблон менее ясен. Обработка аннотаций просматривает аннотации, соответствующие параметру, чтобы вывести шаблон, поэтому в этом случае параметр является CriterionNode
и это соответствует ObjectCreate
аннотация для "*/ Критерии / Критерий", поэтому он создает желаемое правило.
Причина, по которой вам не нужна секунда SetNextRule
в CriterionGroupNode
класс будет повторять ту же самую обработку, поэтому будет добавлено дублирующее правило.
Примечание к аннотациям дигестора
Я добавлю к этому свой стандартный отказ от ответственности в отношении аннотаций Digester: лично я их не люблю и никогда не использую по двум причинам:
- Один из них более общий: я думаю, что аннотации следует использовать только в том случае, если они говорят что-то о самом классе, а не о том, как он используется, но это лишь мое личное мнение о чрезмерном использовании аннотаций.
- Специально для аннотаций 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 - я просто укажу на это, если вам интересно, зачем нужны расширенные версии.
ура