Имя элемента JAXB на основе свойства объекта - PullRequest
8 голосов
/ 14 декабря 2010

Мне нужно создать объектную модель для следующих XML:

Пример XML 1:

<InvoiceAdd>
  <TxnDate>2009-01-21</TxnDate>
  <RefNumber>1</RefNumber>
  <InvoiceLineAdd>
  </InvoiceLineAdd>
</InvoiceAdd>

Пример XML 2:

<SalesOrderAdd>
  <TxnDate>2009-01-21</TxnDate>
  <RefNumber>1</RefNumber>
  <SalesOrderLineAdd>
  </SalesOrderLineAdd>
</SalesOrderAdd>

Вывод XML будет основан на одном строковом параметре или перечислении. String txnType = "Счет-фактура"; (или "SalesOrder");

Я бы использовал один класс TransactionAdd:

@XmlRootElement
public class TransactionAdd {  
  public String txnDate;
  public String refNumber;

  private String txnType;
  ...

  public List<LineAdd> lines;
}

вместо использования подклассов или чего-либо еще. Код, который создает экземпляр TransactionAdd, одинаков для обоих типов транзакций, он отличается только типом.

Этот XML используется довольно известным продуктом под названием QuickBooks и используется веб-службой QuickBooks - поэтому я не могу изменить XML, но я хочу упростить возможность установки имени элемента на основе свойства (txnType ).

Я бы рассмотрел что-то вроде метода определения имени целевого элемента:

@XmlRootElement
public class TransactionAdd {  
  public String txnDate;
  public String refNumber;

  private String txnType;
  ...

  public List<LineAdd> lines;

  public String getElementName() {
     return txnType + "Add";
  }
}

Различные транзакции будут создаваться с использованием следующего кода:

t = new TransactionAdd();
t.txnDate = "2010-12-15";
t.refNumber = "123";
t.txnType = "Invoice";

Целью является сериализация объекта t с именем элемента верхнего уровня на основе txnType. E.g.:

<InvoiceAdd>
   <TxnDate>2009-01-21</TxnDate>
   <RefNumber>1</RefNumber>
</InvoiceAdd>

В случае t.txnType = "SalesOrder" результат должен быть

<SalesOrderAdd>
   <TxnDate>2009-01-21</TxnDate>
   <RefNumber>1</RefNumber>
</SalesOrderAdd>

В настоящий момент я вижу только один обходной путь с подклассами InvoiceAdd и SalesOrderAdd и с использованием аннотации @XmlElementRef, чтобы иметь имя, основанное на имени класса. Но ему нужно будет создавать экземпляры разных классов в зависимости от типа транзакции, а также иметь два других разных класса InvoiceLineAdd и SalesOrderLineAdd, которые выглядят довольно некрасиво.

Пожалуйста, предложите мне любое решение, чтобы справиться с этим. Я хотел бы рассмотреть что-то простое.

Ответы [ 2 ]

4 голосов
/ 15 декабря 2010

Чтобы рассмотреть аспект корневого элемента, который вы могли бы сделать, нужно использовать @XmlRegistry и @XmlElementDecl.Это даст нам несколько возможных корневых элементов для класса TransactionAdd:

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 {

    @XmlElementDecl(name="InvoiceAdd")
    JAXBElement<TransactionAdd> createInvoiceAdd(TransactionAdd invoiceAdd) {
        return new JAXBElement<TransactionAdd>(new QName("InvoiceAdd"), TransactionAdd.class, invoiceAdd);
    }

    @XmlElementDecl(name="SalesOrderAdd")
    JAXBElement<TransactionAdd> createSalesOrderAdd(TransactionAdd salesOrderAdd) {
        return new JAXBElement<TransactionAdd>(new QName("SalesOrderAdd"), TransactionAdd.class, salesOrderAdd);
    }

}

