How to generate one objects from two similar xml documents with jaxb - java

I'm trying to integrate with some provider REST API within xml. I have two requests and two very similar responses: Operation response and Check response.
Operation:
<Response>
<ReturnCode>0</ReturnCode>
<ReturnMessage>OK</ReturnMessage>
<Commands>
<OperationResponseCommand>
<ResultCode>412</ResultCode>
<ResultMessage>Some message hear</ResultMessage>
<OperationId>125206188472552900</OperationId>
<Id>14507921</Id>
</OperationResponseCommand>
</Commands>
</Response>
Check:
<Response>
<ReturnCode>0</ReturnCode>
<ReturnMessage>OK</ReturnMessage>
<Commands>
<CheckResponseCommand>
<ResultCode>412</ResultCode>
<ResultMessage>Some message hear</ResultMessage>
<OperationId>125206188472552900</OperationId>
</CheckResponseCommand>
</Commands>
</Response>
As you can see, the differences are:
<OperationResponseCommand> instead of <CheckResponseCommand>
in first xml.
One additional tag <Id> inside
<OperationResponseCommand>.
And I have two classes for Response and for Command.
First:
#Data
#XmlRootElement(name = "Response")
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
#XmlElement(name = "ReturnCode")
private Integer returnCode;
#XmlElement(name = "ReturnMessage")
private String returnMessage;
#XmlElementWrapper(name = "Commands")
#XmlElement(name = "OperationResponseCommand")
private List<Command> commands = new ArrayList<>();
}
Second:
#Data
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Command {
#XmlElement(name = "ResultCode")
private Integer resultCode;
#XmlElement(name = "ResultMessage")
private String resultMessage;
#XmlElement(name = "OperationId")
private String operationId;
#XmlElement(name = "Id")
private Integer id;
}
For building response object from xml I'm using this class:
public class JAXBOperations {
public <T> T buildObjectFromXml(Class<T> clazz, String xml) {
try {
StringReader reader = new StringReader(xml);
Unmarshaller unmarshaller = createUnmarshaller(clazz);
return (T) unmarshaller.unmarshal(reader);
} catch (JAXBException e) {
throw new RuntimeException("Exception has been occurred while creating unmarshaller to parse xml to object");
}
}
public <T> String buildXmlFromObject(T objectToXml) {
try {
StringWriter writer = new StringWriter();
Marshaller marshaller = createMarshaller(objectToXml.getClass());
marshaller.marshal(objectToXml, writer);
return writer.toString();
} catch (JAXBException e) {
throw new RuntimeException("Exception has been occurred while creating marshaller to parse object to xml");
}
}
private <T> Marshaller createMarshaller(Class<T> clazz) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
return jaxbContext.createMarshaller();
}
private <T> Unmarshaller createUnmarshaller(Class<T> clazz) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
return jaxbContext.createUnmarshaller();
}
}
Method buildObjectFromXml. And it works fine for first xml response. I have an object like this:
Response(
returnCode=0, returnMessage=OK,
commands=[Command(
resultCode=417,
resultMessage=Processing,
operationId=b6619f26-8583-4272-b89d-d1b200109d06,
id=157427079)])
But for the second xml response it doesn't works. I have:
Response(
returnCode=0,
returnMessage=OK,
commands=[])
I tried to change the code of my Response class. Like this:
#Data
#XmlRootElement(name = "Response")
#XmlAccessorType(XmlAccessType.FIELD)
public class tResponse {
#XmlElement(name = "ReturnCode")
private Integer returnCode;
#XmlElement(name = "ReturnMessage")
private String returnMessage;
#XmlElementWrapper(name = "Commands")
#XmlElementRefs({
#XmlElementRef(name = "OperationResponseCommand", type = Command.class),
#XmlElementRef(name = "CheckResponseCommand", type = Command.class)})
private List<Command> commands = new ArrayList<>();
}
Or this:
#Data
#XmlRootElement(name = "Response")
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
#XmlElement(name = "ReturnCode")
private Integer returnCode;
#XmlElement(name = "ReturnMessage")
private String returnMessage;
#XmlElementWrapper(name = "Commands")
#XmlElement(name = "OperationResponseCommand")
private List<Command> operstionCommands = new ArrayList<>();
#XmlElementWrapper(name = "Commands")
#XmlElement(name = "CheckResponseCommand")
private List<Command> checkCommands = new ArrayList<>()
}
But it both doesn't works too.
My question is: what I'm doing wrong? How can I change code of my two classes to correct converting to object both types of xml responses?

