Search code examples
jaxbeclipselinkmoxy

Same Object With Different Mappings Depending on Context (MOXy)


I'm using EclipseLink MOXy as my JAXB (JSR-222) provider and need some help with my mapping file to marshal my classes into XML.

I'm using an external file for my mapping.

I have two types of transactions: A and B. Both contain a header object (same object) with two fields (text1 and text2).

When marshalling these into XML, I would like the xml tag for the fields of the header of transactionA to become <headerA1> and <headerA2>, and for the ones linked to transactionB to become <headerB1> and <headerB2>.

Any idea how I could accomplish that (preferably without using inheritance)?

Here is the code:

HEADER Class

public class Header {

    private String text1;
    private String text2;

      public Header(){}

    public String getText1() {
        return text1;
    }

    public void setText1(String text1) {
        this.text1 = text1;
    }

    public String getText2() {
        return text2;
    }

    public void setText2(String text2) {
        this.text2 = text2;
    }

}

TRANSACTION A

public class TransactionA {

    private Header statementHeader;
    private BigDecimal units;
    private BigDecimal price;

    public TransactionA(){}

    public BigDecimal getUnits() {
        return units;
    }

    public void setUnits(BigDecimal units) {
        this.units = units;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public Header getStatementHeader() {
        return statementHeader;
    }

    public void setStatementHeader(Header statementHeader) {
        this.statementHeader = statementHeader;
    }

}

TRANSACTION B

public class TransactionB {

  private Header statementHeader;
    private BigDecimal units;
    private BigDecimal price;

    public TransactionB(){}

    public BigDecimal getUnits() {
        return units;
    }

    public void setUnits(BigDecimal units) {
        this.units = units;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public Header getStatementHeader() {
        return statementHeader;
    }

    public void setStatementHeader(Header statementHeader) {
        this.statementHeader = statementHeader;
    }

}

MAPPING FILE

<java-types>
  <java-type name="Statement" xml-accessor-type="NONE">
     <java-attributes>
        <xml-element java-attribute="tranA" />
        <xml-element java-attribute="tranB" />
     </java-attributes>
  </java-type>
  <java-type name="Header" xml-accessor-type="NONE">
     <java-attributes>
        <xml-element java-attribute="text1" name="headerA1" />
        <xml-element java-attribute="text2" name="headerA2" />
     </java-attributes>
  </java-type>
  <java-type name="TransactionA" xml-accessor-type="NONE">
     <java-attributes>
        <xml-element java-attribute="statementHeader" name="headerA" />
        <xml-element java-attribute="units" />
        <xml-element java-attribute="price"/>
     </java-attributes>
  </java-type>
  <java-type name="TransactionB" xml-accessor-type="NONE">
     <java-attributes>
        <xml-element java-attribute="statementHeader" name="headerB" />
        <xml-element java-attribute="units" />
        <xml-element java-attribute="price"/>
     </java-attributes>
  </java-type>
</java-types>

RESULT As you can see, the tags for header B are the same as the ones for header A.

<?xml version="1.0" encoding="UTF-8"?>
<tranA>
   <headerA>
      <headerA1>Description</headerA1>
      <headerA2>Units</headerA2>
   </headerA>
   <units>10</units>
   <price>99999999.98999999463558197021484375</price>
</tranA><tranB>
   <headerB>
      <headerA1>Bheader1</headerA1>
      <headerA2>Bheader2</headerA2>
   </headerB>
   <units>10</units>
   <price>99999999.98999999463558197021484375</price>
</tranB>

Solution

  • We have had an enhancement request open for this type of behaviour for a while now. If you could vote on the following but it would help move it up our priority list.


    HOW YOU COULD DO THIS TODAY

    XmlAdapter (HeaderBAdapter)

    You could use an XmlAdapter to provide an alternate mapping for the Header class.

    package forum13986357;
    
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    
    public class HeaderBAdapter extends XmlAdapter<HeaderBAdapter.AdaptedHeaderB, Header>{
    
        public static class AdaptedHeaderB {
            public String headerB1;
            public String headerB2;
        }
    
        @Override
        public AdaptedHeaderB marshal(Header header) throws Exception {
            AdaptedHeaderB adaptedHeaderB = new AdaptedHeaderB();
            adaptedHeaderB.headerB1 = header.getText1();
            adaptedHeaderB.headerB2 = header.getText2();
            return adaptedHeaderB;
        }
    
        @Override
        public Header unmarshal(AdaptedHeaderB adaptedHeaderB) throws Exception {
            Header header = new Header();
            header.setText1(adaptedHeaderB.headerB1);
            header.setText2(adaptedHeaderB.headerB2);
            return header;
        }
    
    }
    

    oxm.xml

    Below is an updated version of your mapping document. I have updated the mapping for the statementHeader property in the TransactionB class to specify that the XmlAdapter should be used.

    <?xml version="1.0"?>
    <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
        package-name="forum13986357">
        <java-types>
            <java-type name="Statement" xml-accessor-type="NONE">
                <java-attributes>
                    <xml-element java-attribute="tranA" />
                    <xml-element java-attribute="tranB" />
                </java-attributes>
            </java-type>
            <java-type name="Header" xml-accessor-type="NONE">
                <java-attributes>
                    <xml-element java-attribute="text1" name="headerA1" />
                    <xml-element java-attribute="text2" name="headerA2" />
                </java-attributes>
            </java-type>
            <java-type name="TransactionA" xml-accessor-type="NONE">
                <java-attributes>
                    <xml-element java-attribute="statementHeader" name="headerA" />
                    <xml-element java-attribute="units" />
                    <xml-element java-attribute="price" />
                </java-attributes>
            </java-type>
            <java-type name="TransactionB" xml-accessor-type="NONE">
                <java-attributes>
                    <xml-element java-attribute="statementHeader" name="headerB">
                        <xml-java-type-adapter value="forum13986357.HeaderBAdapter" />
                    </xml-element>
                    <xml-element java-attribute="units" />
                    <xml-element java-attribute="price" />
                </java-attributes>
            </java-type>
        </java-type
    

    s>

    jaxb.properties

    To use MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html):

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

    Demo

    The following demo code can be used to prove that everything works.

    package forum13986357;
    
    import java.util.*;
    import javax.xml.bind.*;
    import javax.xml.transform.stream.StreamSource;
    
    import org.eclipse.persistence.jaxb.JAXBContextProperties;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            Map<String, Object> properties = new HashMap<String, Object>(1);
            properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "forum13986357/oxm.xml");
            JAXBContext jc = JAXBContext.newInstance(new Class[] {Statement.class}, properties);
    
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            StreamSource xml = new StreamSource("src/forum13986357/input.xml");
            JAXBElement<Statement> jaxbElement = unmarshaller.unmarshal(xml, Statement.class);
    
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(jaxbElement, System.out);
        }
    
    }
    

    input.xml/Output

    <?xml version="1.0" encoding="UTF-8"?>
    <statement>
        <tranA>
            <headerA>
                <headerA1>Description</headerA1>
                <headerA2>Units</headerA2>
            </headerA>
            <units>10</units>
            <price>99999999.98999999463558197021484375</price>
        </tranA>
        <tranB>
            <headerB>
                <headerB1>Bheader1</headerB1>
                <headerB2>Bheader2</headerB2>
            </headerB>
            <units>10</units>
            <price>99999999.98999999463558197021484375</price>
        </tranB>