eclipselink/Moxy : inheritance and attribute name oveloading based on type

后端 未结 1 1976
野趣味
野趣味 2021-01-12 18:57

I\'m facing a marshalling/unmarshalling problem involving inheritance and polymorphism using MOXy\'s JAXB implementation and external metadata bindings file.

I have

相关标签:
1条回答
  • 2021-01-12 19:49

    Below are the answers to your questions. The answer to question 2, is also an answer to question 1.


    1st question : I understand that this is normal, Jaxb needs some way to determine the type of MyContaioner.myObject attribute. The problem is that I have no access to the incoming XML files, so I cant add xsi:type fields to them. Is there a way to determine a class based on the presence of a specific attribute in it ? regardless of it's value. If the source xml contains a @attrFromC attribute, I know the object should be of type C. If it contains attrFromB, it's B.

    You can leverage the ClassExtractor extension in EclipseLink JAXB (MOXy) for this use case:

    MyClassExtractor

    A ClassExtractor is some code that you can implement to help MOXy determine which class it should instanitate. You are passed a Record and you can ask for the presence of the attributes at the current element by XPath to determine which class should be instantiated.

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

    Metadata (oxm.xml)

    You can configure the ClassExtractor using the @XmlClassExtractor annotation. You can also do this via the external metadata file. I have adapted the one included in your question to include this:

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

    The following demo code unmarshals each of the XML documents from your question, and outputs the type being held by the myObject property:

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

    Output

    [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
    

    2nd question : The other problem is that I dont know if Jaxb is capable of overriding xml attribute names like it is expected inside the XML file (@nameA, @nameB and nameC all referring to A.name), is there a way to do it ?

    You can leverage an XmlAdapter for this question. This approach can also be used to answer your first question:

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

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

    Output

    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>
    
    0 讨论(0)
提交回复
热议问题