JAXB marshal polymorphic POJO to XML - java

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>some#email.com</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><organization-information-profile>
<name>Organization Information</name>
</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.

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.

Related

Unmarshalling of XML key-value pair in java object

Unmarshall XML key value part into java object using jaxb. I have tried using map adapter, but I'm not able to do it.
<ACCOUNT_CHANGES>
<TYPE value="Active" />
<RECORD>
<SUBSCRIPTION>
<INFO key="aaa">
<![CDATA[042]]>
</INFO>
<INFO key="bbb">
<![CDATA[45]]>
</INFO>
<INFO key="uuid">
<![CDATA[d9a7e94c-0a9d-c745-82e9-980877cc5043]]>
</INFO>
<INFO key="ccc">
<![CDATA[Active]]>
</INFO>
<INFO key="companyname">
<![CDATA[ltd]]>
</INFO>
</SUBSCRIPTION>
</RECORD>
</ACCOUNT_CHANGES>
You could use an adapter to do that, but as I understand your requirement it is not necessary. If you simply want to unmarshall into an object, not into a Map, you can do the following:
Starting from the root:
#XmlRootElement(name = "ACCOUNT_CHANGES")
#XmlAccessorType(XmlAccessType.FIELD)
public class AccountChanges {
#XmlElement(name = "TYPE")
private Type type;
#XmlElement(name = "RECORD")
private Record record;
}
Let's take Type out of the way:
#XmlAccessorType(XmlAccessType.FIELD)
public class Type {
#XmlAttribute
private String value;
}
Then the record:
#XmlAccessorType(XmlAccessType.FIELD)
public class Record {
#XmlElement(name = "SUBSCRIPTION")
private Subscription subscription;
}
And the subscription:
#XmlAccessorType(XmlAccessType.FIELD)
public class Subscription {
#XmlElement(name = "INFO")
private List<Info> infoList;
}
Info has your key as attribute and then some value. It would look like this:
#XmlAccessorType(XmlAccessType.FIELD)
public class Info {
#XmlAttribute
private String key;
#XmlValue
private String value;
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}
This will unmarshal your xml, and info keys and values will be in the fields. In case you want the key and value in a map, you can use an adapter.
The adapter looks like this:
public class MyMapAdapter extends XmlAdapter<Info, Map<String, String>> {
private HashMap<String, String> hashMap = new HashMap<String, String>();
#Override
public Map<String, String> unmarshal(Info v) throws Exception {
hashMap.put(v.getKey(), v.getValue());
return hashMap;
}
#Override
public Info marshal(Map<String, String> v) throws Exception {
// do here actions for marshalling if u also marshal
return null;
}
}
And you will change the Subscription to use the adapter and have map as a field:
#XmlAccessorType(XmlAccessType.FIELD)
public class Subscription {
#XmlElement(name = "INFO")
#XmlJavaTypeAdapter(MyMapAdapter.class)
private Map<String, String> infoMap;
}
Two ways, both unmarshal your xml payload.
Cheers

Java xml unmarshalling list nested object issue

