Универсальный адаптер для JAXB

Скажем, у меня есть класс Person:

class Person{
  String firstName;
  String lastName;
  String email;
}

XML имеет формат:

<person>
 <firstName value="asd" /> 
 <lastName value="bcd" />
 <email value="qwe" />
</person>

Я могу распаковать/упорядочить этот класс, используя собственную реализацию XmlAdapter для каждого из полей FirstNameAdapter, LastNameAdapter, EmailAdapter. Как видите, каждое поле представлено одинаковым образом — имя поля в виде элемента xml и значение поля в виде атрибута элемента. Можно ли создать "универсальный" адаптер, которому я смогу передать имя поля, и он будет извлекать значение этого поля?

P.S. Я знаю о реализации MOXy JAXB, но мне интересно, возможно ли это с помощью эталонной реализации JAXB.

Спасибо!


person Volodymyr Rudyi    schedule 17.10.2012    source источник


Ответы (2)


Вы можете использовать XmlAdapter вот так:

import java.io.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;

@XmlType
class Valued {
    @XmlAttribute(name="value")
    public String value;
}

class ValuedAdapter extends XmlAdapter<Valued, String> {
    public Valued marshal(String s) {
        Valued v = new Valued();
        v.value = s;
        return v;
    }
    public String unmarshal(Valued v) {
        return v.value;
    }
}

@XmlRootElement
class Person {

    @XmlJavaTypeAdapter(ValuedAdapter.class)
    @XmlElement
    String firstName;

    @XmlJavaTypeAdapter(ValuedAdapter.class)
    @XmlElement
    String lastName;

}

class SO12928971 {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.firstName = "John";
        p.lastName = "Doe";
        JAXBContext jc = JAXBContext.newInstance(Person.class);
        StringWriter sw = new StringWriter();
        jc.createMarshaller().marshal(p, sw);
        String xml = sw.toString();
        System.out.println(xml);
        StringReader sr = new StringReader(xml);
        p = (Person)jc.createUnmarshaller().unmarshal(sr);
        assert "John".equals(p.firstName);
        assert "Doe".equals(p.lastName);
    }
}

Идея заключается в том, что XML-схема и, следовательно, также JAXB имеют четкое различие между именами элементов и типами контента, даже несмотря на то, что во многих документах имеется четкое взаимно-однозначное соответствие между эти двое. Таким образом, в приведенном выше коде type Valued описывает нечто, имеющее атрибут value, независимо от имени элемента. Члены, которые вы хотите сериализовать, помечаются как @XmlElement без имени, включенного в эту аннотацию. Таким образом, они будут генерировать элементы с именем, производным от имени члена. Аннотация @XmlJavaTypeAdapter заставит сериализатор рассматривать эти элементы, как если бы их типы были Valued. Вот каким будет их тип контента XML.

Схема для приведенного выше кода выглядит так:

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="person" type="person"/>

  <xs:complexType name="person">
    <xs:sequence>
      <xs:element name="firstName" type="valued" minOccurs="0"/>
      <xs:element name="lastName" type="valued" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="valued">
    <xs:sequence/>
    <xs:attribute name="value" type="xs:string"/>
  </xs:complexType>
</xs:schema>
person MvG    schedule 17.10.2012
comment
+1 для JAXB имеет четкое различие между именами элементов и типами контента. - person Ian Roberts; 17.10.2012

Примечание. Я EclipseLink JAXB (MOXy) лидер и член JAXB (JSR-222) группа экспертов.

P.S. Я знаю о реализации MOXy JAXB, но мне интересно, возможно ли это с помощью эталонной реализации JAXB.

Для сравнения я добавил, как это можно сделать с расширением @XmlPath EclipseLink JAXB (MOXy).

Человек

package forum12928971;

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
class Person{

    @XmlPath("firstName/@value")
    String firstName;

    @XmlPath("lastName/@value")
    String lastName;

    @XmlPath("email/@value")
    String email;

}

jaxb.properties

Чтобы указать MOXy в качестве поставщика JAXB, вам нужно добавить файл с именем jaxb.properties в тот же пакет, что и ваша модель домена, со следующей записью:

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

Демо

package forum12928971;

import java.io.File;
import javax.xml.bind.*;

public class Demo {
    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Person.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum12928971/input.xml");
        Person person = (Person) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(person, System.out);
    }

}

входной.xml/выходной

<?xml version="1.0" encoding="UTF-8"?>
<person>
   <firstName value="asd"/>
   <lastName value="bcd"/>
   <email value="qwe"/>
</person>
person bdoughan    schedule 17.10.2012