For representing the XML elements <OperationResponseCommand>
and <CheckResponseCommand> you will need separate classes
which should be subclasses of your Command classes.
Let's call them OperationResponseCommand and CheckResponseCommand.
The properties common to all commands should remain in your
Command class. It is also a good idea to declare this class as abstract.
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class Command {
#XmlElement(name = "ResultCode")
private Integer resultCode;
#XmlElement(name = "ResultMessage")
private String resultMessage;
#XmlElement(name = "OperationId")
private Integer operationId;
}
The properties specific for the commands should go into the subclasses:
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class OperationResponseCommand extends Command {
#XmlElement(name = "Id")
private Integer id;
}
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class CheckResponseCommand extends Command {
}
Finally, in your Response class you need to enhance the annotations
of your List<Command> commands property, so that JAXB
will know which object class needs to be instantiated depending
on the XML element name of the commands.
(See also the javadoc of #XmlElements.)
#XmlElementWrapper(name = "Commands")
#XmlElements({
#XmlElement(name = "OperationResponseCommand", type = OperationResponseCommand.class),
#XmlElement(name = "CheckResponseCommand", type = CheckResponseCommand.class)
})
private List<Command> commands = new ArrayList<>();

Related

Why return null my method with JAXB unmarshalling convert sting xml to object?

I am trying to convert my xml string to objects. I have the following code, but when I do get to a field it returns null.
#Override
public DatosInputDto convertXmlToDatosInputDto (String xml) {
DatosInputDto input = null;
try {
JAXBContext jaxbContext = JAXBContext.newInstance(DatosInputDto.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(xml);
input = (DatosInputDto) unmarshaller.unmarshal(reader);
} catch (JAXBException ex) {
LOGGER.error(ex.getMessage(), ex);
}
return input;
}
This is the xml of string:
<?xml version="1.0" encoding="UTF-8"?>
<nd0:OU_SchemaPublic xmlns:nd0="http://www.prueba/cadena/ejemplo/1.00">
<nd0:TTT_Code>LIM28</nd0:TTT_Code>
<nd0:TTT_Date>20210622</nd0:TTT_Date>
<nd0:TTT_CodeType>0203</nd0:TTT_CodeType>
<nd0:TTT_Receiver>0098</nd0:TTT_Receiver>
<nd0:TTT_SenderPT>0092</nd0:TTT_SenderPT>
<nd0:TTT_DocumentFile>Documents</nd0:TTT_DocumentFile>
</nd0:OU_SchemaPublic>
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {"code", "date", "codeType", "receiver", "senderPt","documentFile"})
#XmlRootElement(name = "OU_SchemaPublic")
public class DatosInputDto implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4596758570871609534L;
private String code;
private String date;
private String codeType;
private String receiver
private String senderPt;
private String documentFile;
}
package.info
#javax.xml.bind.annotation.XmlSchema(
xmlns = {#javax.xml.bind.annotation.XmlNs(prefix = "nd0",
namespaceURI = "http://www.prueba/cadena/ejemplo/1.00")},
namespace = "http://www.prueba/cadena/ejemplo/1.00",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.prueba;
What is my mistake?
What do I need to add or remove?
Your XML attributes name different from Pojo class. Add below annotations on fields.
#XmlAttribute
private String code
If not work then add the key name as below.
#XmlAttribute(name="nd0:TTT_Code")
private String code

List coming back as null after unmarshalling into a POJO using JAXB