Ваш класс TransactionAdd будет выглядеть примерно так:Интересно отметить, что мы сделаем свойство txnType @ XmlTransient.

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;

public class TransactionAdd {

    private String txnDate;
    private String refNumber;
    private String txnType;
    private List<LineAdd> lines;

    @XmlElement(name="TxnDate")
    public String getTxnDate() {
        return txnDate;
    }

    public void setTxnDate(String txnDate) {
        this.txnDate = txnDate;
    }

    @XmlElement(name="RefNumber")
    public String getRefNumber() {
        return refNumber;
    }

    public void setRefNumber(String refNumber) {
        this.refNumber = refNumber;
    }

    @XmlTransient
    public String getTxnType() {
        return txnType;
    }

    public void setTxnType(String txnType) {
        this.txnType = txnType;
    }

    public List<LineAdd> getLines() {
        return lines;
    }

    public void setLines(List<LineAdd> lines) {
        this.lines = lines;
    }

}

Затем нам нужно будет предоставить немного логики вне операции JAXB.Для демаршала мы будем использовать локальную часть имени корневого элемента для заполнения свойства txnType.Для маршала мы будем использовать значение свойства txnType для создания соответствующего JAXBElement.

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

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

        File xml = new File("src/forum107/input1.xml");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        JAXBElement<TransactionAdd> je = (JAXBElement<TransactionAdd>) unmarshaller.unmarshal(xml);
        TransactionAdd ta = je.getValue();
        ta.setTxnType(je.getName().getLocalPart());

        JAXBElement<TransactionAdd> jeOut;
        if("InvoiceAdd".equals(ta.getTxnType())) {
            jeOut = new ObjectFactory().createInvoiceAdd(ta);
        } else {
            jeOut = new ObjectFactory().createSalesOrderAdd(ta);
        }
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(jeOut, System.out);
    }

}

To Do

Я рассмотрю вопрос о свойстве lines следующим.

4 голосов
/ 14 декабря 2010

Вы можете использовать XmlAdapter для этого.Исходя из значения String свойства txnType, у вас будет XmlAdapter, который будет маршалировать экземпляр объекта, соответствующего InvoiceLineAdd или SalesOrderLineAdd.

Вот как это будет выглядеть:

TransactionAdd

В свойстве txnType мы будем использовать комбинацию @XmlJavaTypeAdapter и @XmlElementRef:

import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class TransactionAdd {

    private String txnType;

    @XmlJavaTypeAdapter(MyAdapter.class)
    @XmlElementRef
    public String getTxnType() {
        return txnType;
    }

    public void setTxnType(String txnType) {
        this.txnType = txnType;
    }

}

Адаптированные объекты будут выглядеть так:

AbstractAdd

import javax.xml.bind.annotation.XmlSeeAlso;

@XmlSeeAlso({InvoiceAdd.class, SalesOrderAdd.class})
public class AbstractAdd {

}

InvoiceAdd

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class InvoiceAdd extends AbstractAdd {

}

SalesOrderAdd

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class SalesOrderAdd extends AbstractAdd {

}

XmlAdapter для преобразования междуСтрока и адаптированные объекты будут выглядеть следующим образом:

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

public class MyAdapter extends XmlAdapter<AbstractAdd, String> {

    @Override
    public String unmarshal(AbstractAdd v) throws Exception {
        if(v instanceof SalesOrderAdd) {
            return "salesOrderAdd";
        }
        return "invoiceAdd";
    }

    @Override
    public AbstractAdd marshal(String v) throws Exception {
        if("salesOrderAdd".equals(v)) {
            return new SalesOrderAdd();
        } 
        return new InvoiceAdd();
    }

}

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

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

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

        File xml = new File("input.xml");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        TransactionAdd ta = (TransactionAdd) unmarshaller.unmarshal(xml);

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

}

Для создания / использования следующего XML:

<transactionAdd>
    <salesOrderAdd/>
</transactionAdd>

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

...