Pregunta

Tengo que crear modelo de objetos para siguiente XMLs:

XML de ejemplo 1:

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

XML Muestra 2:

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

La salida XML se basa en un único parámetro de cadena o enumeración. Cadena txnType = "Factura"; (O "SalesOrder");

Me gustaría utilizar sola clase TransactionAdd:

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

  private String txnType;
  ...

  public List<LineAdd> lines;
}

en lugar de utilizar subclases o cualquier otra cosa. El código que crea la instancia TransactionAdd es el mismo para ambos tipos de transacción que sólo se diferencia en el tipo.

Este XML es utilizado por un producto bastante conocido llamado QuickBooks y es consumido por el servicio web de QuickBooks - así que no puedo cambiar el XML, pero quiero que sea fácil de ser capaz de establecer nombre del elemento basado en la propiedad (txnType ).

Me consideraría algo así como un método para determinar el nombre del elemento de destino:

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

  private String txnType;
  ...

  public List<LineAdd> lines;

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

Las diferentes operaciones se pueden crear utilizando el código siguiente:

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

El objetivo es serializar t objeto con el nombre de elemento de nivel superior sobre la base de txnType. Por ejemplo:.

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

En caso de t.txnType = "SalesOrder" el resultado debería ser

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

Por el momento sólo veo una solución alternativa a las subclases InvoiceAdd y SalesOrderAdd y el uso de la anotación @XmlElementRef tener un nombre basado en nombre de la clase. Pero necesitará para crear instancias de clases diferentes según el tipo de transacción y también tendrá que tener otras dos clases diferentes InvoiceLineAdd y SalesOrderLineAdd que se parece bastante feo.

Por favor, sugiere ninguna solución para manejar esto. Yo consideraría algo sencillo.

¿Fue útil?

Solución

Para abordar el aspecto elemento raíz que podría hacer tendrá que @XmlRegistry apalancamiento y @XmlElementDecl. Esto nos dará múltiples elementos raíz posible para la clase 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);
    }

}

Su clase TransactionAdd se verá algo como lo siguiente. Lo interesante a destacar es que vamos a hacer la @XmlTransient propiedad txnType.

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;
    }

}

A continuación, tenemos que suministrar un poco de lógica fuera de la operación de JAXB. Para una unmarshal usaremos la parte local del nombre de elemento raíz para poblar la propiedad txnType. Para un mariscal vamos a utilizar el valor de la propiedad txnType para crear el JAXBElement apropiado.

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);
    }

}

Para ello

Voy a mirar en abordar la propiedad líneas siguiente.

Otros consejos

Se podría utilizar un XmlAdapter para esto. Con base en el valor de cadena de la propiedad txnType usted tendría la XmlAdapter Marshal una instancia de un objeto que corresponde a InvoiceLineAdd o SalesOrderLineAdd.

Esto es cómo se vería:

TransactionAdd

En la propiedad txnType vamos a utilizar una combinación de @XmlJavaTypeAdapter y @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;
    }

}

Los objetos adaptados se verá así:

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 {

}

El XmlAdapter para convertir entre la cuerda y los objetos adaptados se verá así:

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();
    }

}

El siguiente código de demostración se puede utilizar:

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);
    }

}

Para producir / consumir el siguiente XML:

<transactionAdd>
    <salesOrderAdd/>
</transactionAdd>

Para obtener más información, véase:

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top