I am trying to unmarshall an XML into Java POJO using JAXB, all elements are getting unmarshalled fine except for the lists(listId1 and listId2), following is the xml, the pojo classes and the business class.
<?xml version="1.0" encoding="UTF-8"?>
<abacus-1.0-snapshot xmlns="urn:xxxxxxx">
<rules>
<rule>
<name>rule1</name>
<errorType>WARNING</errorType>
<flag>true</flag>
<startDate>2020-05-19</startDate>
<listId1>
<id>100101</id>
<id>100102</id>
</listId1>
<listId2>
<id>100103</id>
<id>100104</id>
</listId2>
</rule>
<rule>
<name>rule2</name>
<errorType>ERROR</errorType>
<flag>false</flag>
<startDate>2020-05-20</startDate>
<listId1>
<id>100105</id>
<id>100106</id>
</listId1>
<listId2>
<id>100107</id>
<id>100107</id>
</listId2>
</rule>
</rules>
</abacus-1.0-snapshot>
The Rules.java which maps the <rules> element to Rules.java
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement(name = "rules", namespace = "urn:xxxxxxx")
public class RulesPOJO
{
#XmlElement(name = "rule", namespace = "urn:xxxxxxx")
private final List<rulePOJO> rulesPOJO = new ArrayList<rulePOJO>();
public List<rulePOJO> getRulesPOJO()
{
return rulesPOJO;
}
}
The Rule.java class which is used to map the <rule> element to Rule.java
#XmlAccessorType(XmlAccessType.NONE)
public class RulePOJO
{
#XmlElement(namespace = "urx:xxxxx")
private final String name = null;
#XmlElement(namespace = "urx:xxxxx")
private final String errorType = null;
#XmlElement(namespace = "urx:xxxxx")
private final Date startDate = null;
#XmlElement(name = "listId1", namespace = "urx:xxxxx")
private final List<Long> listId1 = new ArrayList<>();
#XmlElement(name = "listId2", namespace = "urx:xxxxx")
private final List<Long> listId2 = new ArrayList<>();
public String getName()
{
return name;
}
public errorType getErrorType()
{
return errorType;
}
public Boolean getFlag()
{
return flag;
}
public Date getStartDate()
{
return startDate;
}
public List<Long> getListId1()
{
return listId1;
}
public List<Long> getListId2()
{
return listId2;
}
}
the business class used for unmarshalling xml into the pojo
public class Retriever() throws JAXBException
{
Document document = Configuration.load(URI.create("urn:xxxxxx"));
Unmarshaller unmarshaller = JAXBContext.newInstance(RulesPOJO.class).createUnmarshaller();
RulesPOJO rulesPOJO = (RulesPOJO) unmarshaller
.unmarshal(document.getElementsByTagName("Rules").item(0));
for(RulePOJO rulePOJO : RulesPOJO.getRulesPOJO())
{
// the following are coming back null
List<Long> listId1 = rulePOJO.getListId1();
List<Long> listId2 = rulePOJO.getListId2();
}
}
Thanks!
As discussed in the comments, id is the repeating element to be treated as arraylist so as mentioned in the given link https://howtodoinjava.com/jaxb/xmlelementwrapper-annotation/. You can use XMLElementWrapper and XMLElement types of annotations to denote wrapper and child elements.

Unmarshal element attribute and text to separate fields with jaxb

