JAXB: маршалировать/демаршалировать разные XML-элементы в/из одного класса

Скажем, у меня есть следующий класс:

@XmlRootElement
class Payment {
    @XmlElement
    int amount;
    @XmlElement
    Currency currency;

    @XmlElement
    IResponse response;
}

Если response==null - элемент "запрос", иначе - элемент "ответ". При запросе элемент должен быть (не)маршалирован в (от) корневого элемента с именем PaymentRequest, при ответе — в (от) PaymentResponse.

Как я могу настроить такой алгоритм маршалинга? Если JAXB не может этого сделать, может быть, какой-то другой движок может?


person i.y.    schedule 13.10.2015    source источник


Ответы (2)


Создайте и маршалируйте корневой элемент следующим образом:

JAXBElement<Payment> jbe;
if( payment.getResponse() != null ){
    jbe = wrap( null, "PaymentResponse", payment );
} else {
    jbe = wrap( null, "PaymentRequest", payment );
}
m.marshal( jbe, sw );

с помощью простого вспомогательного метода

<T> JAXBElement<T> wrap( String ns, String tag, T o ){
    QName qtag = new QName( ns, tag );
    Class<?> clazz = o.getClass();
    @SuppressWarnings( "unchecked" )
    JAXBElement<T> jbe = new JAXBElement( qtag, clazz, o );
    return jbe;
}

Простой способ сделать возможным демаршаллинг — создать два подкласса PaymentResponse и PaymentRequest, которые действуют как @XmlRootElements. Объектная фабрика содержит

@XmlElementDecl(namespace = "", name = "PaymentRequest")
public JAXBElement<PaymentRequest> 
createPaymentRequest(PaymentRequest value) {
    return new JAXBElement<PaymentRequest>(_Payment_QNAME, PaymentRequest.class, null, value);
}
@XmlElementDecl(namespace = "", name = "PaymentResponse")
public JAXBElement<PaymentResponse> 
createPaymentResponse(PaymentResponse value) {
    return new JAXBElement<PaymentResponse>(_Payment_QNAME, PaymentResponse.class, null, value);
}

Демаршаллинг:

JAXBContext jc = JAXBContext.newInstance( PACKAGE );
Unmarshaller m = jc.createUnmarshaller();
JAXBElement<?> tb = null;
try {
    Payment payment = readFrom( Payment.class );
} catch( Exception e  ){
}

и метод readFrom:

public <T> T readFrom( Class<T> type ) throws Exception {
    try {
        JAXBContext jc = JAXBContext.newInstance( PACKAGE );
        Unmarshaller u = jc.createUnmarshaller();
        JAXBElement<T> jbe = (JAXBElement<T>)u.unmarshal( new File( XMLIN ) );
        return type.cast( jbe.getValue() );
    } catch (JAXBException e) {
        e.printStackTrace();
    }
    return null;
}
person laune    schedule 14.10.2015
comment
Благодарю вас! Однако я вижу здесь две проблемы. Во время маршалинга вы должны явно указать имя корневого элемента. А для реализации демаршалинга нужно изменить структуру классов (создав разные классы для запросов и ответов) — к сожалению, у меня не получается. Итак, я нашел другое решение, смотрите следующий ответ. - person i.y.; 15.10.2015
comment
Различные классы для запросов и ответов не влияют на остальную часть приложения, они нигде не видны. - person laune; 15.10.2015

Наконец-то я реализовал демаршалинг, перехватив события StAX. Вот код:

JAXBContext jc = JAXBContext.newInstance(RootElement.class, A.class, B.class, C.class, D.class, E.class);
Unmarshaller unmarsh = jc.createUnmarshaller();
XMLStreamReader xs = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(etalonRs));
XMLStreamReader xd = new StreamReaderDelegate(xs) {
      public static final String ROOT_ELEMENT = "TestRoot";
      public static final int REPLACEABLE_LEVEL = 2;
      public final Collection<String> sufficesToDelete = Arrays.asList("Rq", "Rs");

      protected Stack<String> elementNamesStack = new Stack<>();
      protected Set<String> replaceableNames = new HashSet<>();

      @Override
      public String getLocalName() {
          String realName = super.getLocalName();
          if (replaceableNames.contains(realName) && elementNamesStack.size() == REPLACEABLE_LEVEL) {
              for (String suffix : sufficesToDelete) {
                  if (realName.endsWith(suffix)) {
                      return realName.substring(0, realName.lastIndexOf(suffix));
                  }
              }
          }
          return realName;
      }

      @Override
      public int next() throws XMLStreamException {
          final int eventCode = super.next();
         processLevel(eventCode);
          return eventCode;
      }

      @Override
      public int nextTag() throws XMLStreamException {
          final int eventCode = super.nextTag();
          processLevel(eventCode);
          return eventCode;
      }

      private void processLevel(int eventCode) {
          switch (eventCode) {
              case XMLStreamReader.START_ELEMENT:
                  final String origElementName = super.getLocalName();
                  if ((elementNamesStack.size() + 1) == REPLACEABLE_LEVEL && elementNamesStack.peek().equals(ROOT_ELEMENT))
                      replaceableNames.add(origElementName);
                  elementNamesStack.push(origElementName);
                  break;
              case XMLStreamReader.END_ELEMENT: 
                  assert(elementNamesStack.pop().equals(super.getLocalName()));
                  break;

          }
      }
  };

Object o = unmarsh.unmarshal(xd);

Вот мои тестовые классы. Да, реальная структура в продакшне сложнее — там разные «платы» и их элементы не в корне, поэтому пришлось использовать аннотацию @XmlAnyElement:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TestRoot")
public static class RootElement {
    @XmlElement(name = "SomeDate")
    private Date dt = new Date();

    @XmlAnyElement(lax=true)
    private A a = new C();
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TestA")
@XmlType
public static abstract class A {
    private int fld1 = 1;

    @XmlAnyElement(lax=true)
    @XmlElementWrapper
    protected List<Object> list = new ArrayList<>();
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TestC")
public static class C extends A {
    private int fld2  = 3;
}

Маршалинг можно реализовать таким же образом, но вы должны написать свой "StreamWriterDelegate" с нуля.

person i.y.    schedule 15.10.2015