2012-01-20 16 views
5

Estoy enfrentando un problema de marshalling/unmarshalling que implica herencia y polimorfismo utilizando la implementación JAXB de MOXy y el archivo de enlaces de metadatos externos.eclipselink/Moxy: herencia y nombre de atributo oveloading basado en el tipo

No tengo control sobre los archivos XML o las clases de modelo.

Hay varias clases dentro del modelo que heredan otras clases de DTO. . Aquí se muestra un ejemplo del medio ambiente que estoy trabajando en este ejemplo es sólo aquí con un propósito de sintaxis, el entorno real implica la herencia anidada, etc. colecciones:

Aquí está la clase que se hereda

class A { 

     private String name; 

     public String getName(){ 
       return name; 
     } 

     public void setName(String value){ 
       name = value; 
     } 

    } 

Aquí es una clase heredada

class B extends A { 

     private String attrFromB; 

     public String getAttrFromB(){ 
       return attrFromB; 
     } 

     public void setAttrFromB(String value){ 
       attrFromB = value; 
     } 
    } 

Y otra

class C extends A { 

     private String attrFromC; 

     public String getAttrFromC(){ 
       return attrFromC; 
     } 

     public void setAttrFromC(String value){ 
       attrFromC= value; 
     } 
    } 

Aquí está una clase de contenedor

class MyContainerClass{ 

     private A myObject; 

     public A getMyObject(){ 
      return myObject; 
     } 

     public void setMyObject(A value){ 
      myObject = value; 
     } 
    } 

Aquí es el XML que se debe producir en el caso de MyContainer que contiene A MyContainer

<MyContainer> 
     <MyObject nameA="foo" /> 
    </MyContainer> 

contiene B

<MyContainer> 
     <MyObject nameB="foo" attrFromB="bar" /> 
    </MyContainer> 

Y MyContainer que contiene C

<MyContainer> 
     <MyObject nameC="foo" attrFromC="bar" /> 
    </MyContainer> 

Así que ya se puede ver problemas en el horizonte ...

Aquí está el archivo de asignación que escribiría:

<?xml version="1.0"?> 
    <xml-bindings 
     xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" 
     package-name="com.test.example" 
     version="2.1"> 

     <java-type name="A" xml-accessor-type="NONE"> 
      <xml-root-element name="MyObject" /> 
      <java-attributes> 
       <xml-element java-attribute="name" xml-path="@nameA" /> 
      </java-attributes> 
     </java-type> 

     <java-type name="B" xml-accessor-type="NONE"> 
      <xml-root-element name="MyObject" /> 
      <xml-see-also> 
       com.test.example.A 
      </xml.see.also> 
      <java-attributes> 
       <xml-element java-attribute="name" xml-path="@nameB" /> 
       <xml-element java-attribute="attrFromB" xml-path="@attrFromB" /> 
      </java-attributes> 
     </java-type> 

     <java-type name="C" xml-accessor-type="NONE"> 
      <xml-root-element name="MyObject" /> 
      <xml-see-also> 
       com.test.example.A 
      </xml.see.also> 
      <java-attributes> 
       <xml-element java-attribute="name" xml-path="@nameC" /> 
       <xml-element java-attribute="attrFromC" xml-path="@attrFromC" /> 
      </java-attributes> 
     </java-type> 

     <java-type name="MyContainer" xml-accessor-type="NONE"> 
      <xml-root-element name="MyContainer" /> 
      <java-attributes> 
       <xml-element java-attribute="myObject" type="com.test.example.A" xml-path="MyObject" /> 
      </java-attributes> 
     </java-type> 

    </xml-bindings> 

El primer problema es que si me ato las clases como que, tengo la siguiente excepción:

