Может ли JAXB/MOXy отображать значение элемента и атрибут элемента в одно и то же поле POJO?

Я только начал работать над веб-сервисами, используя JAXB, чтобы разобрать входящие документы SOAP в классы нашей предметной области. Я столкнулся с технической проблемой, связанной с форматом OIO XML, используемым в государственных учреждениях Дании. В формате, среди прочего, указано, что нельзя использовать атрибут схемы xml, допускающий значение nillable, для объявления элемента xml. Таким образом, я должен найти другое решение моей проблемы.

Описание У нас есть несколько чисел и дат, которые клиент веб-сервиса может отправить для обновления приложения. Эти числа и даты сопоставляются с полями POJO эквивалентных типов. Задача состоит в том, как сбросить значение такого поля POJO, создав и отправив правильный XML.

Отправка 12:31:34T01-01-2010..... обновит поле POJO до указанного значения.

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

Я также не могу отправить, так как это не разрешено стандартом OIO XML.

Поэтому в качестве мрачного обходного пути я планирую отправить, поскольку это не должно быть запрещено стандартом OIO XML.

Это приводит к проблеме, что если элемент startTime содержит атрибут delete="true", то должно ли соответствующее поле POJO быть установлено равным нулю; если a не имеет атрибута удаления, то передайте допустимое значение элемента в поле POJO.

Аннотация @XMLElement позволяет мне отображать только значение startTime, например.

class MyClass{
   @XMLElement
   private Date startTime;
}

Как я могу заставить атрибут удаления также влиять на значение поля MyClass.startTime?

С уважением, Джеспер


person Jesper    schedule 19.10.2010    source источник


Ответы (5)


Для этого вы можете использовать XmlAdapter. Адаптер будет конвертировать в/из объекта даты. Адаптированный объект будет иметь два свойства. Первое свойство даты с аннотацией @XmlValue и логическое свойство с аннотацией @XmlAttribute. Полный пример выложу завтра.

Мой класс

Мы аннотируем свойство startTime с помощью @XmlJavaTypeAdapter.

import java.util.Date;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
class MyClass{

    private Date startTime;

    @XmlJavaTypeAdapter(DateAdapter.class)
    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

}

Адаптер даты

В классе DateAdapter мы будем конвертировать между экземпляром Date и классом с двумя свойствами (дата и логическое значение).

import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class DateAdapter extends XmlAdapter<AdaptedDate, Date> {

    @Override
    public AdaptedDate marshal(Date date) throws Exception {
        AdaptedDate adaptedDate = new AdaptedDate();
        adaptedDate.setDate(date);
        return adaptedDate;
    }

    @Override
    public Date unmarshal(AdaptedDate adaptedDate) throws Exception {
        return adaptedDate.getDate();
    }

}

Дата адаптации

Это представление двух свойств нашей информации о дате. Он создается XmlAdapter.

import java.util.Date;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;

public class AdaptedDate {

    private Date date;

    @XmlValue
    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    @XmlAttribute
    public Boolean isDelete() {
        if(null == date) {
            return true;
        }
        return null;
    }

}

Демо

Для демонстрации этого кода можно использовать следующий демонстрационный код:

import java.util.Date;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(MyClass.class);
        System.out.println(jc);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        MyClass myClass1 = new MyClass();
        marshaller.marshal(myClass1, System.out);

        MyClass myClass2 = new MyClass();
        myClass2.setStartTime(new Date());
        marshaller.marshal(myClass2, System.out);
    }

}

Дальнейшее расширение:

  • Вы можете управлять именем элемента для атрибута startTime, добавив аннотацию @XmlElement(name="foo").
  • Вы можете дополнительно управлять представлением XML, используя аннотацию MOXy @XmlPath("foo/bar"), например @XmlPath().

Для получения дополнительной информации см.:

person bdoughan    schedule 19.10.2010

Большое спасибо за ответ. Кажется, это работает для очень простого сценария, но у меня нет успеха для немного более сложных сценариев.

Я пытался имитировать ваш код в своем собственном домене, только я объединяю данные, а не упорядочиваю их.

Класс адаптера:

public class DeleteStringAdapter extends XmlAdapter<DeletableString, String> {

@Override
public DeletableString marshal(String value) throws Exception {
    System.out.println("MARSHALL: " + value);
    return new DeletableString(value);
}

@Override
public String unmarshal(DeletableString v) throws Exception {
    System.out.println("UNMARSHALL: "  + v);

    if(v.isDelete() != null && v.isDelete()){
        return null;
    }

    return v.getValue();    
}

}

Тип адаптера:

public class DeletableString {

public DeletableString() {
}

public DeletableString(String value) {
    this.value = value;
}

private Boolean delete;

private String value;

@XmlAttribute
public Boolean isDelete() {
    return delete;
}

public void setDelete(Boolean delete) {
    this.delete = delete;
}   

@XmlValue
public String getValue() {
    return value;
}

public void setValue(String value) {
    this.value = value;
}

@Override
public String toString() {
    return DeletableString.class.getSimpleName() + "[delete=" + isDelete() + ", value=" + value + "]";
}
}

