@XmlJavaTypeAdapter没有调用TypeAdapter

时间:2016-01-26 10:53:12

标签: java xml jaxb2

我有一个已经被其他应用程序使用的固定XML结构。其中一些是第三方,因此更改XML不是一种选择。

XML包含一个我正在努力解组的部分。下面是一个减少版本。此元素是其他元素的子元素。

<premium>
    <allowInstalments>true</allowInstalments>
    <annualPremium>2964.23</annualPremium>

    <!-- other various elements -->
    <calcElement partname="driver">
        <driverXs>300.00</driverXs>
        <seq>1</seq>
    </calcElement>
    <calcElement partname="ratingData">
        <baseMiles>6000</baseMiles>
        <vehicleGroup>15</vehicleGroup>
        <documentVersion>4</documentVersion>
    </calcElement>
</premium>

为了测试这个解组正确(和编组,但我现在正试图解组),我写了以下测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RegressionApplication.class})
public class AdaptedCalcElementTest {


    @Autowired
    @Qualifier(value = "unmarshaller")
    private Unmarshaller unmarshaller;

    @Test
    public void canUnmarshallIntoDriverCalcElement() throws Exception {
        String xml = "<wrapper><calcElement partname=\"driver\">" +
                "<driverXs>300.00</driverXs>" +
                "<seq>1</seq>" +
                "</calcElement></wrapper>";

        CalcElementWrapper calcElementWrapper = (CalcElementWrapper) unmarshaller.unmarshal(Input.from(xml).build());
        assertThat(calcElementWrapper, notNullValue());
        assertThat(calcElementWrapper.listElements, notNullValue());
        assertThat(calcElementWrapper.listElements, hasSize(1));
        CalcElement calcElement = calcElementWrapper.listElements.get(0);
        assertThat(calcElement, instanceOf(DriverCalcElement.class));
    }

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlRootElement(name = "wrapper")
    public static class CalcElementWrapper {
        @XmlJavaTypeAdapter(CalcElementAdapter.class)
        public List<CalcElement> listElements;
    }
}

我的Adapter类根据pathname属性创建正确的CalcElement扩展类:

public class CalcElementAdapter extends XmlAdapter<CalcElementAdapter.AdaptedCalcElement, CalcElement> {

    @Override
    public CalcElement unmarshal(CalcElementAdapter.AdaptedCalcElement v) throws Exception {
        if (v.partname.equalsIgnoreCase("driver")) {
            DriverCalcElement calcElement = new DriverCalcElement();
            calcElement.setPartname(v.partname);
            calcElement.setDriverXs(new BigDecimal(v.driverXs));
            calcElement.setSeq(new Integer(v.seq));
            return calcElement;
        } else if (v.partname.equalsIgnoreCase("ratingData")) {
            RatingDataCalcElement calcElement = new RatingDataCalcElement();
            calcElement.setBaseMiles(new Integer(v.baseMiles));
            calcElement.setDocumentVersion(new Integer(v.documentVersion));
            calcElement.setVehicleGroup(new Integer(v.vehicleGroup));
            return calcElement;
        }

        return null;
    }

    @Override
    public CalcElementAdapter.AdaptedCalcElement marshal(CalcElement v) throws Exception {
        return null;
    }

    public static class AdaptedCalcElement {

        @XmlAttribute
        public String partname;

        public String driverXs;
        public String seq;
        public String baseMiles;
        public String vehicleGroup;
        public String documentVersion;
    }
}

CalcElement和派生类定义如下:

public abstract class CalcElement {

    private String partname;

    @XmlAttribute
    public String getPartname() {
        return partname;
    }

    public void setPartname(String partname) {
        this.partname = partname;
    }
}

public class DriverCalcElement extends CalcElement {
    private BigDecimal driverXs;
    private Integer seq;

    public BigDecimal getDriverXs() {
        return driverXs;
    }

    public void setDriverXs(BigDecimal driverXs) {
        this.driverXs = driverXs;
    }

    public Integer getSeq() {
        return seq;
    }

    public void setSeq(Integer seq) {
        this.seq = seq;
    }
}

public class RatingDataCalcElement extends CalcElement {
    private Integer baseMiles;
    private Integer vehicleGroup;
    private Integer documentVersion;

    public Integer getBaseMiles() {
        return baseMiles;
    }

    public void setBaseMiles(Integer baseMiles) {
        this.baseMiles = baseMiles;
    }

    public Integer getVehicleGroup() {
        return vehicleGroup;
    }

    public void setVehicleGroup(Integer vehicleGroup) {
        this.vehicleGroup = vehicleGroup;
    }

    public Integer getDocumentVersion() {
        return documentVersion;
    }

    public void setDocumentVersion(Integer documentVersion) {
        this.documentVersion = documentVersion;
    }
}

unmarshaller配置如下:

@Bean(name = "unmarshaller")
Unmarshaller getUnmarshaller() {
    return getJaxb2Marshaller();
}

private Jaxb2Marshaller getJaxb2Marshaller() {
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setPackagesToScan("com.itb.lego.regression");
    return marshaller;
}

我试图关注@ blaise-doughan的博客文章,但我看不出我错过了什么。

但是,测试失败,因为它没有为calcElement元素调用适配器。我错过了什么?

