JAXB 2.x : How to override an XmlElement annotation from parent class - Mission Impossible?

21,684

Solution 1

Just don't annotate the source property on class B. The source property was mapped on the parent class and should not be mapped again on the child class. Since you are annotating the get/set methods the appropriate get/set will be called on class B.

package test;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="root")
public class B extends A {

  private StringBuffer source = null;

  public String getSource() {
    return source.toString();
  }

  public void setSource(String source) {
    this.source = new StringBuffer(source);
  }
}

UPDATE

There may be a bug in the Metro JAXB (reference implementation). When I run this updated example with EclipseLink JAXB (MOXy) I get the following output:

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <source>
      <string1>1</string1>
      <string2>2</string2>
   </source>
</root>
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="b">
   <source xsi:type="dataB">
      <string1>1</string1>
      <string2>2</string2>
      <string3>3</string3>
   </source>
</root>

This can be reproduced with the following code:

package test;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class Demo {

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

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        A a = new A();
        DataA da = new DataA();
        da.string1 = "1";
        da.string2 = "2";
        a.setSource(da);
        marshaller.marshal(a, System.out);

        B b = new B();
        DataB db = new DataB();
        db.string1 = "1";
        db.string2 = "2";
        db.string3 = "3";
        b.setSource(db);
        marshaller.marshal(b, System.out);
    }
}

To use MOXy as the JAXB implementation you need to supply a file named jaxb.properties in the model package (test) with the following entry:

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

Solution 2

you don't need to use MOXy.. Just change the Class B and use @XmlAlso.

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "root")
public class B extends A {
}


@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({ DataA.class, DataB.class })
@XmlRootElement(name = "source")
@XmlType(name = "source")

public class DataA {
   private String string1 = "1";
   private String string2 = "2";
.....//getters and setters here
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "source")
public class DataB extends DataA {
    private String string3 = "3";
.....//getters and setters here
}

@XmlAccessorType(XmlAccessType.FIELD) 
@XmlSeeAlso({ A.class, B.class }) 
@XmlRootElement(name = "root") 

public class A {

    private DataA source = new DataA();

    public DataA getSource() {
        return source;
    }

    public void setSource(DataA source) {
        this.source = source;
    }

}


 B b = new B();
        DataB db = new DataB();
        db.setString1("1");
        db.setString2("2");
        db.setString3("3");
        b.setSource(db);
        marshaller.marshal(b, System.out);

WILL FINALLY WRITE:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <source>
        <string1>1</string1>
        <string2>2</string2>
    </source>
</root>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <source xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="dataB">
        <string1>1</string1>
        <string2>2</string2>
        <string3>3</string3>
    </source>
</root>
Share:
21,684
basZero
Author by

basZero

WORK: Web App Developer, Java Enthusiast since 1995 HOME: I'm in love with photography

Updated on July 05, 2022