Класс рабочего домена с аннотациями:

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlRootElement(name = "xml-fragment")
public class SimpleNavne implements Serializable{

private String forNavn = "";

private String fornavneMrkKode = "";

@XmlElement(name="PersonGivenName")
@XmlJavaTypeAdapter(value = DeleteStringAdapter.class)
public String getForNavn() {
    return forNavn;
}

public void setForNavn(String forNavn) {
    this.forNavn = forNavn;
}

@Override
public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append(SimpleNavne.class.getSimpleName() + "[");
    builder.append("forNavn=");
    builder.append(forNavn);
    builder.append("]");
    return builder.toString();
}

}

Демо

public class AppTest {

@Test
public void testApp() throws Exception {

    System.setProperty("jaxb.debug", "true");

    try{
        JAXBContext jaxbContext = JAXBContext.newInstance(SimpleNavne.class);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        Object ps =  unmarshaller.unmarshal(new File("./personname-test4.xml"));

        System.out.println(ps);

    }catch(Exception e){
        e.printStackTrace();
    }
}
}

Файл:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns:xml-fragment xmlns:ns="http://cpr.csc.com/navne">
<ns:PersonGivenName delete="false">010</ns:PersonGivenName>
</ns:xml-fragment>

Вышеприведенное, кажется, работает нормально, если сделать вывод из вывода

SimpleNavne[forNavn=010]

Однако у меня возникают проблемы при введении полей, аннотированных с помощью @XMLPath, в классе домена SimpleName.

Измененный класс домена

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlRootElement(name = "xml-fragment")
public class SimpleNavne implements Serializable{

private String forNavn = "";

private String fornavneMrkKode = "";

@XmlElement(name="PersonGivenName")
@XmlJavaTypeAdapter(value = DeleteStringAdapter.class)
public String getForNavn() {
    return forNavn;
}

public void setForNavn(String forNavn) {
    this.forNavn = forNavn;
}

@XmlPath("/PersonGivenNameMarkingStructure/MarkingCode/text()")
public String getFornavneMrkKode() {
    return fornavneMrkKode;
}

public void setFornavneMrkKode(String forNavnMrk) {
    this.fornavneMrkKode = forNavnMrk;
}

@Override
public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append(SimpleNavne.class.getSimpleName() + "[");
    builder.append(", forNavn=");
    builder.append(forNavn);
    builder.append(", fornavneMrkKode=");
    builder.append(fornavneMrkKode);
    builder.append("]");
    return builder.toString();
}

}

Измененный файл:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns:xml-fragment xmlns:ns="http://cpr.csc.com/navne">
<ns:PersonGivenName delete="true">010</ns:PersonGivenName>
<ns:PersonGivenNameMarkingStructure>
    <ns:MarkingCode>011</ns:MarkingCode>
</ns:PersonGivenNameMarkingStructure>
</ns:xml-fragment>

Результат:

SimpleNavne[forNavn=010, fornavneMrkKode=]

А должно было быть:

SimpleNavne[forNavn=010, fornavneMrkKode=011]

Я делаю что-то совершенно неправильно или MOXy не поддерживает этот сценарий?

PS. Я пробовал использовать MOXy 2.1.1 и 2.2.0-M3 с тем же результатом.

person Jesper Tejlgaard    schedule 20.10.2010

Большое спасибо, что потратили на это время.

В ответ на ваш последний ответ: Может ли JAXB/MOXy отображать значение элемента и атрибут элемента в одно и то же поле POJO?

Я использую файл jaxb.properties с заводской конфигурацией. Мой вывод выглядит почти так же, как ваш, за исключением отсутствующего значения:

21-10-2010 09:47:08 javax.xml.bind.ContextFinder find
FINE: Trying to locate com/csc/cpr/ws/domain/jaxb.properties
21-10-2010 09:47:08 javax.xml.bind.ContextFinder loadJAXBProperties
FINE: loading props from file:/C:/Ajour/ajourworkspace/jaxb-test-with-schema/target/classes/com/csc/cpr/ws/domain/jaxb.properties
21-10-2010 09:47:08 javax.xml.bind.ContextFinder find
FINE:   found
21-10-2010 09:47:08 javax.xml.bind.ContextFinder safeLoadClass
FINE: Trying to load org.eclipse.persistence.jaxb.JAXBContextFactory
21-10-2010 09:47:08 javax.xml.bind.ContextFinder newInstance
FINE: loaded org.eclipse.persistence.jaxb.JAXBContextFactory from jar:file:/C:/Users/jpedersen22/.m2/repository/org/eclipse/persistence/eclipselink/2.1.1/eclipselink-2.1.1.jar!/org/eclipse/persistence/jaxb/JAXBContextFactory.class
UNMARSHALL: DeletableString[delete=true, value=010]
SimpleNavne[forNavn=null, fornavneMrkKode=]