1 个答案:

答案 0 :(得分:0)

我发现除了告诉JaxB如何解组CalcElement之外我还要走得更远。

我的第一步是创建一个可以从任何类型的CalcElement解组的类。

@XmlRootElement(name = "calcElement")
public class AdaptableCalcElement {

    @XmlAttribute
    public String partname;

    @XmlElement
    public String driverXs;
    @XmlElement
    public String seq;
    @XmlElement
    public String baseMiles;
    @XmlElement
    public String vehicleGroup;
    @XmlElement
    public String documentVersion;
}

所有元素都是字符串,因为这只是一个过渡类,但它将保存所有可用数据,以允许我编写逻辑来创建特定的CalcElement派生类。

因为calcElement元素没有包含在包含元素中,并且两个包含类之间的差异是直接子元素。这意味着我需要2个不同的包装类。一个用于保存AdaptableCalcElement列表,然后保存最终版本以保存CalcElement列表。

这给了我以下两个课程:

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlRootElement(name = "premium")
public class AdaptablePremium extends BasePremium {

    private List<AdaptableCalcElement> calcElements;

    @XmlElement(name = "calcElement")
    public List<AdaptableCalcElement> getCalcElements() {
        return calcElements;
    }

    public void setCalcElements(List<AdaptableCalcElement> calcElements) {
        this.calcElements = calcElements;
    }
}

public class Premium extends BasePremium {

    List<CalcElement> calcElements;

    public List<CalcElement> getCalcElements() {
        return calcElements;
    }

    public void setCalcElements(List<CalcElement> calcElements) {
        this.calcElements = calcElements;
    }
}

这两个都是从BasePremium扩展而来的,它包含了两者共有的所有字段。

此时我可以将XML解组为AdaptablePremium。高级XML仅在包含XML的情况下有效,这允许我们指定容器(Details)包含Premium,以及如何解组它。

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "detail")
public class Detail {
    // All other fields for this class   

    @XmlJavaTypeAdapter(PremiumAdapter.class)
    private Premium premium;
}

现在这意味着当我解组详细信息元素时,它将使用此类并尝试使用PremiumAdapter类解组高级元素。 PremiumAdapter类定义了如何将AdaptablePremium转换为Premium。 BasePremium有很多成员(非常糟糕的XML结构,但它是固定的),因此我不是复制每个成员,而是使用反射来复制除CalcElements之外的所有内容,然后明确地执行此操作。我留下了以下XmlAdapter类

@Component
public class PremiumAdapter extends XmlAdapter<AdaptablePremium, Premium> {

    @Override
    public Premium unmarshal(AdaptablePremium adaptablePremium) throws Exception {
        Premium premium = new Premium();
        Class<?> premiumClass = premium.getClass();
        Class<?> adaptablePremiumClass = adaptablePremium.getClass();

        ReflectionUtils.doWithFields(Premium.class,
                field -> {
                    String fieldName = capitalise(field.getName());
                    Class<?> type = field.getType();
                    try {
                        Method getter = adaptablePremiumClass.getMethod("get" + fieldName);
                        Method setter = premiumClass.getMethod("set" + fieldName, type);
                        String value = (String) ReflectionUtils.invokeMethod(getter, adaptablePremium);
                        if (type.equals(String.class)) {
                            ReflectionUtils.invokeMethod(setter, premium, value);
                        } else if (type.equals(BigDecimal.class)) {
                            ReflectionUtils.invokeMethod(setter, premium, new BigDecimal(value));
                        }
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                },
                field -> !field.getName().equalsIgnoreCase("calcElements")
        );

        adaptCalcElements(adaptablePremium, premium);

        return premium;
    }

    public String capitalise(String name) {
        return name.substring(0, 1).toUpperCase() + name.substring(1);
    }

    public void adaptCalcElements(AdaptablePremium adaptablePremium, Premium premium) {
        List<CalcElement> calcElements = new ArrayList<>();

        for(AdaptableCalcElement adaptableCalcElement : adaptablePremium.getCalcElements()) {
            if (adaptableCalcElement.partname.equalsIgnoreCase("driver")) {
                DriverCalcElement calcElement = new DriverCalcElement();
                calcElement.setPartname(adaptableCalcElement.partname);
                calcElement.setDriverXs(new BigDecimal(adaptableCalcElement.driverXs));
                calcElement.setSeq(new Integer(adaptableCalcElement.seq));
                calcElements.add(calcElement);
            } else if (adaptableCalcElement.partname.equalsIgnoreCase("ratingData")) {
                RatingDataCalcElement calcElement = new RatingDataCalcElement();
                calcElement.setPartname(adaptableCalcElement.partname);
                calcElement.setBaseMiles(new Integer(adaptableCalcElement.baseMiles));
                calcElement.setDocumentVersion(new Integer(adaptableCalcElement.documentVersion));
                calcElement.setVehicleGroup(new Integer(adaptableCalcElement.vehicleGroup));
                calcElements.add(calcElement);
            }
        }
        premium.setCalcElements(calcElements);
    }

    @Override
    public AdaptablePremium marshal(Premium v) throws Exception {
        // Currently unimplemented
        return null;
    }
}

我还有一些重构要做,但现在可以正确地将xml解组为正确的类型。