Comments

  • basZero
    basZero almost 2 years

    Why is this not possible? It seems so simple but it does not behave as expected.

    Summary: Class A uses an aggregated DataA bean whereas Class B (a subclass of Class A) is using an aggregated DataB bean (whereas DataB extends DataA).

    I wrote these test classes to visualize and explain my question:

    Class A:

    package test;
    
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement(name="root")
    public class A {
    
      private DataA source = new DataA();
    
      @XmlElement(name="source")
      public DataA getSource() {
        return source;
      }
    
      public void setSource(DataA source) {
        this.source = source;
      }
    
    }
    

    and its DataA class (I used the FIELD annotation so that all fields gets marshalled):

    package test;
    
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    
    @XmlAccessorType(XmlAccessType.FIELD)
    public class DataA {
    
        public String string1 = "1";
        public String string2 = "2";
    
    }
    

    And now the Class B (subclass of Class A): My goal is to reuse functionalities of A and also reuse the properties from the DataA bean by using the DataB bean:

    package test;
    
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement(name="root")
    public class B extends A {
    
      private DataB source = new DataB();
    
      public DataB getSource() {
        return this.source;
      }
    
      public void setSource(DataB source) {
        this.source = source;
      }
    
    }
    

    Its corresponding DataB bean looks like this:

    package test;
    
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    
    @XmlAccessorType(XmlAccessType.FIELD)
    public class DataB extends DataA {
        public String string3 = "3";
    }
    

    Now, when I marshall an instance of class A, it gives this output:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root>
      <source>
        <string1>1</string1>
        <string2>2</string2>
      </source>
    </root>
    

    When I marshall an instance of class B, I get the very same result:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root>
      <source>
        <string1>1</string1>
        <string2>2</string2>
      </source>
    </root>
    

    But I expected that also string3 would get marshalled, but it is only writing the properties of bean DataA! WHY? This is not really intuitive when thinking in terms of OOP.

    When I set the @XmlElement annotation also on the Class B... like this:

    @XmlElement
    public DataB getSource() {
        return this.source;
    }
    

    ... then the property gets marshalled twice because it is once annotated by the parent class as well as by the child class. This is also what I do not want:

    The output now is:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root>
        <source xsi:type="dataB" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <string1>1</string1>
            <string2>2</string2>
            <string3>3</string3>
        </source>
        <source>
            <string1>1</string1>
            <string2>2</string2>
            <string3>3</string3>
        </source>
    </root>
    

    What I expected from JAXB as a result is the following XML:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root>
        <source>
            <string1>1</string1>
            <string2>2</string2>
            <string3>3</string3>
        </source>
    </root>
    

    Any hints how to tweak JAXB to produce the expected result?? Thanks for any feedback.

  • jmj
    jmj over 13 years
    +1 nice to see you :) , 1 Q. in base class its private then how will it come to child class too ? is it due to overridden setter getters ?
  • basZero
    basZero over 13 years
    That's great and really helps!
  • basZero
    basZero over 13 years
    I will compile another example where not only a String is used: Let's say class A uses Bean with 2 string properties whereas class B uses a Bean with 3 properties. If I do not annotate class B, only the 2 parameters are marshalled instead of 3... let me prepare the example...
  • bdoughan
    bdoughan over 13 years
    Jigar Joshi - In this example it is the accessors that are annotated. So when populating the object, a JAXB impl will use set methods. These set methods can be different on the parent and child as long as they are named the same.
  • bdoughan
    bdoughan over 13 years
    bzero - No problem, if this solves your problem please consider accepting and/or up voting this answer.
  • basZero
    basZero over 13 years
    No it is not yet solved, I'll update my question then a better example with incorporating your initial hint.
  • jmj
    jmj over 13 years
    These set methods can be different on the parent and child as long as they are named the same. You mean object of Method using reflection ?
  • basZero
    basZero over 13 years
    @Blaise: thanks! Actually I just use Java SE 6, forgot to mention that. I think there is even another JAXB impl in it compared to the RI. I will try out MOXy
  • basZero
    basZero over 13 years
    HOW TO INSTALL: Is it possible to use only MOXy or do I need to download the whole EclipseLink JARs?
  • bdoughan
    bdoughan over 13 years
    @bzero - Java SE 6 from Oracle (Sun) will contain the JAXB reference implementation Metro. I'm the MOXy tech lead.
  • bdoughan
    bdoughan over 13 years
    You can use MOXy with either of the following configurations: 1) eclipselink.jar 2) Bundles: org.eclipse.persistence.asm, org.eclipse.persistence.core, and org.eclipse.persistence.moxy.
  • basZero
    basZero over 13 years
    @Blaise: I see... So Oracle should update the SE6! I'm about to install the MOXy via Update Center in Eclipse... if I put the jaxb.properties into the package folder of my JAXB-beans only, I assume that there will be no impact on other XML binding libs in my application, right?
  • bdoughan
    bdoughan over 13 years
    @bzero - SE6 receives regular updates with fixes to the JAXB RI. You should report this issue as a Metro bug at: java.net/jira/browse/JAXB
  • basZero
    basZero over 13 years
    @Blaise: it works now for me too with MOXy: <?xml version="1.0" encoding="UTF-8"?> <root xmlns:xsi="w3.org/2001/XMLSchema-instance" xsi:type="b"> <source> <string1>1</string1> <string2>2</string2> <string3>3</string3> </source> </root>
  • basZero
    basZero over 13 years
    Why does it print out the xsi:type ?
  • bdoughan
    bdoughan over 13 years
    @bzero - As far as JAXB is concerned the property is of type DataA, since this is the property type on the super class. When an instance of DataB is marshalled the xsi:type attribute is used to qualify it as an instance of a sub type. Check out: bdoughan.blogspot.com/2010/11/…
  • basZero
    basZero over 13 years
    LINE NOT WORKING with MOXy: m.setProperty("com.sun.xml.internal.bind.namespacePrefixMapp‌​er", mapper);
  • basZero
    basZero over 13 years
    Can you comment on this: stackoverflow.com/questions/4661263/…
  • bdoughan
    bdoughan over 13 years
    MOXy does not support the namespacePrefixMapper, however you can do this much easier in MOXy. MOXy will use the prefixes as specified in the @XmlSchema annotation. For an example see: stackoverflow.com/questions/3289644/…
  • basZero
    basZero over 13 years
    Amazing, thanks! now it fully works as it should have worked 1 day ago.
  • basZero
    basZero over 13 years
    Hi, that's an interesting approach but did you mix up something? Because there is class A and there is "B extends A", so A does not know anything about B. Applying an annotation in class A which actually references class B is breaking the object oriented data modeling... ?
  • basZero
    basZero over 13 years
    Maybe you can apply the @Also only to class B? then it would be ok and I'd try it out.
  • basZero
    basZero almost 8 years
    @BlaiseDoughan: i'm upgrading from Java 1.6 to Java 1.8 now. Would you still recommend to go with Moxy?