Использование JAXB для поддержки схем с незначительными вариациями

Ситуация

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

Проблема

Я собирался использовать JAXB 2 (из Metro) с наследованием в качестве решения. Я ожидал, что структура пакета закончится примерно так:

    com.company.xml.schema.v1
    com.company.xml.schema.v2
    com.company.xml.schema.v2.vendorxyz

Если классы в пакете v2 просто расширяют классы в пакете v1 и при необходимости переопределяют. К сожалению, этот план оказался невозможным, поскольку подклассы не могут перезаписывать аннотации в родительских классах (см. Здесь). Например, если атрибут в схеме был переименован между версиями, то класс элемента v2 должен был бы полностью повторно реализовать элемент, не наследуя от v1.

Итак, это оставляет мне только два варианта, насколько я могу сказать

Вариант 1

Создайте "базовый" пакет для каждого типа схемы, аннотируйте классы элементов в этом пакете с помощью @XmlAccessorType (XmlAccessType.NONE) и удалите все остальные аннотации. Затем в каждом пакете с версией создайте классы, которые подклассифицируют соответствующий класс в базовом пакете и добавят все необходимые аннотации. Это решение дает мне небольшую помощь в области наследования, но дублирование кода огромно, и было бы сложно поддерживать.

Вариант 2

Не используйте JAXB. Мне действительно не нравится это решение, так как я также хотел бы работать с JAX-RS/JAX-WS.

Вопросы

  • Как я должен использовать JAXB для поддержки нескольких схем с небольшими вариациями без кучи дублирования кода?
  • Есть ли другая комбинация технологий, на которую я должен смотреть?

ИЗМЕНИТЬ

Решение, приведенное ниже от Blaise, отлично работало для большинства наших схем, которые были лишь незначительным переводом друг друга с общими одинаковыми данными. Однако мы столкнулись с проблемой в тех случаях, когда имеет смысл использовать наследование с именами пакетов для управления версиями. Например:

com.company.xml.schema.v1.ElementA
com.company.xml.schema.v2.ElementA

(где v2.ElementA расширяет v1.ElementA)

Использование MOXy OXM в этом случае натыкается на ошибку, и обходной путь можно найти здесь (с решением, предоставленным Blaise, не менее!)

Ответ 1

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

Вы можете использовать внешний документ привязки в EclipseLink JAXB для сопоставления вариантов среди XML-схем.

Поставщик 1

Вы можете использовать стандартные аннотации JAXB для сопоставления одного из поставщиков:

package forum9419732;

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    @XmlAttribute
    private int id;

    private String lastName;
    private String firstName;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

}

Поставщик 2

Мы будем использовать MOXy внешние метаданные для настройки метаданных, предоставленных аннотациями. В документе (oxm-v2.xml) ниже мы сопоставим свойства firstName и lastName с атрибутами XML:

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum9419732">
    <java-types>
        <java-type name="Customer">
            <java-attributes>
                <xml-attribute java-attribute="firstName"/>
                <xml-attribute java-attribute="lastName"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Поставщик 3

Снова мы будем использовать внешний документ привязки MOXy (oxm-v3.xml), чтобы переопределить аннотации. На этот раз мы сделаем свойство id XML-элементом.

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum9419732">
    <java-types>
        <java-type name="Customer">
            <java-attributes>
                <xml-element java-attribute="id" name="identifier"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Demo

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

package forum9419732;

import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

    public static void main(String[] args) throws JAXBException {
        Customer customer = new Customer();
        customer.setId(123);
        customer.setFirstName("Jane");
        customer.setLastName("Doe");

        // VENDOR 1
        JAXBContext jcV1 = JAXBContext.newInstance(Customer.class);
        marshal(jcV1, customer);

        // VENDOR 2
        Map<String, Object> propertiesV2 = new HashMap<String, Object>(1);
        propertiesV2.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum9419732/oxm-v2.xml");
        JAXBContext jcV2 = JAXBContext.newInstance(new Class[] {Customer.class}, propertiesV2);
        marshal(jcV2, customer);

        // VENDOR 3
        Map<String, Object> propertiesV3 = new HashMap<String, Object>(1);
        propertiesV3.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum9419732/oxm-v3.xml");
        JAXBContext jcV3 = JAXBContext.newInstance(new Class[] {Customer.class}, propertiesV3);
        marshal(jcV3, customer);

        // VENDOR 4
        Map<String, Object> propertiesV4 = new HashMap<String, Object>(1);
        List<String> oxmV4 = new ArrayList<String>(2);
        oxmV4.add("forum9419732/oxm-v2.xml");
        oxmV4.add("forum9419732/oxm-v3.xml");
        propertiesV4.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, oxmV4);
        JAXBContext jcV4 = JAXBContext.newInstance(new Class[] {Customer.class}, propertiesV4);
        marshal(jcV4, customer);
    }

    private static void marshal(JAXBContext jc, Customer customer) throws JAXBException {
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
        System.out.println();
    }

}

Выход

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

<?xml version="1.0" encoding="UTF-8"?>
<customer id="123">
   <lastName>Doe</lastName>
   <firstName>Jane</firstName>
</customer>

<?xml version="1.0" encoding="UTF-8"?>
<customer id="123" lastName="Doe" firstName="Jane"/>

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <identifier>123</identifier>
   <lastName>Doe</lastName>
   <firstName>Jane</firstName>
</customer>

<?xml version="1.0" encoding="UTF-8"?>
<customer lastName="Doe" firstName="Jane">
   <identifier>123</identifier>
</customer>

Дополнительная информация