XmlAdapter не работает должным образом в JAXB RI - PullRequest
2 голосов
/ 04 ноября 2011

Я пытаюсь реализовать XmlAdapter для изменения маршалинга / демаршаллинга некоторых свойств объекта. В частности, я попробовал с NullStringAdapter, описанным здесь:

Jaxb: xs: атрибут нулевые значения

Цель NullStringAdapter - сортировать нулевые значения как пустые строки и наоборот.

Единственное отличие от описанного выше примера и моего кода в том, что я хочу применить адаптер к элементу, а не к атрибуту, поэтому у меня есть:

@XmlElement
@XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
    return someValue;  //someValue could be null, in that case the adapter should marshall it as an empty string
}

Однако после некоторой отладки я понял, что методы Adapter никогда не вызываются при маршалинге из Java в XML !. Это происходит, когда значение XmlElement равно нулю. Когда это значение отличается от нуля, методы адаптера вызываются, как и ожидалось.

Спасибо за любую помощь!

Ответы [ 2 ]

5 голосов
/ 08 ноября 2011

Примечание: Я - EclipseLink JAXB (MOXy) , и член JAXB 2 (JSR-222) экспертная группа.

Однако после некоторой отладки я понял, что методы Adapter никогда не вызываются при маршалинге из Java в XML !.Это происходит, когда значение XmlElement равно нулю.Когда это значение отличается от нуля, методы адаптера вызываются как и ожидалось.

Это поведение варьируется в зависимости от реализации JAXB.Референтная реализация JAXB не будет вызывать метод маршала для XmlAdapter, когда поле / свойство имеет значение NULL, но MOXy будет.

Что говорится в спецификации JAXB (раздел 5.5.1 Простое свойство)

Метод get or is возвращает значение свойства, указанное в предыдущем подразделе.Если возвращается значение null, свойство считается отсутствующим в представляемом им XML-контенте.

Интерпретация MOXy этого оператора заключается в том, что значение поля / свойства действительно равно значению после егопрошел через XmlAdapter.Это необходимо для поддержки поведения , которое ищет Серхио .

3 голосов
/ 04 ноября 2011

Конечно, адаптер никогда не будет вызван, если на входе нет элемента, который бы инициировал это действие. В этом примере, с которым вы связаны, происходит то, что представлен атрибут с пустым значением:

<element att="" />

Ключевым моментом здесь является то, что является атрибутом att, но имеет пустую строку. Таким образом, демаршаллер JAXB представит это сеттеру. Но, поскольку на нем объявлен адаптер, он пройдет через него и получит нулевое значение.

Но если бы у вас было это

<element />

это другая история. Нет атрибута att, поэтому никогда не потребуется вызывать сеттер.

Существует разница между элементом, который возникает, но не имеет содержимого, и полным отсутствием элемента. Первый может в основном рассматриваться как содержащий пустую строку, но последний просто «не существует».

РЕДАКТИРОВАТЬ: испытания с этими классами ...

Bean.java

package jaxbadapter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name="Test")
public class Bean {

    @XmlElement
    @XmlJavaTypeAdapter(NullStringAdapter.class)
    private String someValue;

    public Bean() {
    }

    public String getSomeValue() {
        return someValue;
    }

    public void setSomeValue(final String someValue) {
        this.someValue = someValue;
    }

}

NullStringAdapter.java

package jaxbadapter;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class NullStringAdapter extends XmlAdapter<String, String> {

    @Override
    public String unmarshal(final String v) throws Exception {
        if("".equals(v)) {
            return null;
        }
        return v;
    }

    @Override
    public String marshal(final String v) throws Exception {
        if(null == v) {
            return "";
        }
        return v;
    }

}

ObjectFactory.java

package jaxbadapter;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;


@XmlRegistry
public class ObjectFactory {

    public ObjectFactory() {
    }

    public Bean createBean() {
        return new Bean();
    }

    @XmlElementDecl(name = "Test")
    public JAXBElement<Bean> createTest(Bean value) {
        return new JAXBElement<>(new QName("Test"), Bean.class, null, value);
    }

}

Main.java

package jaxbadapter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.transform.stream.StreamResult;

public class Main {

    public static void main(String[] args) throws Exception {

        final JAXBContext context = JAXBContext.newInstance("jaxbadapter");

        final Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

        final ObjectFactory of = new ObjectFactory();

        final Bean b1 = new Bean();

        final Bean b2 = new Bean();
        b2.setSomeValue(null);

        final Bean b3 = new Bean();
        b3.setSomeValue("");

        m.marshal(of.createTest(b1), System.out);
        System.out.println("");

        m.marshal(of.createTest(b2), System.out);
        System.out.println("");

        m.marshal(of.createTest(b3), System.out);
        System.out.println("");


    }

}

Это вывод:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test>
    <someValue></someValue>
</Test>

На самом деле меня немного удивило. Затем я попытался изменить геттер на return someValue == null ? "" : someValue; безрезультатно. Затем установите точку останова на получателе и обнаружите, что она никогда не вызывается.

Очевидно, JAXB использует отражение, чтобы попытаться получить значение, а не проходить через установщик при использовании XmlAccessType.FIELD. Hardcore. Теперь вы можете обойти это, используя вместо этого XmlAccessType.PROPERTY и пометив либо получатель, либо установщик ...

package jaxbadapter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(name="Test")
public class Bean {

    private String someValue;

    public Bean() {
    }

    @XmlElement
    @XmlJavaTypeAdapter(NullStringAdapter.class)
    public String getSomeValue() {
        return someValue;
    }

    public void setSomeValue(final String someValue) {
        this.someValue = someValue;
    }

}

... но это все равно не помогло. Метод marshal адаптера вызывался только один раз, в последнем тестовом примере, где была задана пустая строка. Очевидно, он сначала вызывает метод получения, а когда он возвращает значение NULL, он просто полностью пропускает содержимое адаптера.

Единственное решение, которое я могу придумать, - это просто полностью отказаться от использования адаптера здесь и поместить подстановку в геттер, убедившись, что используется XmlAccessType.PROPERTY:

package jaxbadapter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(name="Test")
public class Bean {

    private String someValue;

    public Bean() {
    }

    @XmlElement
//  @XmlJavaTypeAdapter(NullStringAdapter.class)
    public String getSomeValue() {
        return someValue == null ? "" : someValue;
    }

    public void setSomeValue(final String someValue) {
        this.someValue = someValue;
    }

}

Это сработало для меня. Это действительно вариант, если вы сами создаете JAXB-аннотированные классы, а не генерируете их с помощью XJC из схемы.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...