2012-04-14 19 views
5

actualización - ver edición en la parte inferiorJAXB Anotaciones: ¿cómo puedo hacer que una lista de elementos XmlIDRef tenga el valor de identificación como atributo en lugar de texto del cuerpo del elemento?

IDREFS/keyrefs parecen ser posible en anotaciones JAXB, pero el árbitro termina siendo texto del elemento.

Me gustaría que la referencia sea un atributo de un elemento.

Por ejemplo, dado este modelo de objetos:

@XmlType 
public class Employee { 
    @XmlID 
    @XmlAttribute 
    String name; 
    @XmlAttribute 
    int years; 
    @XmlAttribute 
    String foo; 
} 

@XmlType 
public class Office { 
    @XmlAttribute 
    String name; 
    @XmlElementWrapper 
    @XmlElement(name = "employee") 
    List<Employee> employees; 
} 

@XmlRootElement 
public class Company { 
    @XmlElementWrapper 
    @XmlElement(name = "office") 
    List<Office> offices; 
    @XmlElementWrapper 
    @XmlElement(name = "employee") 
    List<Employee> employees; 
} 

Me gustaría que el formato XML externalizada a terminar pareciéndose a esto:

<company> 
    <offices> 
     <office name="nyc"> 
      <employees> 
       <!--*** id ref to employee name ***--> 
       <employee ref="alice"/> 
       <employee ref="bob"/> 
      </employees> 
     </office> 
     <office name="sf"> 
      <employees> 
       <employee ref="connie"/> 
       <employee ref="daphne"/> 
      </employees> 
     </office> 
    </offices> 
    <employees> 
     <!-- *** name is the id *** --> 
     <employee name="alice" years="3" foo="bar"/> 
     <employee name="bob" years="3" foo="bar"/> 
     <employee name="connie" years="3" foo="bar"/> 
     <employee name="daphne" years="3" foo="bar"/> 
    </employees> 
</company> 

En su lugar, lo mejor que puedo hacer es esto (con las anotaciones que figuran más arriba en el código java):

<company> 
    <offices> 
     <office name="nyc"> 
      <employees> 
       <employee>alice</employee> 
       <employee>bob</employee> 
      </employees> 
     </office> 
     <office name="sf"> 
      <employees> 
       <employee>connie</employee> 
       <employee>daphne</employee> 
      </employees> 
     </office> 
    </offices> 
    <employees> 
     <employee name="alice" years="3" foo="bar"/> 
     <employee name="bob" years="3" foo="bar"/> 
     <employee name="connie" years="3" foo="bar"/> 
     <employee name="daphne" years="3" foo="bar"/> 
    </employees> 
</company> 

¿hay alguna manera de forzar el idref valor para ser un atributo del empleado, en lugar de texto del cuerpo del elemento? Sé que puedo hacer esto con un Esquema XML, pero me gustaría atenerme a las anotaciones si es posible.

Gracias.

Editar La solución de Torious a continuación casi funciona, pero no funciona en algunas circunstancias.

unmarshalling falla si los elementos de "oficinas" vienen antes (en el archivo xml) los elementos "empleados" a los que hace referencia la oficina. Las referencias de empleados no se encuentran, y el contenedor EmployeeRef tiene un objeto de empleado nulo. Si el "empleado" es el primero, funciona.

Esto no sería un gran problema, pero el método de Mariscal pondrá primero las "oficinas", por lo que falla el intento de unificar lo que acaba de organizarse.

Editar 2 comentario en la respuesta de Torious resuelve el problema del pedido.

Respuesta

4

La solución es usar un XmlAdapter que envuelve un Employee en una instancia de un nuevo tipo, EmployeeRef, que especifica cómo asignar la ref id XML:

@XmlType 
public class Office { 

    @XmlAttribute 
    String name; 

    @XmlElementWrapper 
    @XmlElement(name="employee") 
    @XmlJavaTypeAdapter(EmployeeAdapter.class) // (un)wraps Employee 
    List<Employee> employees; 
} 

@XmlType 
public class EmployeeRef { 

    @XmlIDREF 
    @XmlAttribute(name="ref") 
    Employee employee; 

    public EmployeeRef() { 
    } 