I have a xml with content below:
<ParentClass>
<StringA>A</StringA>
<StringB>B</StringB>
<Items>
<Item ts="2016-03-25T20:00:00+02:00">1.17</Item>
<Item ts="2016-03-25T21:00:00+02:00">1.15</Item>
</Items>
</ParentClass>
I would like to read it but I got stuck on proper mapping. Classes are as below:
#XmlAccessorType(XmlAccessType.FIELD)
#NoArgsConstructor
#AllArgsConstructor
#Data
public class ParentClass {
#XmlElement(name = "StringA", required = true)
private String a;
#XmlElement(name = "StringB", required = true)
private String b;
#XmlElement(name = "Items", required = true)
private List<Item> consumptionList;
}
#NoArgsConstructor
#AllArgsConstructor
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
#XmlAttribute(name = "ts", required = true)
#XmlJavaTypeAdapter(value = LocalDateTimeAdapter.class)
private LocalDateTime timestamp;
private double value; //this corrensponds to 1.17 and 1.15 in xml
}
In the actual file there are 100+ items yet when I read it the list is populated with only one instance of Item class and it has both fields null.
I guess the mapping in the Item class is all wrong but tried all and nothing seems to work.
How should I properly map it to achieve the goal?
You need to add #XmlValue to field value, otherwise it defaults to #XmlElement.
Also, you need to change the annotations on consumptionList to
#XmlElementWrapper(name = "Items")
#XmlElement(name = "Item", required = true)
Also be aware that the ts values are OffsetDateTime (or ZonedDateTime) values, not LocalDateTime, unless your LocalDateTimeAdapter applies a time zone, e.g. the JVM default time zone.
I've found that the best way to help apply #Xml... annotations correctly, is to create objects and marshal them to XML to see what you get. Your current code will create this XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ParentClass>
<StringA>A</StringA>
<StringB>B</StringB>
<Items ts="2016-03-25T20:00:00+02:00">
<value>1.17</value>
</Items>
<Items ts="2016-03-25T21:00:00+02:00">
<value>1.15</value>
</Items>
</ParentClass>
If you apply the changes mentioned above, you get:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ParentClass>
<StringA>A</StringA>
<StringB>B</StringB>
<Items>
<Item ts="2016-03-25T20:00:00+02:00">1.17</Item>
<Item ts="2016-03-25T21:00:00+02:00">1.15</Item>
</Items>
</ParentClass>
The above outputs were created by adding this code:
class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
#Override
public String marshal(LocalDateTime time) throws Exception {
return time.atZone(ZoneOffset.ofHours(2))
.format(DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssxxx"));
}
#Override
public LocalDateTime unmarshal(String text) throws Exception {
return ZonedDateTime.parse(text).toLocalDateTime();
}
}
public static void main(String... args) throws Exception {
ParentClass p = new ParentClass("A", "B", Arrays.asList(
new Item(LocalDateTime.parse("2016-03-25T20:00:00"), 1.17),
new Item(LocalDateTime.parse("2016-03-25T21:00:00"), 1.15)));
JAXBContext jaxbContext = JAXBContext.newInstance(ParentClass.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(p, System.out);
}

Collection<?> wrap elements in plural name of actual type in JAXB

I have a Response class which contains some basic attributes and a wildcard Collection<?>.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
private String approved;
private String errorCode;
#XmlAnyElement(lax = true)
private Collection<?> collection;
public Response() {
}
public Response(String approved, Collection<?> collection) {
this.approved = approved;
this.collection = collection;
}
public String getApproved() {
return approved;
}
public String getErrorCode() {
return errorCode;
}
public Collection<?> getCollection() {
return collection;
}
}
This collection can contain many types, for example this type:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Transaction {
private BigDecimal amount;
private String transactionId;
public Transaction(BigDecimal amount, String transactionId ) {
super();
this.amount = amount;
this.transactionId = transactionId ;
}
public Transaction() {
super();
}
public BigDecimal getAmount() {
return amount;
}
public String getTransactionId() {
return transactionId;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
}
When serializing the Response class, I get this XML.
<?xml version="1.0" encoding="UTF-8"?>
<response>
<approved>00</approved>
<errorCode></errorCode>
<transaction>
<amount>500.00</amount>
<transactionId>pgka3902</transactionId>
</transaction>
<transaction>
<amount>201.05</amount>
<transactionId>abcd3020</transactionId>
</transaction>
</response>
Adding #XmlElementWrapper wraps <transaction> elements in <collection> which is not acceptable still. I need the wrapper to be named the plural of the actual type in collection. For example, the above xml should be:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<approved>00</approved>
<errorCode />
<transactions>
<transaction>
<amount>500.00</amount>
<transactionId>pgka3902</transactionId>
</transaction>
<transaction>
<amount>201.05</amount>
<transactionId>abcd3020</transactionId>
</transaction>
</transactions>
</response>
Is it possible to do this with JAXB? I'm using Eclipselink Moxy implementation.
Instead of Response holding a Collection you could change that to Object. Then you could have different classes for each of your collection types.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Transactions {
#XmlElement(name="transaction")
private List<Transaction> transactions;
}
The #XmlElementWrapper annotation has an optional parameter: name. If not specified, by default it will be the name of the Java field which is collection in your case. That's why your wrapper tag is named <collection>.
You can specify the name of the wrapper element/tag by passing the name argument to the #XmlElementWrapper annotation like this:
#XmlElementWrapper(name="transactions")
This will result in your desired XML tags.

Binding XML with JAXB annotations

I have the following XML format:
<repositories>
<set>
<id>1</id>
<name>First</name>
<spec>data</spec>
</set>
<set>
<id>2</id>
<name>INFO</name>
<spec>main</spec>
</set>
.
.
</repositories>
I create the following package-info.java
#javax.xml.bind.annotation.XmlSchema (
elementFormDefault=XmlNsForm.QUALIFIED,
xmlns = {}
)
package website.model;
import javax.xml.bind.annotation.XmlNsForm;
ANd the following classes:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Repositories {
#XmlElement
private ListofRepositories repositories;
public ListofRepositories getRepositories() {
return repositories;
}
public void setRepositories(ListofRepositories repositories) {
this.repositories = repositories;
}
}
Wrap the sets
#XmlAccessorType(XmlAccessType.FIELD)
public class ListofRepositories {
private List<Sets> set;
public List<Sets> getSet() {
return set;
}
public void setSet(List<Sets> set) {
this.set = set;
}
}
And the data:
#XmlAccessorType(XmlAccessType.FIELD)
public class Sets {
private Long id;
private String name;
private String spec;
//get set
}
I don't know why this doesn't work. The response is always null. I implemented similar processes with Java and JAXB annotations and i never had this kind of problem. Does anyone knows what is wrong and how can i fix it?
For the above xml, you need following class structure:
#XmlRootElement(name="repositories")
#XmlAccessorType(XmlAccessType.FIELD)
public class Repositories {
#XmlElement
private List<Sets> set;
//getter and setter
}
#XmlAccessorType(XmlAccessType.FIELD)
public class Sets {
private Long id;
private String name;
private String spec;
//getter and setter
}
But, according to your class structure you will get following xml:
<Repositories>
<repositories>
<set>
<id></id>
<name></name>
<spec></spec>
</set>
<set>
<id></id>
<name></name>
<spec></spec>
</set>
.
.
.
</repositories>
</Repositories>
with JAXB you could also generate these classes from an xsd file -- and check the incoming xml against the xsd (which will show why it will not accept it)

