I'm attempting to build a Jersey web service which will take in data, format it into an XML document, then pass it to another service. I realize that Jersey does have XML support, but I'm having a bit of trouble implementing it due to the required XML structure for the project. The desired output looks something like this:
<root-element>
<table>
<row>
<d>data1</d>
<d>data2</d>
<d>data3</d>
</row>
<row>
<d>data4</d>
<d>data5</d>
<d>data6</d>
</row>
</table>
My issue arises in that there are a variable number of <d> and <row> elements, which will be determined based on the data passed in. I know that I can format a simple table with #XmlRootElement above the class which handles the data, but this may only be useful for my <root-element> since the element only gets populated with other elements. I know I'll need to use some sort of loop to create each <row>, but I'm not sure how I can create each <d> element with different data in each field. Any suggestions?
You can use a Java model with JAXB (JSR-222) annotations to support your use case. Elements that can occur more than once will correspond to List properties in your Java model. Below is an example of how your document could be mapped.
Table
We will use the #XmlElementWrapper annotation to add a grouping element, and the #XmlElement annotation to set the element name for the items in the collection.
package forum11543081;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="root-element")
#XmlAccessorType(XmlAccessType.FIELD)
public class Table {
#XmlElementWrapper(name="table")
#XmlElement(name="row")
private List<Row> rows;
}
Row
If the name of your property/field matches the name of the resulting XML element then you do not require any annotations.
package forum11543081;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Row {
private List<String> d;
}
Demo
Below is a standalone example to prove that the mapping works:
package forum11543081;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Table.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum11543081/input.xml");
Table table = (Table) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(table, System.out);
}
}
input.xml/Output
<root-element>
<table>
<row>
<d>data1</d>
<d>data2</d>
<d>data3</d>
</row>
<row>
<d>data4</d>
<d>data5</d>
<d>data6</d>
</row>
</table>
</root-element>
For More Information
http://blog.bdoughan.com/2010/09/jaxb-collection-properties.html
http://blog.bdoughan.com/2010/08/creating-restful-web-service-part-15.html
If you want to use the default Jersey/JAXB marshalling into XML, you would build a schema reflecting the structure you have indicated which includes collections (unbounded elements) and generate (using xjc) the corresponding java classes. The response from your restful service would be the type associated with the root element and you would build the structure as part of the service. The unbounded elements are rendered as java lists so they can be of arbitrary number of elements. In the code you would just .add(element) as necessary. Something like:
<schema ...>
...
<element name="root-element">
<complexType>
<sequence>
<element name="table" type="tns:TableType" />
</sequence>
</complexType>
</element>
<complexType name="TableType">
<sequence>
<element name="row" minOccurs="0" maxOccurs="unbounded" type="tns:RowType" />
</sequence>
</complexType>
<complexType name="RowType">
<sequence>
<element name="d" minOccurs="0" maxOccurs="unbounded" type="string" />
</sequence>
</complexType>
</schema>
The alternate approach would be (as mentioned by TedTrippin) using stax (streaming processor) to build up the xml document tag by tag with loops in appropriate places and returning the final result.
What I've ended up doing:
Since I had code from another project I could re-use for looping through the XML building, I decided to build the XML in a document, then write that document to a string like so:
public class XmlHandler{
public static String buildXml(){
String xmlString="";
//Create XML Document
DocumentBuilderFactory docfac = DocumentBuilderFactory.newInstance();
DocumentBuilder docbuil = null;
docbuil = docfac.newDocumentBuilder();
Document doc = docbuil.newDocument();
//Build XML Elements
Element root = doc.createElement("root-element");
doc.appendChild(root);
Element table = doc.createElement("table");
root.appendChild(table);
//Hard coded data here for testing purposes.
String[][]array={
{"data1", "data2", "data3"},
{"data4", "data5", "data6"}
};
Text text = null;
Element d = null;
Element row = null;
for(String[] line : array)
{
row=doc.createElement("row");
table.appendChild(row);
for(String label : line)
{
d = doc.createElement("d");
row.appendChild(d);
text = doc.createTextNode(label);
d.appendChild(text);
}
}
}
//Write Document to String
DOMImplementationLS domImplLS = (DOMImplementationLS) doc.getImplementation();
LSSerializer serializer = domImplLS.createLSSerializer();
serializer.getDomConfig().setParameter("format-pretty-print", true);
LSOutput output = domImplLS.createLSOutput();
output.setEncoding("UTF-8");
StringWriter sw = new StringWriter();
output.setCharacterStream(sw);
serializer.write(doc, output);
xmlString = sw.toString();
return xmlString;
}
}
While the hard coded String array won't be around for long, just until I find out what data types I need to pass in, this class is doing the trick just fine.
Related
I've got an xml file that looks like this
<console-menu-entry index="2" text="Print Hello World">
<console-menu-entry index="1" text="Print Hello">
print 'Hello'
</console-menu-entry>
<console-menu-entry index="2" text="Print World">
print 'World'
</console-menu-entry>
</console-menu-entry>
Basically node <console-menu-entry> may have either tags inside it or some text value.
How to process it using jaxb? When I do like this it fails with
If a class has #XmlElement property, it cannot have #XmlValue property.
error.
My class looks like this
#XmlRootElement(name="console-menu-entry")
#XmlAccessorType(XmlAccessType.FIELD)
#ToString
public #Data class XmlConsoleMenuEntry {
#XmlAttribute
private String index;
#XmlAttribute
private String text;
#XmlValue
private String value;
#XmlElement(name="console-menu-entry")
private List<XmlConsoleMenuEntry> entries;
}
P.S. Using jaxb is not a requirement so if there is an approach using another library I am open to suggestions.
I would suggest to use JAXB but come from another end. Try to make use of xsd schema. This approach has a number of advantages:
Developing an XSD lets you understand your datamodel more deeply, detect possible flaws and see the data structure more clear.
You can use XSD to generate the parser which will help you to parse your xml (meeting that model) in a few lines of code
You can use XSD to create xml files (all the modern IDEs integrate XSD data in code-assistance mechanisms) so that you will get advice from IDE on which element would fit your datamodel in a particular place, which attribute is requited for a particular element (even which values are suitable for that attrubute) and many other usefule things
Below is the example of xsd schema which will let you generate parser which parses your example (you should only provide appropriate namespace for your xml elements)
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://some.your.schema"
xmlns:tns="http://some.your.schema"
elementFormDefault="qualified">
<xs:complexType name="ConsoleMenuEntry" mixed="true" >
<xs:sequence>
<xs:element ref="tns:console-menu-entry" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="index" type="xs:integer" use="required"/>
<xs:attribute name="text" type="xs:string" use="required"/>
</xs:complexType>
<xs:element name="console-menu-entry" type="tns:ConsoleMenuEntry"/>
</xs:schema>
You now can generate the parser files (Windows example)
"%JAVA_HOME%\bin\xjc" -d ../src -p your.app.generated test.xsd
Where -d ../src specifies the path on hard drive where your parser classes would be located, -p your.app.generated specifies the package for you generated parser classes and test.xsd is the schema file name
Here is the example of test.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<console-menu-entry xmlns="http://some.your.schema" index="1" text="Some new text">
<console-menu-entry index="1" text="some other text">
sdfsdkljf
</console-menu-entry>
<console-menu-entry index="2" text="some other text"/>
</console-menu-entry>
And the code that parses it:
public class Main {
public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(ObjectFactory.class);
ConsoleMenuEntry rootEntry = ((JAXBElement<ConsoleMenuEntry>) jc.createUnmarshaller().unmarshal(new File("PATH_TO_FILE\\test.xml"))).getValue();
processMenuEntry(rootEntry);
}
private static void processMenuEntry(ConsoleMenuEntry menuEntry) {
System.out.println("Index (attr) = " + menuEntry.getIndex() + ", Text (attr) = '" + menuEntry.getText() + "'");
for (Serializable element : menuEntry.getContent()) {
if (element instanceof JAXBElement) {
processMenuEntry(((JAXBElement<ConsoleMenuEntry>) element).getValue());
} else if (element instanceof String) {
String innerText = element.toString().trim();
if (innerText.length() > 0) {
System.out.println("Inner text: '" + innerText);
}
}
}
}
}
I use a common JAXB model for JAX-WS (Metro) and JAX-RS (Jersey). I have the following request snippet:
<xs:element name="CreatedProjFolders">
<xs:complexType>
<xs:sequence>
<xs:element name="CreatedProjFolder" type="tns:CreatedProjFolder" minOccurs="0"
maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="parentItemId" type="tns:itemId" use="required" />
</xs:complexType>
</xs:element>
<xs:complexType name="CreateProjFolder">
<xs:attribute name="itemId" type="tns:itemId" use="required" />
...
</xs:complexType>
XJC generated this class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"createProjFolders"
})
#XmlRootElement(name = "CreateProjFolders")
public class CreateProjFolders {
#XmlElement(name = "CreateProjFolder", required = true)
#NotNull
#Size(min = 1)
#Valid
// Name has been pluralized with JAXB bindings file
protected List<CreateProjFolder> createProjFolders;
#XmlAttribute(name = "parentItemId", required = true)
#NotNull
#Size(max = 128)
protected String parentItemId;
...
}
The appropriate JSON POST should look like:
{"parentItemId":"P5J00142301", "createProjFolders":[
{"itemId":"bogus"}
]}
but actually must look like:
{"parentItemId":"P5J00142301", "CreateProjFolder":[
{"itemId":"bogus"}
]}
How can rename the property name for JSON only resembling the one in Java (protected List<CreateProjFolder> createProjFolders)?
When MOXy is used as your JSON-binding provider the JSON keys will be the same as what is specfieid in the #XmlElement annotations. For example when you have:
#XmlElement(name = "CreateProjFolder", required = true)
protected List<CreateProjFolder> createProjFolders;
You will get:
{"parentItemId":"P5J00142301", "CreateProjFolder":[
{"itemId":"bogus"}
]}
If you want different names in JSON than in XML you can leverage MOXy's external mapping metadata to override what has been specified in the annotations:
http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html
After reading Blaise's post and the blog, it took me two days to come up with a working solution. First of all, the current status of MOXyJsonProviderand ConfigurableMoxyJsonProvider make it a pain-in-the-ass exprience to make it work and have never been designed for that.
My first test was to make a clean room implementation which is completely decoupled from Jersey and runs in a main method.
Here is the main method:
public static void main(String[] args) throws JAXBException {
Map<String, Object> props = new HashMap<>();
InputStream importMoxyBinding = MOXyTest.class
.getResourceAsStream("/json-binding.xml");
List<InputStream> moxyBindings = new ArrayList<>();
moxyBindings.add(importMoxyBinding);
props.put(JAXBContextProperties.OXM_METADATA_SOURCE, moxyBindings);
props.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
props.put(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
JAXBContext jc = JAXBContext.newInstance("my.package",
CreateProjFolders.class.getClassLoader(), props);
Unmarshaller um = jc.createUnmarshaller();
InputStream json = MOXyTest.class
.getResourceAsStream("/CreateProjFolders.json");
Source source = new StreamSource(json);
JAXBElement<CreateProjFolders> create = um.unmarshal(source, CreateProjFolders.class);
CreateProjFolders folders = create.getValue();
System.out.printf("Used JAXBContext: %s%n", jc);
System.out.printf("Unmarshalled structure: %s%n", folders);
Marshaller m = jc.createMarshaller();
m.setProperty(MarshallerProperties.INDENT_STRING, " ");
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
System.out.print("Marshalled structure: ");
m.marshal(folders, System.out);
}
Here the json-binding.xml:
<?xml version="1.0" encoding="UTF-8"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="my.package"
xml-mapping-metadata-complete="false">
<xml-schema namespace="urn:namespace"
element-form-default="QUALIFIED" />
<java-types>
<java-type name="CreateProjFolders">
<xml-root-element />
<java-attributes>
<xml-element java-attribute="projFolders" name="createProjFolders" />
</java-attributes>
</java-type>
<java-type name="CreateProjFolder">
<java-attributes>
<xml-element java-attribute="access" name="access" />
</java-attributes>
</java-type>
<java-type name="Access">
<java-attributes>
<xml-element java-attribute="productionSites" name="productionSites" />
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
and a sample input file:
{"parentItemId":"some-id",
"createProjFolders":[
{"objectNameEn":"bogus", "externalProjectId":"123456",
"access":{"productionSites":["StackOverflow"], "user":"michael-o"}}
]
}
Unmarshalling and marshalling work flawlessly. Now, how to make it work with Jersey? You can't because you cannot pass JAXBContext properties.
You need to copy MOXy's MOXyJsonProvider and the entire source of Jersey Media MOXy except for XML stuff into a new Maven project because of the AutoDiscoverable feature. This package will replace the original dependency.
Apply the following patches. Patches aren't perfect and can be imporoved because some code is duplicate, thus redundant but that can be done in a ticket later.
Now confiure that in your Application.class:
InputStream importMoxyBinding = MyApplication.class
.getResourceAsStream("/json-binding.xml");
List<InputStream> moxyBindings = new ArrayList<>();
moxyBindings.add(importMoxyBinding);
final MoxyJsonConfig jsonConfig = new MoxyJsonConfig();
jsonConfig.setOxmMedatadataSource(moxyBindings);
ContextResolver<MoxyJsonConfig> jsonConfigResolver = jsonConfig.resolver();
register(jsonConfigResolver);
Now try it. After several calls on different models you will see JAXBExceptions with 'premature end of file'. You will ask you why?! The reason is that the MOXyJsonProvider creates and caches JAXBContexts per domain class and not per package which means that your input stream is read several times but has already been closed after the first read. You are lost. You need to reset the stream but cannot change the inner guts of MOXy. Here is a simple solution for that:
public class ResetOnCloseInputStream extends BufferedInputStream {
public ResetOnCloseInputStream(InputStream is) {
super(is);
super.mark(Integer.MAX_VALUE);
}
#Override
public void close() throws IOException {
super.reset();
}
}
and swap your Application.class for
moxyBindings.add(new ResetOnCloseInputStream(importMoxyBinding));
After you have felt the pain in the ass, enjoy the magic!
Final words:
I did not accept Blaise's answer (upvote given) because it was only a fraction of a solution but lead me into the right direction.
Both classes make it quite hard to solve a very simple problem, more suprisingly that I am appearantly the only one who wants to pass OXM_METADATA_SOURCE. Seriously?
I have a web-service, defined by writing its WSDL and underlaying XSD, and the java server code classes / java bindings were generated using JAXB/xjc.
Everything looks fine service is running properly... but for every request (looking well-formed after receiving when looking on log-output) the nested elements seem to be always null when accessing through my java code.
Can someone figure out why customerId.getCustomer() always returns null?
My XSD (partly):
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns:tip="http://example.org/tip" xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://example.org/tip/pro">
<complexType name="id">
<attribute name="id" type="int" use="required"/>
<attribute name="name" type="string" use="optional"/>
</complexType>
<complexType name="customer_id">
<sequence>
<element name="customer" type="tip:id" minOccurs="0"/>
</sequence>
</complexType>
<element name="get_customer_request" type="tip:customer_id"/>
</schema>
The generated class CustomerId:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "customer_id", propOrder = {"customer"})
public class CustomerId {
protected Id customer;
public Id getCustomer() {
return customer;
}
public void setCustomer(Id value) {
this.customer = value;
}
}
The generated class for Id look similar, I don't think there is something special.
In my request handler I got the following extract:
Handler:
JAXBElement<?> request = requestHandler.unmarshallRequest(inputStream);
Object jaxbClass = request.getDeclaredType();
expectedClass = CustomerId.class;
// next line does not throw exception with given XML
if (jaxbClass != expectedClass) throw new IllegalArgumentException();
CustomerId customerId = (CustomerId)request.getValue();
if (customerId == null) {
logInfo("customerId: null");
} else if (customerId.getCustomer() == null) {
// this is the part that always will be executed... why?
logInfo("customerId.customer: null");
} else {
logInfo("customer id: " + customerId.getCustomer().getId());
// return mbean.getCustomer(customerId);
}
And finally an example request XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<m:get_customer_request xmlns:m="http://example.org/tip/pro">
<customer id="0" name="help"/>
</m:get_customer_request>
I stripped out SOAP envelope and body tags, since this is not causing any trouble.
Can anyone see, what I am doing wrong? (I am pretty sure, I do...)
Thanks for your effords!
PART 1
When I create a new Id and set customerId.customer with this, the full
output is
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<get_customer_request xmlns="example.com/tip/pro">
<customer name="xy" id="1"/>
</get_customer_request>
Based on this information it appears that your JAXB mappings expect the customer element to be in the example.com/tip/pro namespace, and your request document should be:
<?xml version="1.0" encoding="ISO-8859-1"?>
<m:get_customer_request xmlns:m="http://example.org/tip/pro">
<m:customer id="0" name="help"/>
</m:get_customer_request>
PART 2
When putting m: prefix to customer element in my request, the parser
complains that he found m:customer and expected customer.
This means that your XML schema does not match your mappings. If you expect the customer element to be in the namespace you can change your XML schema to the following:
<?xml version="1.0" encoding="UTF-8"?>
<schema
xmlns:tip="http://example.org/tip"
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.org/tip/pro"
elementFormDefault="qualified">
...
</schema>
For more information on JAXB and namespaces see:
http://blog.bdoughan.com/2010/08/jaxb-namespaces.html
I have a question about JAXB. Basically what I have are these two classes:
Element {
String name
List<Attribute> attributes;
}
Attribute {
String key
String value
}
Of course with getters and setters, and with JAXB XmlRootElement.
The XML generated from this is:
<element>
<attributes>
<key>id</key>
<value>1</value>
</attributes>
<name>My Element</name>
</element>
But what I'm looking for is something more like this:
<element id="1">
<name>My Element</name>
</element>
That is, for each instance of Attribute, I want key=value (as an attribute)
Is this possible in JAXB?
Regards,
Morten
I do not think that it will work with a List. But there is an alternative using a
Map and
#XmlAnyAttribute
Your example:
#XmlRootElement
public static class Element
{
#XmlElement
String name;
#XmlAnyAttribute
Map<QName, Object> map;
}
{
//
Element element = new Element();
element.name = "a wonderful name";
element.map = new HashMap<QName, Object>();
element.map.put( new QName( "id" ), "1" );
element.map.put( new QName( "other" ), "2" );
}
Result of that:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<element id="1" other="2">
<name>a wonderful name</name>
</element>
Best regards!
I am novice to JAXB , i am trying to sample using JAXB.
trying to dispaly the values in the MenuList.xml
----MenuList.xml-----------
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<menulist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Category1 name="Development">
<MenuItem1>Projects</MenuItem1>
<MenuItem2>Library</MenuItem2>
<MenuItem3>Library1</MenuItem3>
</Category1>
</menulist>
----------------------------MenuList.xsd-------------------
<?xml version="1.0" encoding="utf-8" ?>
<!--Created with Liquid XML Studio Developer Edition (Trial) 8.0.11.2171 (http://www.liquid-technologies.com)-->
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="menulist">
<xs:complexType>
<xs:sequence>
<xs:element name="Category1">
<xs:complexType>
<xs:attribute name="MenuItem1" type="xs:string" use="required" />
<xs:attribute name="MenuItem2" type="xs:string" use="required" />
<xs:attribute name="MenuItem3" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
The uisng the command I run the xsd file and it generated the classes.
MenuList and Object Factory.
AppTest.java
package com.xx.menu;
import java.io.File;
import java.io.IOException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
public class TestNew {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
JAXBContext jc = JAXBContext.newInstance("com.xx.menu"); //Create unmarshaller
Unmarshaller um = jc.createUnmarshaller();
File file = new File ("C:\\sample\\menulist.xml");
JAXBElement element = (JAXBElement)um.unmarshal(file);
Menulist menulist= (Menulist) element.getValue ();
System.out.println("name : "+menulist.getMenu());
}
catch( UnmarshalException ue ) {
ue.printStackTrace();
System.out.println( "Caught UnmarshalException" );
} catch( JAXBException je ) {
je.printStackTrace();
} catch( Exception ioe ) {
ioe.printStackTrace();
}
}
}
Error:
java.lang.ClassCastException: com.xx.menu.Menulist
at com.xx.menu.TestNew.main(TestNew.java:26)
Can you please help me where I am going wrong ...I will be greatly thankful to you.
Thanks
You're overcomplicating things. You don't need to muck about with JAXBElement if the Unmarshaller is giving you the Menulist object directly. The ClassCastExcepotion pretty much tells you what you need to do:
Menulist menulist= (Menulist) um.unmarshal(file);
JAXBElement is only used in certain specific situations, and this isn't one of them.
Could you please add a short description about when JAXBElement is to
be used?
JAXBElement is used by JAXB when a class or property does not contain enough information by itself to indicate what element should be written to XML.
#XmlRootElement
In your example the Menulist class corresponds to the menulist element. Since in the schema that element has an anonymous complex type, one element is associated with the class generated from the corresponding complex type.
#XmlElementDecl
If in your example the menulist element had corresponded to a named complex type, then potentially multiple elements could correspond to the same class generated from that complex type. Instead of annotating the class with #XmlRootElement, one or more #XmlElementDecl annotations would be generated on the ObjectFactory class. This would cause JAXBElement to be unmarshalled.
Handling All Results
If you don't know whether or not a JAXBElement will be returned you can leverage a JAXBIntrospector to do any necessary unwrapping.
JAXBIntrospector ji = jaxbContext.createJAXBIntrospector();
Menulist menulist = (Menulist) ji.getValue(unmarshaller.unmarshal(xml));
For More Information
http://blog.bdoughan.com/2012/07/jaxb-and-root-elements.html