2012-01-20 16 views
5

Mam do czynienia z problemem rozrządowym/niemagającym, obejmującym dziedziczenie i polimorfizm za pomocą implementacji MOXy JAXB i zewnętrznego pliku powiązań metadanych.eclipselink/Moxy: przepełnienie dziedziczenia i nazwy atrybutu oparte na typie

Nie kontroluję plików XML ani klas modeli.

W modelu istnieje wiele klas dziedziczących inne klasy DTO. . Oto przykład środowiska pracuję w tym przykładzie jest tylko tutaj w jakimś celu składni, prawdziwe środowisko obejmuje zagnieżdżony dziedziczenia, kolekcje itp:

Oto klasy, które będą dziedziczone

class A { 

     private String name; 

     public String getName(){ 
       return name; 
     } 

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

    } 

Oto jeden dziedziczone klasy

class B extends A { 

     private String attrFromB; 

     public String getAttrFromB(){ 
       return attrFromB; 
     } 

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

I jeszcze

class C extends A { 

     private String attrFromC; 

     public String getAttrFromC(){ 
       return attrFromC; 
     } 

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

Oto klasa pojemnik

class MyContainerClass{ 

     private A myObject; 

     public A getMyObject(){ 
      return myObject; 
     } 

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

Oto XML, który powinien produkować w przypadku MyContainer zawierających

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

MyContainer zawierający b

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

And MyContainer zawierający C

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

Więc widać już problemy w horyzoncie ...

Tutaj jest plik mapowania, że ​​chciałbym napisać:

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

Pierwszym problemem jest to, że jeśli wiążę klas jak że pojawia się następujący wyjątek:

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

1st pytanie: rozumiem, że to jest normalne , Jaxb potrzebuje jakiegoś sposobu, aby określić typ atrybutu MyContaioner.myObject. Problem polega na tym, że nie mam dostępu do przychodzących plików XML, więc nie mogę dodawać do nich pól xsi: type. Czy istnieje sposób określania klasy na podstawie obecności w niej określonego atrybutu? niezależnie od jego wartości. Jeśli xml źródłowy zawiera atrybut @attrFromC, wiem, że obiekt powinien być typu C. Jeśli zawiera attrFromB, to B.


Drugim problemem jest to, że „nazwa” atrybut nie istnieje wewnątrz B i C, więc jaxb ignoruje je.

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

2-te pytanie: Innym problemem jest to, że nie wiem, czy jest w stanie JAXB nadrzędnymi xml nazw atrybutów jak oczekuje się wewnątrz pliku XML (@nameA, @nameB i nameC wszystkim odnosząc się do A. imię), czy jest jakiś sposób na zrobienie tego?

Z góry dziękuję za poświęcony czas.

Odpowiedz

4

Poniżej znajdują się odpowiedzi na twoje pytania. Odpowiedź na pytanie 2, jest również odpowiedź na pytanie 1.


1st pytanie: rozumiem, że to jest normalne, JAXB potrzebuje jakiś sposób aby określić rodzaj atrybutu MyContaioner.myObject. Problem polega na tym, że nie mam dostępu do przychodzących plików XML, więc nie mogę dodać do nich pól typu xsi: type. Czy istnieje sposób określenia klasy opartej na obecności określonego atrybutu? niezależnie od jego wartości. Jeśli xml źródłowy zawiera atrybut @attrFromC, wiem przedmiotem powinny być typu C. Jeśli zawiera attrFromB, to B.

Można wykorzystać rozszerzenie ClassExtractor w EclipseLink JAXB (MOXy) dla tego przypadku użycia:

MyClassExtractor

ClassExtractor A jest jakiś kod, który można wdrożyć w celu określenia, które Moxy klasa powinna instanitate. Otrzymasz Record i możesz poprosić o obecność atrybutów w bieżącym elemencie przez XPath, aby określić, która klasa powinna być utworzona.

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

} 

Metadane (oxm.xml)

Można skonfigurować ClassExtractor pomocą @XmlClassExtractor adnotacji. Możesz to również zrobić za pomocą zewnętrznego pliku metadanych. Mam zaadaptował jedną zawarte w swoim pytaniu do włączenia w ten sposób:

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

Poniższy kod demo unmarshals każdego z dokumentów XML z pytaniem, i wysyła typ przetrzymywany przez myObject Obiekt:

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

} 

Wyjście

[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-ty pytanie: Innym problemem jest to, że nie wiem, czy JAXB jest stanie nadrzędnymi xml nazw atrybutów jak oczekuje się wewnątrz pliku XML (@nameA, @nameB i nameC wszystko odnosząc na A.name), czy jest na to jakiś sposób?

Możesz użyć XmlAdapter dla tego pytania.Takie podejście może być również używany, aby odpowiedzieć na pierwsze pytanie:

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

} 

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

} 

Wyjście

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

Czytając pierwszą część swojej odpowiedzi, to już jest naprawdę pomocne. Dzięki. Nie mogę się doczekać drugiej części. – Drewman

+0

Dla pewności, jeśli w klasie Extractor, chcę przetestować obecność węzła, a nie atrybut. Czy mogę użyć record.get ("SomeNodeName") lub record.get ("SomeNodeName/text()")? – Drewman

+0

@ user1121108 - Dodałem odpowiedź dla drugiej części. W ekstraktorze klasowym będziesz mógł testować tylko obecność atrybutów. W tym momencie wykonania elementy podrzędne nie zostały jeszcze przetworzone. –

Powiązane problemy