jaxb unmarshalling with other class as property

I have this two model.
AssetMetadata:
#XmlRootElement(name="AssetMetadata")
public class AssetMetadata {
private AssetMetadataType assetMetadataType;
private String id;
private String assetId;
....
AssetMetadataType:
#XmlRootElement(name = "AssetMetadataType")
public class AssetMetadataType {
private String id;
private String name;
....
I use the JaxB unmarshaller like this.
spring config:
<oxm:jaxb2-marshaller id="marshaller">
<oxm:class-to-be-bound name="ch.srf.esb.radioimporter.domain.AssetMetadata"/>
<oxm:class-to-be-bound name="ch.srf.esb.radioimporter.domain.AssetMetadataType"/>
</oxm:jaxb2-marshaller>
Java code:
#Autowired #Qualifier("marshaller") private Unmarshaller unmarshaller;
...
final InputStream is = new ByteArrayInputStream(xml.getBytes());
this.unmarshaller.unmarshal(new StreamSource(is));
Now when I send the following XML, the AssetMetadataType is not set:
<AssetMetadata>
<AssetMetadataType>
<id>1</id>
<name>EPG</name>
</AssetMetadataType>
<assetId>39b4864d-931b-40c6-85ad-c45251b97952</assetId>
<title>title</title>
<description>description</description>
</AssetMetadata>
What do I do wrong ?
#XmlRootElement should only be set on the root element. That's why it's called #XmlRootElement. It'll be ignored anywhere else.
Try removing #XmlRootElement from the AssetMetadataType class, and change the property in AssetMetadata to be:
#XmlElement(name="AssetMetadataType")
private AssetMetadataType assetMetadataType;

Categories

Resources