2012-01-20 9 views
5

Sono di fronte a un problema di marshalling/unmarshalling che coinvolge ereditarietà e polimorfismo utilizzando l'implementazione JAXB di MOXy e il file di binding di metadati esterno.eclipselink/Moxy: ereditarietà e attributo del nome attributo basato sul tipo

ho alcun controllo sui file XML o le classi del modello.

Non ci sono più classi all'interno del modello che ereditano altre classi DTO. . Ecco un esempio di ambiente di cui sto lavorando in questo esempio è qui solo per qualche scopo della sintassi, l'ambiente reale comporta eredità nidificato, collezioni ecc:

Qui è la classe che verrà ereditato

class A { 

     private String name; 

     public String getName(){ 
       return name; 
     } 

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

    } 

Qui è una classe ereditata

class B extends A { 

     private String attrFromB; 

     public String getAttrFromB(){ 
       return attrFromB; 
     } 

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

E un altro

class C extends A { 

     private String attrFromC; 

     public String getAttrFromC(){ 
       return attrFromC; 
     } 

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

Ecco una classe contenitore

class MyContainerClass{ 

     private A myObject; 

     public A getMyObject(){ 
      return myObject; 
     } 

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

Ecco l'XML che dovrebbe produrre nel caso di myContainer contenente A myContainer

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

contenente B

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

E myContainer contenente C

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

Così si può già vedere problemi all'orizzonte ...

Ecco il file di mapping che avrei scritto:

<?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> 

Il primo problema è che se mi legano le classi come che, ottengo la seguente eccezione:

[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()]. 

prima domanda: capisco che questo è normale , Jaxb ha bisogno di un modo per determinare il tipo di attributo MyContaioner.myObject. Il problema è che non ho accesso ai file XML in arrivo, quindi non posso aggiungere xsi: digita i campi su di loro. C'è un modo per determinare una classe in base alla presenza di un attributo specifico in essa? indipendentemente dal suo valore. Se la sorgente XML contiene un attributo @attrFromC, so l'oggetto deve essere di tipo C. Se contiene attrFromB, è B.


Il secondo problema è che il "name" non esiste all'interno B e C, quindi jaxb ignora em.

--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. 

seconda domanda: L'altro problema è che non so se JAXB è in grado di sovrascrivere i nomi degli attributi XML, come ci si aspetta all'interno del file XML (@nameA, @nameB e nameC tutto riferiscono a A. nome), c'è un modo per farlo?

Grazie in anticipo per il vostro tempo.

risposta

4

Di seguito sono elencate le risposte alle tue domande. La risposta alla domanda 2, è anche una risposta alla domanda 1.


1 ° domanda: ho capito che questo è normale, JAXB ha bisogno di qualche modo per determinare il tipo di attributi MyContaioner.myObject. Il problema è che non ho accesso ai file XML in entrata, quindi non posso aggiungere a questi campi xsi: type. C'è un modo per determinare una classe basata su la presenza di un attributo specifico in esso? indipendentemente dal suo valore. Se l'XML di origine contiene un attributo @attrFromC, so che l'oggetto dovrebbe essere di tipo C. Se contiene attrFromB, è B.

È possibile sfruttare l'estensione ClassExtractor in EclipseLink JAXB (MOXy) per questo caso d'uso:

MyClassExtractor

Un ClassExtractor è un codice che è possibile implementare per aiutare Moxy determinare quale classe dovrebbe instanitate. Sei passato un Record e puoi chiedere la presenza degli attributi nell'elemento corrente da XPath per determinare quale classe deve essere istanziata.

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

} 

metadati (oxm.xml)

È possibile configurare il ClassExtractor utilizzando il @XmlClassExtractor annotazione. Puoi farlo anche tramite il file di metadati esterno. Ho adattato quello incluso nella sua domanda per includere questo:

<?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> 

Demo

Il seguente codice demo unmarshal ciascuno dei documenti XML da tua domanda, ed emette il tipo che si terrà dal myObject proprietà:

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

} 

uscita

[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 

2 ° domanda: L'altro problema è che non so se è JAXB in grado di rilevante xml attribuiscono nomi come ci si aspetta all'interno il file XML (@nameA, @nameB e nameC tutto riferendo a A.name), è c'è un modo per farlo?

Per questa domanda è possibile utilizzare uno XmlAdapter.Questo approccio può essere utilizzato anche per rispondere alla tua prima domanda:

AAdapter

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

} 

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

} 

uscita

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

Leggendo la prima parte della sua risposta, questo è già stato veramente utile. Grazie. Non posso aspettare la seconda parte. – Drewman

+0

Giusto per sicurezza, se all'interno di ClassExtractor, voglio testare la presenza di un nodo e non un attributo. Posso usare record.get ("SomeNodeName") o record.get ("SomeNodeName/text()")? – Drewman

+0

@ user1121108 - Ho aggiunto una risposta per la seconda parte. Nell'estrattore di classe sarai in grado di testare solo la presenza di attributi. A quel punto dell'esecuzione gli elementi secondari non sono ancora stati elaborati. –

Problemi correlati