Как видите, MOXy — это используемая реализация JAXB. Я также успешно использовал аннотацию @XMLPath, когда DeleteStringAdapter не используется. т.е. если я удалю @XmlJavaTypeAdapter(value = DeleteStringAdapter.class) из getFornavn, то получу следующее:

21-10-2010 10:00:02 javax.xml.bind.ContextFinder find
FINE: Trying to locate com/csc/cpr/ws/domain/jaxb.properties
21-10-2010 10:00:02 javax.xml.bind.ContextFinder loadJAXBProperties
FINE: loading props from file:/C:/Ajour/ajourworkspace/jaxb-test-with-schema/target/classes/com/csc/cpr/ws/domain/jaxb.properties
21-10-2010 10:00:02 javax.xml.bind.ContextFinder find
FINE:   found
21-10-2010 10:00:02 javax.xml.bind.ContextFinder safeLoadClass
FINE: Trying to load org.eclipse.persistence.jaxb.JAXBContextFactory
21-10-2010 10:00:02 javax.xml.bind.ContextFinder newInstance
FINE: loaded org.eclipse.persistence.jaxb.JAXBContextFactory from jar:file:/C:/Users/jpedersen22/.m2/repository/org/eclipse/persistence/eclipselink/2.1.1/eclipselink-2.1.1.jar!/org/eclipse/persistence/jaxb/JAXBContextFactory.class
SimpleNavne[forNavn=010, fornavneMrkKode=011]

Что касается XML 010, то он был сделан для облегчения моей жизни при тестировании различных сценариев (с атрибутом или без). В любом случае, мой DeleteStringAdapter позволяет атрибуту удаления иметь приоритет, а это именно то, что я хочу.

person Jesper Tejlgaard    schedule 21.10.2010
comment
Я отправил вам по электронной почте проект Java, который я использую для отладки вашей проблемы. Не могли бы вы запустить его, чтобы увидеть, получите ли вы тот же результат? Надеюсь, вы получите тот же результат, и мы сможем выяснить дельту, которая вызывает неправильное поведение. - person bdoughan; 21.10.2010

В ответ на ваш второй вопрос:

Когда я запускаю ваш код (используя EclipseLink 2.1.1), я получаю следующий вывод:

20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder find
FINE: Trying to locate forum80/jaxb.properties
20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder loadJAXBProperties
FINE: loading props from file:/C:/Workspaces/EclipseLink-Trunk/SCRATCH/bin/forum80/jaxb.properties
20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder find
FINE:   found
20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder safeLoadClass
FINE: Trying to load org.eclipse.persistence.jaxb.JAXBContextFactory
20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder newInstance
FINE: loaded org.eclipse.persistence.jaxb.JAXBContextFactory from jar:file:/C:/Workspaces/EclipseLink-2.1/eclipselink.jar!/org/eclipse/persistence/jaxb/JAXBContextFactory.class
UNMARSHALL: DeletableString[delete=true, value=010]
SimpleNavne[, forNavn=null, fornavneMrkKode=011]

Этот результат отличается от того, что вы видите:

SimpleNavne[forNavn=010, fornavneMrkKode=]

Возможно, нам потребуется настроить логику XML-адаптера для обработки вашего конкретного образца XML. В своем XML-фрагменте вы указываете как delete="true", так и значение для элемента "PersonGivenName", которое сопоставляется со свойством "forNavn". Должны ли в этом случае выигрывать нуль или значение?

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns:xml-fragment xmlns:ns="http://cpr.csc.com/navne">
    <ns:PersonGivenName delete="true">010</ns:PersonGivenName>
    <ns:PersonGivenNameMarkingStructure>
        <ns:MarkingCode>011</ns:MarkingCode>
    </ns:PersonGivenNameMarkingStructure>
</ns:xml-fragment>

Я могу заставить свойство "fornavneMrkKode" возвращать пустую строку, если используется JAXB RI. Есть ли шанс, что у вас неправильно указаны jaxb.properties для использования EclipseLink MOXy?

Файл jaxb.properties должен находиться в том же пакете, что и классы вашей модели, со следующей записью:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

ОБНОВЛЕНИЕ:

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

person bdoughan    schedule 20.10.2010

Я провел ваш проект с большим успехом! Я также обнаружил свою проблему, сравнив наши проекты. Я поместил класс DeletableString в отдельный пакет, но забыл добавить файл package-info.java. Добавление файла с содержимым ниже заставляет все это работать:

@XmlSchema(namespace = "http://cpr.csc.com/navne", elementFormDefault = XmlNsForm.QUALIFIED)
package com.csc.cpr.ws.domain.customtypes;

import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

Большое спасибо за вашу помощь. Это было очень ценно

С уважением, Джеспер

person Jesper Tejlgaard    schedule 21.10.2010
comment
Нет проблем, рад, что смог помочь. - person bdoughan; 21.10.2010