How do I remove an XML layer using Jaxb - jaxb

I have an XML file that looks like this
<coordinate>
<url>http://some-url</url>
<properties>
<property type="integer">key1=12345</property>
<property visibility="private" type="string">key2=value2</property>
</properties>
</coordinate>
I would like that to generate an Object structure that looks like this:
class Coordinate
string url
List<Property> properties
class Property
string type
string visibility
Jaxb seems to require a "Properties" type layer in between the coordinate element and the property elements. Is there any way to just get it to add the properties directly to the list contained in the Coordinate object?

JAXB #XmlElementWrapper annotation can be used for this scenario as below.
#XmlElementWrapper(name = "properties")
#XmlElement(name="property")
protected List<Property> propertyList = new ArrayList<Property>();

Related

Simple XSLT transformation into ABAP Object

I'm again stuck with a transformation from XML into ABAP. This time, I want to put the XML data directly into an ABAP Object.
My XML looks like this:
<qualityStatus>
<address>0</address>
<bounceRisk>0</bounceRisk>
<checked>1</checked>
<domain>1</domain>
<domainScores>
<domainScore>
<domain>gmx.de</domain>
<score>0.8333333134651184</score>
</domainScore>
<domainScore>
<domain>ggs.de</domain>
<score>0.6666666269302368</score>
</domainScore>
<domainScore>
<domain>xyz.de</domain>
<score>0.6666666269302368</score>
</domainScore>
</domainScores>
<extSyntax>1</extSyntax>
<mailserver>1</mailserver>
<mailserverDiagnosis>1</mailserverDiagnosis>
<probability>1</probability>
<syntax>1</syntax>
</qualityStatus>
Edit: I changed back to a XSLT transformation, shortened to one attribute it looks like this:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sap="http://www.sap.com/sapxsl" version="1.0">
<xsl:output encoding="iso-8859-1" indent="yes" method="xml" version="1.0"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/qualityStatus">
<asx:abap version="1.0" xmlns:asx="http://www.sap.com/abapxml">
<asx:values>
<ROOT href="#o26"/>
</asx:values>
<asx:heap xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:abap="http://www.sap.com/abapxml/types/built-in"
xmlns:cls="http://www.sap.com/abapxml/classes/global"
xmlns:dic="http://www.sap.com/abapxml/types/dictionary">
<cls:ZCL_ADDRESS_QUALITY id="o26" >
<local.ZCL_ADDRESS_QUALITY>
<W_ADDRESS>
<xsl:value-of select="address"/>
</W_ADDRESS>
<!--More attributes here-->
</local.ZCL_ADDRESS_QUALITY>
</cls:ZCL_ADDRESS_QUALITY>
</asx:heap>
</asx:abap>
</xsl:template>
My object attributes are all public right now, because I thought this could be the problem. However, setter and getter do exist. Yes, my class does implement the interface if_serializable_object.
DATA:
w_address TYPE char1,
w_bouncerisk TYPE char1,
w_checked TYPE char1,
w_decoded TYPE stringval,
w_domain TYPE char1,
w_domainscores TYPE z_domainscore_t, "Table type for name + score
w_extsyntax TYPE char1,
w_mailserver TYPE char1,
w_mailserverdiagnosis TYPE char1,
w_probability TYPE char1,
w_syntax TYPE char1,
w_syntaxwarnings TYPE z_syntaxwarnings_t. "Table of syntaxwarnings
Finally, I call my transformation with an instance of my class:
CALL TRANSFORMATION zst_addressquality
SOURCE XML lw_xml
RESULT result = lo_addressquality.
Now, when debugging through the transformation code, it successfully notices all fields of the given lw_xml and appears to write them into the object lo_addressquality. But the object attributes stay empty afterwards.
When testing the serialization, I can access result which contains my object, but result-w_address (and all others) are empty.
While testing, I created a structure with completely identical names and types. With it, it worked as intended.
What am I missing? Is there anything else I have to watch out for when working with transformation into ABAP Objects?
_Edit: After changing to the XSLT, I can get until W_ADDRESS before my code throws an CX_XSLT_ABAP_CALL_ERROR. So, I'm still not able to access the object'S attributes properly. :|_
Objects can be serialized/deserialized only with an XSL transformation. It's not possible to do it with a simple transformation, dixit ABAP documentation:
ST programs are restricted to the transformation of elementary and structured ABAP data, along with internal tables. The transformation of reference variables and referenced objects is not currently supported.
The XSL transformation must convert the XML into ASXML, which in short corresponds to a structure like this:
<?xml ...?>
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
...
</asx:values>
<asx:heap>
...
</asx:heap>
</asx:abap>
The easiest way to understand what the ASXML should look like is to serialize your object reference using the identity transformation (it's an XSL transformation), and then adapt your transformation to produce the same kind of asXML:
CALL TRANSFORMATION id SOURCE anyRootName = yourObjectReference RESULT XML asXMLutf8xstring.
Example:
REPORT.
CLASS serialization_demo DEFINITION.
PUBLIC SECTION.
INTERFACES if_serializable_object.
DATA attribute TYPE i.
ENDCLASS.
START-OF-SELECTION.
DATA obj_ref TYPE REF TO serialization_demo.
DATA xstring TYPE xstring.
CREATE OBJECT obj_ref.
obj_ref->attribute = 5.
CALL TRANSFORMATION id " serialize
SOURCE root = obj_ref
RESULT XML xstring.
CLEAR obj_ref.
CALL TRANSFORMATION id " deserialize
SOURCE XML xstring
RESULT root = obj_ref.
ASXML (in the xstring variable):
<?xml version="1.0" encoding="utf-8"?>
<asx:abap version="1.0" xmlns:asx="http://www.sap.com/abapxml">
<asx:values>
<ROOT href="#o3"/>
</asx:values>
<asx:heap xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:abap="http://www.sap.com/abapxml/types/built-in" xmlns:cls="http://www.sap.com/abapxml/classes/global" xmlns:dic="http://www.sap.com/abapxml/types/dictionary">
<prg:SERIALIZATION_DEMO id="o3" xmlns:prg="http://www.sap.com/abapxml/classes/program/ZZSRO_TEST16I">
<local.SERIALIZATION_DEMO>
<ATTRIBUTE>5</ATTRIBUTE>
</local.SERIALIZATION_DEMO>
</prg:SERIALIZATION_DEMO>
</asx:heap>
</asx:abap>

jxpath not honoring JAXB's XmlElement name of xjc-generated classes

I use an XML Schema Definition and JAXB to generate Java classes with proper #XmlElement or #XmlRootElement annotations.
Since the schema has some deep nesting in it (not my choice), I'd rather use jxpath to access deeply buried classes using an XPath (rather than cumbersome daisy-chain of .getThat() and that != null).
The problem is that some of the XML element names contain dashes, e.g. foo-bar. When I try to access elements using org.apache.jxpath, I need to rewrite my XPath so that such names are camel-cased instead (fooBar) like the name of the actual Java objects. Is there any way to tell jxpath to find the elements using the XPath corresponding to the XML element names (instead of the camel-cased Bean names)?
I think it is related to this question, however in my case I don't actually case what kind of tricks and decorations are used on the auto-generated classes, as long as xjc can do it.
Here is a simple example to illustrate the issue.
First, a small XSD file:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="record">
<xs:complexType>
<xs:sequence>
<xs:element name="foo-bar" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Compile it...
xjc -p org.pd.jx example.xsd
Here is the xjc-generated Record.java class (minus comments):
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {"fooBar"})
#XmlRootElement(name = "record")
public class Record {
#XmlElement(name = "foo-bar", required = true)
protected String fooBar;
public String getFooBar() {return fooBar;}
public void setFooBar(String value) {this.fooBar = value;}
}
Then, trying to access the data via jxpath (in practice I have to deal with lots of deeply nested classes), one can see below that the correct XPath ("foo-bar") doesn't work, but a camel-cased version does.
Record record = new Record();
record.setFooBar("hello world");
JXPathContext context = JXPathContext.newContext(record);
context.setLenient(true);
String a = (String)context.getValue("foo-bar", String.class); // is null
String b = (String)context.getValue("fooBar", String.class); // "hello world"
I believe JXPath operates on an objects properties and not the XML elements they correspond to. I do not believe that JXPath parses any of the JAXB metadata so it wouldn't know the XML nodea you have mapped. What you are seeing appears to be the expected behaviour.