How do I annotate the externalValue and companyId fields in the Root class so that "abc" gets mapped to externalValue and "123" gets mapped to companyId?
Do I need the #XmlJavaTypeAdapter annotation? Where? I'm hoping that if I do, it can just handle those 2 fields and I can leave the annotations for title and countryCodes as-is.
XML:
<item>
<externalValue companyId="123">abc</externalValue>
<title>My Title</title>
<country>US</country>
<country>CA</country>
</item>
POJO:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
private String externalValue;
private String companyId;
#XmlElement
private String title;
#XmlElement(name = "country")
public List<String> countryCodes;
// getters and setters...
}
I am afraid that this is not possible to achieve only with annotations (so without extra POJO and some adapter) in general case namely JAXB specs. However if your happen to use MOXy as your JAXB implementation it is easy as adding annotation #XmlPath like this:
#XmlPath("externalValue/#companyId")
private String companyId;
Related question: Unmarshalling an XML using Xpath expression and jaxb
You have to define the class in the following manner.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
private CompanyIdValue companyIdValue;
#XmlElement
private String title;
#XmlElement(name = "country")
public List<String> countryCodes;
//getter and setter
}
In case of both attribute in an XML element tag, you have to define a separate class. Define a separate class called CompanyIdValue, for XML element, you have to define #XmlValue and for attribute you have to annotate with #XmlAttribute
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
public class CompanyIdValue {
#XmlElement(name = "externalValue")
private String externalValue;
private String companyId;
public String getExternalValue() {
return externalValue;
}
#XmlValue
public void setExternalValue(String externalValue) {
this.externalValue = externalValue;
}
public String getCompanyId() {
return companyId;
}
#XmlAttribute
public void setCompanyId(String companyId) {
this.companyId = companyId;
}
}
I provide below a test program also for testing.
public class Test {
public static void main(String[] args) {
try {
Item item = new Item();
CompanyIdValue companyIdValue = new CompanyIdValue();
companyIdValue.setCompanyId("SomeId");
companyIdValue.setExternalValue("Some External value");
item.setCompanyIdValue(companyIdValue);
item.setCountryCodes(Arrays.asList("A", "B"));
item.setTitle("Some Title");
JAXBContext jaxbContext = JAXBContext.newInstance(Item.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.marshal(item, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}

Use JAXB to create Object from XML String with Multiple Groups in XML file

I need to convert XML string into java object.
This is the XML File
<?xml version="1.0" encoding="UTF-8"?>
<DATA_DS>
<G_1>
<TERM_ID>4</TERM_ID><NAME>30 Net</NAME>
</G_1>
</DATA_DS>
I have created Class like this;
#XmlRootElement(name = "DATA_DS")
#XmlAccessorType(XmlAccessType.FIELD)
public class PaymentTerm {
#XmlElement(name = "TERM_ID")
private double termId;
#XmlElement(name = "NAME")
private String termName;
public double getTermId() {
return termId;
}
public void setTermId(double termId) {
this.termId = termId;
}
public String getTermName() {
return termName;
}
public void setTermName(String termName) {
this.termName = termName;
}
}
In Main Class
jaxbContext = JAXBContext.newInstance(PaymentTerm.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(xmlString);
PaymentTerm paymentTerm = (PaymentTerm) unmarshaller.unmarshal(reader);
This doesn't unmarshell the XML string properly because of nested groups in XML file.
If I remove the G_1 group from XML file then it convert perfectly. I need to do conversion with G_1 group
Where I have to fix the code?
<DATA_DS> contains one element, <G_1>, which itself contains two elements, <TERM_ID> and <NAME>, so your objects needs to reflect that, i.e. the class representing <DATA_DS> must have one field, typed to be a class representing <G_1>, which must have two fields.
Where I have to fix the code?
You need to create a class for <G_1>:
#XmlRootElement(name = "DATA_DS")
#XmlAccessorType(XmlAccessType.FIELD)
public class PaymentTerm {
#XmlElement(name = "G_1", required = true)
private PaymentGroup group;
}
#XmlAccessorType(XmlAccessType.FIELD)
public class PaymentGroup {
#XmlElement(name = "TERM_ID", required = true)
private double termId;
#XmlElement(name = "NAME", required = true)
private String termName;
}
You should also consider why <G_1> exists, e.g. can there be more than one <G_1> inside <DATA_DS>? If so, make it a list:
#XmlElement(name = "G_1", required = true)
private List<PaymentGroup> groups;

Unable to marshal type as XML element because #XmlRootElement annotation is missing

I want to marshal object to XML.
However, it fails with exception:
javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.SAXException2: unable to marshal type "FreightOfferDetail" as an element because it is missing an #XmlRootElement annotation]
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:331)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:257)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:96)
at com.wktransportservices.fx.test.util.jaxb.xmltransformer.ObjectTransformer.toXML(ObjectTransformer.java:27)
at com.wktransportservices.fx.test.sampler.webservice.connect.FreightOfferToConnectFreight.runTest(FreightOfferToConnectFreight.java:59)
at org.apache.jmeter.protocol.java.sampler.JavaSampler.sample(JavaSampler.java:191)
at org.apache.jmeter.threads.JMeterThread.process_sampler(JMeterThread.java:429)
at org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:257)
at java.lang.Thread.run(Thread.java:662)
Caused by: com.sun.istack.SAXException2: unable to marshal type "FreightOfferDetail" as an element because it is missing an #XmlRootElement annotation
at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:244)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:303)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:490)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:328)
In fact, this annotation is present (for parent and delivered class):
#XmlRootElement(name = "Freight_Offer")
#XmlAccessorType(XmlAccessType.FIELD)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class FreightOffer {
#JsonIgnore
#XmlTransient
private String freightId;
private String id;
private String externalSystemId;
private AddressLocation pickUp;
private AddressLocation delivery;
private FreightDescription freightDescription;
private ListContacts contacts;
private Customer customer;
private ListSla slas;
private String pushId;
private CompanyProfile company;
private Route route;
private String href;
private Lifecycle lifecycle;
private Visibility visibility;
private Boolean unfoldedVXMatching;
// getters / setters
Child class:
#XmlAccessorType(XmlAccessType.PROPERTY)
public class FreightOfferDetail extends FreightOffer {
private List<Contact> contact;
#XmlElement(name = "contacts")
#JsonProperty("contacts")
public List<Contact> getContact() {
return contact;
}
public void setContact(List<Contact> contact) {
this.contact = contact;
}
It fails exactly at this method toXML():
public class ObjectTransformer<T> implements Transformer<T> {
protected final JAXBContext context;
protected final Marshaller marshaller;
protected final int okStatusCode = 200;
protected final String okSubErrorCode = "OK";
public ObjectTransformer(JAXBContext context) throws JAXBException {
this.context = context;
marshaller = context.createMarshaller();
marshaller.setProperty("jaxb.encoding", "UTF-8");
marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE);
}
public String toXML(T object) throws JAXBException {
StringWriter writer = new StringWriter();
marshaller.marshal(object, writer);
String xmlOffer = writer.toString();
return xmlOffer;
}
It should work, but it shouldn't.
I couldn't find what is missed or wrong here.
UPDATE:
Here is snippet from test:
public SampleResult runTest(JavaSamplerContext context) {
AbstractSamplerResults results = new XMLSamplerResults(new SampleResult());
results.startAndPauseSampler();
if (failureCause != null) {
results.setExceptionFailure("FAILED TO INSTANTIATE connectTransformer", failureCause);
} else {
FreightOfferDTO offer = null;
FreightOffer freightOffer = null;
try {
results.resumeSampler();
RouteInfo routeDTO = SamplerUtils.getRandomRouteFromRepo(context.getIntParameter(ROUTES_TOUSE_KEY));
offer = FreightProvider.createRandomFreight(routeDTO, createUserWithLoginOnly(context));
freightOffer = connectTransformer.fromDTO(offer);
String xmlOfferString = connectTransformer.toXML(freightOffer); // <- it fails here.
I take the date from CSV file and converting to DTO object. This method returns to me
FreightOfferDetail.
Here is snippet from this method:
public FreightOfferDetail freightFromDTO(FreightOfferDTO freightDTO, boolean fullFormat){
FreightOfferDetail freight = new FreightOfferDetail();
freight.setFreightId(freightDTO.getIds().getAtosId());
freight.setId(freightDTO.getIds().getFxId());
// ...
How to marshal object to XML file, at this case?
public String toXML(T object) throws JAXBException {
StringWriter stringWriter = new StringWriter();
JAXBContext jaxbContext = JAXBContext.newInstance(T.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
// format the XML output
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
QName qName = new QName("com.yourModel.t", "object");
JAXBElement<T> root = new JAXBElement<Bbb>(qName, T.class, object);
jaxbMarshaller.marshal(root, stringWriter);
String result = stringWriter.toString();
LOGGER.info(result);
return result;
}
Here is the article I use when I have to marshal/unmarshal without #XmlRootElement: http://www.source4code.info/2013/07/jaxb-marshal-unmarshal-with-missing.html
All the best hope it helps :)
Note that the error message talks about FreightOfferDetail, not FreightOffer.
Based on that, I would expect that somewhere (outside the provided code), you're asking a Detail to be marshalled.
If you want to be able to do that (with JAXB?) you need to annotate that class as well with the XmlRootElement annotation.
You can use the ObjectFactory class to workaround for the classes which doesn't have the #XmlRootElement. ObjectFactory has overloaded methods.
Method:1 It does simple creation of the object and
Method:2 It will wrap the object with #JAXBElement.
Always to use Method:2 to avoid javax.xml.bind.MarshalException - with linked exception missing an #XmlRootElement annotation
Method:1
public GetCountry createGetCountry() {
return new GetCountry();
}
Method:2
#XmlElementDecl(namespace = "my/name/space", name = "getCountry")
public JAXBElement<GetCountry> createGetCountry(GetCountry value) {
return new JAXBElement<GetCountry>(_GetCountry_QNAME, GetCountry.class, null, value);
}
for more information follow this stackoverflow-question
Hope this will be helpful...
When creating the Java-Objects from the WSDL with the apache-cxf maven-plugin, using sth. like:
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
...
</plugin>
There will be a automatically generated ObjectFactory.
When configuring your Exception in Java you can use this generated ObjectFactory for returning the fault. The important step is to return a JAXBElement<YourSoapFault> in the getFaultInfo() method of your exception.
#WebFault(name = "YourSoapFault",
targetNamespace = "ns://somenamespace")
public class SomeWebServiceException extends RuntimeException {
private final YourSoapFault fault;
public SomeWebServiceException(String message, YourSoapFault fault) {
super(message);
this.fault = fault;
}
public SomeWebServiceException(String message, YourSoapFault fault, Throwable e) {
super(message, e);
this.fault = fault;
}
public JAXBElement<YourSoapFault> getFaultInfo() {
return new ObjectFactory().createYourSoapFault(fault);
}
}

Categories