[Exception [EclipseLink-44] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DescriptorException 
    Exception Description: Missing class indicator field from database row [UnmarshalRecord()]. 

primera pregunta: entiendo que esto es normal , Jaxb necesita alguna forma de determinar el tipo de atributo MyContaioner.myObject. El problema es que no tengo acceso a los archivos XML entrantes, así que no puedo agregar xsi: escriba campos para ellos. ¿Hay alguna forma de determinar una clase basada en la presencia de un atributo específico en ella? independientemente de su valor. Si el código fuente XML contiene un atributo @attrFromC, sé que el objeto debe ser de tipo C. Si contiene attrFromB, es B.


El segundo problema es que el atributo "name" no existe en el interior B y C, entonces jaxb los ignora.

--Ignoring attribute [name] on class [com.test.example.B] as no Property was generated for it. 
    --Ignoring attribute [name] on class [com.test.example.C] as no Property was generated for it. 

segunda pregunta: El otro problema es que no sé si JAXB es capaz de anular xml nombres de atributo como se espera dentro del archivo XML (@nameA, @nameB y NAMEC todo lo referente a A. nombre), ¿hay alguna manera de hacerlo?

Gracias de antemano por su tiempo.

Respuesta

4

A continuación se encuentran las respuestas a sus preguntas. La respuesta a la pregunta 2, es también una respuesta a la pregunta 1.


primera pregunta: ¿Entiendo que esto es normal, JAXB necesita alguna manera para determinar el tipo de atributo MyContaioner.myObject. El problema es que no tengo acceso a los archivos XML entrantes, así que no puedo agregar xsi: escriba campos para ellos. ¿Hay alguna manera de determinar una clase basada en la presencia de un atributo específico en ella? independientemente de su valor. Si el XML de origen contiene un atributo @attrFromC, sé el objeto debe ser de tipo C. Si contiene attrFromB, es B.

Puede aprovechar la extensión ClassExtractor en EclipseLink JAXB (MOXy) para este caso de uso:

MyClassExtractor

Un ClassExtractor es un código que se puede poner en práctica para ayudar a determinar qué clase moxy debe instanitate. Le pasaron un Record y puede solicitar la presencia de los atributos en el elemento actual por parte de XPath para determinar qué clase debe ser instanciada.

package com.test.example; 

import org.eclipse.persistence.descriptors.ClassExtractor; 
import org.eclipse.persistence.sessions.*; 

public class MyClassExtractor extends ClassExtractor{ 

    @Override 
    public Class<?> extractClassFromRow(Record record, Session session) { 
     if(null != record.get("@attrFromB")) { 
      return B.class; 
     } else if(null != record.get("@attrFromC")) { 
      return C.class; 
     } else { 
      return A.class; 
     } 
    } 

} 

metadatos (oxm.xml)

puede configurar el ClassExtractor utilizando la anotación @XmlClassExtractor. También puede hacerlo a través del archivo de metadatos externo. He adaptado al incluido en su pregunta para incluir lo siguiente:

<?xml version="1.0"?> 
<xml-bindings 
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" 
    package-name="com.test.example" 
    version="2.3"> 
    <java-types> 
     <java-type name="A" xml-accessor-type="NONE"> 
      <xml-class-extractor class="com.test.example.MyClassExtractor"/> 
      <xml-root-element name="MyObject" /> 
      <java-attributes> 
       <xml-attribute java-attribute="name" name="nameA" /> 
      </java-attributes> 
     </java-type> 
     <java-type name="B" xml-accessor-type="NONE"> 
      <xml-root-element name="MyObject" /> 
      <java-attributes> 
       <xml-attribute java-attribute="name" name="nameB" /> 
       <xml-attribute java-attribute="attrFromB"/> 
      </java-attributes> 
     </java-type> 
     <java-type name="C" xml-accessor-type="NONE"> 
      <xml-root-element name="MyObject" /> 
      <java-attributes> 
       <xml-attribute java-attribute="name" name="nameC" /> 
       <xml-attribute java-attribute="attrFromC"/> 
      </java-attributes> 
     </java-type> 
     <java-type name="MyContainerClass" xml-accessor-type="NONE"> 
      <xml-root-element name="MyContainer" /> 
      <java-attributes> 
       <xml-element java-attribute="myObject" name="MyObject" /> 
      </java-attributes> 
     </java-type> 
    </java-types> 
</xml-bindings> 

demostración

El siguiente código de demostración descoloca cada uno de los documentos XML a partir de su pregunta, y emite el tipo que se celebra por el myObject propiedad:

package com.test.example; 

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

public class Demo { 

    public static void main(String[] args) throws Exception { 
     Map<String, Object> properties = new HashMap<String, Object>(); 
     properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "com/test/example/oxm.xml"); 
     JAXBContext jc = JAXBContext.newInstance(new Class[] {MyContainerClass.class}, properties); 
     Unmarshaller unmarshaller = jc.createUnmarshaller(); 

     StringReader aXml = new StringReader("<MyContainer><MyObject nameA='foo'/></MyContainer>"); 
     MyContainerClass myContainerA = (MyContainerClass) unmarshaller.unmarshal(aXml); 
     System.out.println(myContainerA.getMyObject().getClass()); 

     StringReader bXml = new StringReader("<MyContainer><MyObject nameB='foo' attrFromB='bar'/></MyContainer>"); 
     MyContainerClass myContainerB = (MyContainerClass) unmarshaller.unmarshal(bXml); 
     System.out.println(myContainerB.getMyObject().getClass()); 

     StringReader cXml = new StringReader("<MyContainer><MyObject nameC='foo' attrFromC='bar'/></MyContainer>"); 
     MyContainerClass myContainerC = (MyContainerClass) unmarshaller.unmarshal(cXml); 
     System.out.println(myContainerC.getMyObject().getClass()); 
    } 

} 

salida

[EL Warning]: 2012-01-20 10:36:41.828--Ignoring attribute [name] on class [com.test.example.B] as no Property was generated for it. 
[EL Warning]: 2012-01-20 10:36:41.828--Ignoring attribute [name] on class [com.test.example.C] as no Property was generated for it. 
class com.test.example.A 
class com.test.example.B 
class com.test.example.C 

segunda pregunta: ¿El otro problema es que no sé si es JAXB capaces de anular los nombres de atributos XML como se espera dentro el archivo XML (@nameA, @nameB y todo lo referente NAMEC a A.name), es hay una manera de hacerlo?

Puede aprovechar un XmlAdapter para esta pregunta.Este enfoque también se puede utilizar para responder a su primera pregunta:

aAdaptador

package com.test.example; 

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

public class AAdapter extends XmlAdapter<AAdapter.AdaptedA, A> { 

    @Override 
    public AdaptedA marshal(A a) throws Exception { 
     if(null == a) { 
      return null; 
     } 
     AdaptedA adaptedA = new AdaptedA(); 
     if(a instanceof C) { 
      C c = (C) a; 
      adaptedA.nameC = c.getName(); 
      adaptedA.attrFromC = c.getAttrFromC(); 
     } else if(a instanceof B) { 
      B b = (B) a; 
      adaptedA.nameB = b.getName(); 
      adaptedA.attrFromB = b.getAttrFromB(); 
     } else if(a instanceof A) { 
      adaptedA.nameA = a.getName(); 
     } 
     return adaptedA; 
    } 

    @Override 
    public A unmarshal(AdaptedA adaptedA) throws Exception { 
     if(null == adaptedA) { 
      return null; 
     } 
     if(null != adaptedA.attrFromC) { 
      C c = new C(); 
      c.setName(adaptedA.nameC); 
      c.setAttrFromC(adaptedA.attrFromC); 
      return c; 
     } else if(null != adaptedA.attrFromB) { 
      B b = new B(); 
      b.setName(adaptedA.nameB); 
      b.setAttrFromB(adaptedA.attrFromB); 
      return b; 
     } 
     A a = new A(); 
     a.setName(adaptedA.nameA); 
     return a; 
    } 

    public static class AdaptedA { 
     @XmlAttribute public String nameA; 
     @XmlAttribute public String nameB; 
     @XmlAttribute public String nameC; 
     @XmlAttribute public String attrFromB; 
     @XmlAttribute public String attrFromC; 
    } 

} 

metadatos (OXM-2.xml)

<?xml version="1.0"?> 
<xml-bindings 
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" 
    package-name="com.test.example" 
    version="2.3"> 
    <java-types> 
     <java-type name="MyContainerClass" xml-accessor-type="NONE"> 
      <xml-root-element name="MyContainer" /> 
      <java-attributes> 
       <xml-element java-attribute="myObject" name="MyObject"> 
       <xml-java-type-adapter value="com.test.example.AAdapter"/> 
       </xml-element> 
      </java-attributes> 
     </java-type> 
    </java-types> 
</xml-bindings> 

Demo2

package com.test.example; 

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

public class Demo2 { 

    public static void main(String[] args) throws Exception { 
     Map<String, Object> properties = new HashMap<String, Object>(); 
     properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "com/test/example/oxm-2.xml"); 
     JAXBContext jc = JAXBContext.newInstance(new Class[] {MyContainerClass.class}, properties); 
     Unmarshaller unmarshaller = jc.createUnmarshaller(); 
     Marshaller marshaller = jc.createMarshaller(); 
     marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 

     StringReader aXml = new StringReader("<MyContainer><MyObject nameA='foo'/></MyContainer>"); 
     MyContainerClass myContainerA = (MyContainerClass) unmarshaller.unmarshal(aXml); 
     System.out.println(myContainerA.getMyObject().getClass()); 
     marshaller.marshal(myContainerA, System.out); 

     StringReader bXml = new StringReader("<MyContainer><MyObject nameB='foo' attrFromB='bar'/></MyContainer>"); 
     MyContainerClass myContainerB = (MyContainerClass) unmarshaller.unmarshal(bXml); 
     System.out.println(myContainerB.getMyObject().getClass()); 
     marshaller.marshal(myContainerB, System.out); 

     StringReader cXml = new StringReader("<MyContainer><MyObject nameC='foo' attrFromC='bar'/></MyContainer>"); 
     MyContainerClass myContainerC = (MyContainerClass) unmarshaller.unmarshal(cXml); 
     System.out.println(myContainerC.getMyObject().getClass()); 
     marshaller.marshal(myContainerC, System.out); 
    } 

} 

salida

class com.test.example.A 
<?xml version="1.0" encoding="UTF-8"?> 
<MyContainer> 
    <MyObject nameA="foo"/> 
</MyContainer> 
class com.test.example.B 
<?xml version="1.0" encoding="UTF-8"?> 
<MyContainer> 
    <MyObject nameB="foo" attrFromB="bar"/> 
</MyContainer> 
class com.test.example.C 
<?xml version="1.0" encoding="UTF-8"?> 
<MyContainer> 
    <MyObject nameC="foo" attrFromC="bar"/> 
</MyContainer> 
+1

La lectura de la primera parte de su respuesta, esto ya está siendo realmente útil. Gracias. No puedo esperar para la segunda parte. – Drewman

+0

Solo para estar seguro, si dentro de ClassExtractor, quiero probar la presencia de un nodo y no un atributo. ¿Puedo usar record.get ("SomeNodeName") o record.get ("SomeNodeName/text()")? – Drewman

+0

@ user1121108 - He agregado una respuesta para la segunda parte. En el extractor de clases, solo podrás probar la presencia de atributos. En ese punto de la ejecución, los elementos secundarios aún no se han procesado. –

Cuestiones relacionadas