rawat
rawat

Reputation: 165

JAXB marshal polymorphic POJO to XML

I am trying to use JAXB to marshal class file(with annotations). Under <profile-set> it can have different tags for e.g.

<organization-information-profile>
<connection-profile>
<user-information-profile>

Sample output XML files are as below

a)

    <?xml version="1.0"?>
    <request version="2.0" principal="111" credentials="xxxxx">
      <target name="TestAPI" operation="create">
        <parameter>
          <organization>
            <qualified-name>some-qualified-name</qualified-name>
            <profile-set>
              <name>TestOrg</name>
              <organization-information-profile>
                <name>Organization Information</name>
                <qualified-name>/Organization Information</qualified-name>
                <last-name>Test</last-name>
                <address>some-address</address>
                <city>my-city</city>
                <province></province>
                <postal-code>1111</postal-code>
                <country>Timbaktu</country>
                <phone-number-day>1111</phone-number-day>
                <email-address>[email protected]</email-address>
                <attribute name="PhoneNumber1">
                  <value context="organization">23333</value>
                </attribute>
                <attribute name="ShortName">
                  <value context="organization">my company</value>
                </attribute>
                <attribute name="TaxId">
                  <value context="organization">myorg</value>
                </attribute>
              </organization-information-profile>
            </profile-set>
          </organization>
        </parameter>
      </target>
</request>

b)

        <?xml version="1.0"?>
        <request version="2.0" principal="11111" credentials="xxxxx">
          <target name="TestAPI" operation="update">
            <parameter>
              <organization>
                <qualified-name>some-qualified-name</qualified-name>
                <profile-set>
                  <name>TestOrg</name>
                  <connection-profile>
                    <qualified-name>some-qualified-name</qualified-name> 
                    <service>
                      <name>some service</name> 
                    </service>
                    <attribute name="att-1">
                      <value context="organization" segment="some-segment" subscript="524288">fill-the-value</value> 
                    </attribute>
                    <attribute name="att-2">
                      <value context="organization" segment="some-segment" subscript="524288">qedqwe</value> 
                    </attribute>            
                  </connection-profile>
                </profile-set>
              </organization>
            </parameter>
          </target>
        </request> 

Below is the code (only profile-set)

 public static class ProfileSet
    {
        @XmlElement(name = "name")
        public String name;

        // innerPayLoad is template to store different profile objects
        @XmlJavaTypeAdapter(CustomAdaptor.class)
        @XmlElement
        public InnerPayLoad innerPayLoad;

        public ProfileSet(String name, InnerPayLoad innerPayLoad)
        {
            this.name = name;
            this.innerPayLoad = innerPayLoad;
        }

    }

And CustomAdaptor

public class CustomAdaptor extends XmlAdapter<String,InnerPayLoad<?>>
{


    @Override
    public InnerPayLoad<?> unmarshal(String v) throws Exception
    {
        return null;
    }

    @Override
    public String marshal(InnerPayLoad<?> v) throws Exception
    {
         String value = TestCode.convertToXmlNoHeader(v.whichProfile,v.whichProfile.getClass());

         // after converting value becomes 

        //  <organization-information-profile>
        //      <name>Organization Information</name>
        //  </organization-information-profile> 

        return value;
    }
}       

But the final XML produced is not similar to (a) for organization-information-profile

 <?xml version='1.0' encoding='UTF-8'?>
<request version="2.0" principle="11111" credentials="xxxxx">
  <target name="TestAPI" operation="create">
    <parameter>
      <organization>
        <qualified-name>newOrg</qualified-name>
        <profile-set>
          <innerPayLoad>&lt;organization-information-profile>
    &lt;name>Organization Information&lt;/name>
&lt;/organization-information-profile></innerPayLoad>
          <name>testOrg</name>
        </profile-set>
      </organization>
    </parameter>
  </target>
</request>

Is it possible to remove <innerPayLoad> tag and just insert with CustomAdaptor marshal function return value?

Appreciate help and hints to solve this issue.

Upvotes: 2

Views: 164

Answers (1)

Thomas Fritsch
Thomas Fritsch

Reputation: 10127

You don't need to write a custom adapter for the various profile types within your ProfileSet.

Instead, to handle such mixed XML Content the canonical approach goes like this.

In your ProfileSet class you should define a polymorphic Java property profile which can take the contents of a <organization.information-profile>, <connection-profile> or <user-information-profile> element. (I preferred the name profile here instead of innerPayload). The mapping between these XML element names and Java classes is done by using the @XmlElements annotation.

@XmlAccessorType(XmlAccessType.FIELD)
public class ProfileSet {

    @XmlElement(name = "name")
    private String name;

    // template to store different profile objects    
    @XmlElements({
        @XmlElement(name = "organization-information-profile", type = OrganizationInfomationProfile.class),
        @XmlElement(name = "connection-profile", type = ConnectionProfile.class),
        @XmlElement(name = "user-information-profile", type = UserInformationProfile.class)
    })
    private Profile profile;

    // default constructor used by JAXB unmarshaller
    public ProfileSet() {
    }

    public ProfileSet(String name, Profile profile) {
        this.name = name;
        this.profile = profile;
    }
 }

You need an abstract super-class Profile containing only the properties common to all kinds of profiles:

@XmlAccessorType(XmlAccessType.FIELD)
public abstract class Profile {

    @XmlElement
    private String name;

    @XmlElement(name = "attribute")
    private List<Attribute> attributes;
}

You have one subclass OrganizationInformationProfile for representing the <organization-information-profile> element

@XmlAccessorType(XmlAccessType.FIELD)
public class OrganizationInfomationProfile extends Profile {

    @XmlElement(name = "qualified-name")
    private String qualifiedName;

    @XmlElement(name = "last-name")
    private String lastName;

    @XmlElement(name = "address")
    private String address;

    // ... other properties

}

and another subclass ConnectionProfile for representing the <connection-profile> element

@XmlAccessorType(XmlAccessType.FIELD)
public class ConnectionProfile extends Profile {

    @XmlElement(name = "service")
    private Service service;
}

and yet another subclass UserInformationProfile for representing the <user-information-profile> element.

By using the above approach you can unmarshal your XML examples and get the same output again when marshalling.

Upvotes: 1

Related Questions