JAXB -field based on two sibling XML elements

for example i have this xml:
<xml>
<a>1</a>
<b>2</b>
</xml>
and this class:
#XmlRootElement(name = "xml")
public class xml{
int aPlusb;
....
}
i know how to create an XmlAdapter but i would like it to set the filed aPlusb to be the plus between the value of element a and the value of filed b.
is there a way to do this in JAXB without making an XmlTransient field and calculate it separately?

Determine the order of xml mixed content in derived class

I have the schema like this :
<xsd:complexType name="ContentType" mixed="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
The content type is a broad base type allowing any content.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="BaseContentType">
<xsd:sequence>
<xsd:any minOccurs="0" maxOccurs="unbounded" namespace="##any" processContents="lax"
/>
</xsd:sequence>
<xsd:attribute name="orientation" type="OrientationEnum" use="optional"
default="portrait">
<xsd:annotation>
<xsd:documentation><![CDATA[
The #orientation attribute is used to specify a "landscape"
orientation for the published form. This is primarily used
for schedules or for tables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
I use xjc command line tool to generate the Java classes from the above schema and the classes are generated as follows:
public abstract class BaseContentType {
#XmlMixed
protected List<Serializable> content;
#XmlAttribute(name = "id")
#XmlJavaTypeAdapter(CollapsedStringAdapter.class)
#XmlID
#XmlSchemaType(name = "ID")
protected String id;
and
public class ContentType
extends BaseContentType
{
#XmlMixed
#XmlAnyElement(lax = true)
#OverrideAnnotationOf
protected List<Object> contentOverrideForContentType;
When I unmarshall, all the nested xml elements get populated in the List contentOverrideForContentType of the ContentType object and all the text elements get populated in the List content of BaseContentType.
How do I determine the order of the text elements with respect to the nested elements and construct the entire text?
I am trying to get the entire text within the ContentType for which I have to look at top level text and the text of all the nested tags and combine them all(here is where I need to know the order). Is there a better way to just extract all the text from ContentType?
Thanks!
EDIT
This is related to this question.
If you declare a propOrder on the specific jaxB class you can specify the order that objects will appear in the xml
e.g.
#XmlType(propOrder = {
"firstObjectName",
"fsecondObjectName"
})
note: when using a propOrder all elements in the object must be added to the propOrder.
I had the same issue and I found out that the elements will be ordered following the order in which the elements had been added to the list ... this is assuming you are using an ordered list (List, ArrayList...)

How can I make #XmlAttribute in a special order by using JAXB?

I have XML file which needs 3 attributes in an element. How can make the order of street, zip and city attribute as I wanted?
<address street="Big Street" zip="2012" city="Austin">
</address>
#XmlType(name="Street)
#XmlRootElement(name = "Street")
public class Street {
#XmlAttribute
private String name;
#XmlAttribute
private String type;
... set and get method
}
Anecdotally, the attributes seem to be in reverse order than they are mentioned in code. In my case, I'm using two variables (name & value) and I had to declare them as:
// The inverse order of name & value seems to make them render in XML in name/value order
#XmlAttribute
protected String value;
#XmlAttribute
protected String name;
When the XML is generated, it results in the following:
<attribute name="nameValue" value="valueValue"/>
You can use #XmlAccessorOrder(has predefined values) or #XmlType(Only works for properties) to govern the ordering.
Samples
Edit :
For custom ordering JAXB specification doesnt provide anything, but you can do if your JAXB provider provides you some features.
Found this link where it speaks about ordering using EclipseLink JAXB.

Resources