    public EmployeeRef(Employee employee) { 
     this.employee = employee; 
    } 
} 

public class EmployeeAdapter extends XmlAdapter<EmployeeRef, Employee> { 

    @Override 
    public EmployeeRef marshal(Employee employee) throws Exception { 
     return new EmployeeRef(employee); 
    } 

    @Override 
    public Employee unmarshal(EmployeeRef ref) throws Exception { 
     return ref.employee; 
    } 
} 

Buena suerte.

+0

yup, esto lo hizo el truco. Gracias. – marathon

+0

bien, esto no funciona en algunas (la mayoría) de las circunstancias. Consulte editar más arriba – marathon

+1

Anote la clase 'Empresa' con' @XmlType (propOrder = {"employees", "offices"}) 'para forzar el orden deseado al ordenar. – Torious

2

Nota: Soy el líder EclipseLink JAXB (MOXy) y miembro del grupo de expertos JAXB (JSR-222).

A continuación se muestra un ejemplo de cómo esto se puede hacer con MOXy aprovechando la anotación @XmlPath. Debido a un bug, deberá usar una etiqueta EclipseLink 2.4.0 nightly a partir del 17 de mayo de 2012. Esta solución también se debe agregar a la transmisión EclipseLink 2.3.3 (comenzando el 18 de mayo de 2012).Puede descargar una etiqueta de noche desde la siguiente ubicación:

Oficina

En la propiedad employees puede utilizar la anotación @XmlIDREF en combinación con el @XmlPath anotación para obtener el mapeo deseado @XmlIDREF le dice a la implementación de JAXB que escriba una clave externa en lugar del objeto.

package forum10150263; 

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

@XmlType 
public class Office { 
    @XmlAttribute 
    String name; 

    @XmlPath("employees/employee/@ref") 
    @XmlIDREF 
    List<Employee> employees; 
} 

Empleado

La contrapartida de @XmlIDREF es @XmlID. @XmlID se usa para especificar la clave primaria para un objeto.

package forum10150263; 

import javax.xml.bind.annotation.*; 

@XmlType 
public class Employee { 
    @XmlID 
    @XmlAttribute 
    String name; 

    @XmlAttribute 
    int years; 

    @XmlAttribute 
    String foo; 
} 

Company

Cada objeto referenciado por medio del mecanismo @XmlIDREF también necesita ser referenciado por una relación de contención. En este ejemplo, esto se realiza mediante la propiedad employees.

package forum10150263; 

import java.util.List; 
import javax.xml.bind.annotation.*; 

@XmlRootElement 
public class Company { 
    @XmlElementWrapper 
    @XmlElement(name = "office") 
    List<Office> offices; 

    @XmlElementWrapper 
    @XmlElement(name = "employee") 
    List<Employee> employees; 
} 

jaxb.properties

Para especificar moxy como su proveedor de JAXB es necesario agregar un archivo llamado jaxb.properties en el mismo paquete que el modelo de dominio con la siguiente entrada (ver Specifying EclipseLink MOXy as Your JAXB Provider)

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

demostración

Las API JAXB estándar se usan para desasignar/organizar el XML.

package forum10150263; 

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

public class Demo { 

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

     Unmarshaller unmarshaller = jc.createUnmarshaller(); 
     File xml = new File("src/forum10150263/input.xml"); 
     Company company = (Company) unmarshaller.unmarshal(xml); 

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

} 

input.xml/salida

<?xml version="1.0" encoding="UTF-8"?> 
<company> 
    <offices> 
     <office name="nyc"> 
     <employees> 
      <employee ref="alice"/> 
      <employee ref="bob"/> 
     </employees> 
     </office> 
     <office name="sf"> 
     <employees> 
      <employee ref="connie"/> 
      <employee ref="daphne"/> 
     </employees> 
     </office> 
    </offices> 
    <employees> 
     <employee name="alice" years="3" foo="bar"/> 
     <employee name="bob" years="3" foo="bar"/> 
     <employee name="connie" years="3" foo="bar"/> 
     <employee name="daphne" years="3" foo="bar"/> 
    </employees> 
</company> 

Para más información

Cuestiones relacionadas