Recently I have faced a problem which seems to be very common: how to represent an XML element with attributes and simple textual content, like this:
<elem attr="aval">elemval</elem>
using JAXB.
I've found many advices on how to do this, but every one of these advices involves manual editing of binding classes.
I have a set of schemas and I use XJC to convert these schemas to Java classes. However, it seems that it produces wrong code, i.e. it does not generate methods to set plain content, there are methods for setting attributes only.
Is it possible to fix this behavior of XJC? Extensive googling didn't help on this question.
Below is an XML schema that defines the XML structure for you use case.
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/schema"
xmlns:tns="http://www.example.org/schema" elementFormDefault="qualified">
<element name="elem">
<complexType>
<simpleContent>
<extension base="string">
<attribute name="attr" type="string" />
</extension>
</simpleContent>
</complexType>
</element>
</schema>
Generating a JAXB model from this XML schema will result in the following class:
package forum12859885;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"value"
})
#XmlRootElement(name = "elem")
public class Elem {
#XmlValue
protected String value;
#XmlAttribute(name = "attr")
protected String attr;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getAttr() {
return attr;
}
public void setAttr(String value) {
this.attr = value;
}
}
Related
I have a xml representation of object like
OrderList (has list of) Orders and each order has a list of commodities.
I want to validate my commodities and if not valid I want to remove them from order. If all commodities are invalid then I remove the order from the orderlist.
I have been able to validate Orderlist
JAXBContext jaxbContext = JAXBContext.newInstance("com.jaxb");
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File(XSD));
JAXBSource source = new JAXBSource(jaxbContext, orderList);
Validator validator = schema.newValidator();
DataFeedErrorHandler handler = new DataFeedErrorHandler();
validator.setErrorHandler(handler);
validator.validate(source);
I am not able to find a way to validate commodities.
Something like
for(Order order: orderList){
for(Commodity commodity: order.getCommodity()){
if(!isCommodityValid(commodity)){
// mark for removal
}
}
}
Any help would be greatly appreciated.
TL;DR
You can do a dummy marshal and leverage the JAXB validation mechanisms rather than using the javax.xml.validation mechanisms directly.
LEVERAGING Marshaller.Listener & ValidationEventHandler (CommodityValidator)
For this example we will leverage aspects of Marshaller.Listener and ValidationEventHandler to accomplish the use case.
Marshal.Listener - This will be called for each object being marshalled. We can use it to cache the instance of Order that we may need to remove the instance of Commodity from.
ValidationEventHandler this will give us access to each problem occuring with validation during the marshal operation. For each problem it will be passed a ValidationEvent. This ValidationEvent will hold a ValidationEventLocator from which we can get the object that had the problem being marshalled.
import javax.xml.bind.*;
public class CommodityValidator extends Marshaller.Listener implements ValidationEventHandler {
private Order order;
#Override
public void beforeMarshal(Object source) {
if(source instanceof Order) {
// If we are marshalling an Order Store It
order = (Order) source;
}
}
#Override
public boolean handleEvent(ValidationEvent event) {
if(event.getLocator().getObject() instanceof Commodity) {
// If the Error was Caused by a Commodity Object Remove it from the Order
order.setCommodity(null);
return true;
}
return false;
}
}
DEMO CODE
The following code can be run to prove that everything works.
import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.validation.*;
import org.xml.sax.helpers.DefaultHandler;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Orders.class);
// STEP 1 - Build the Object Model
Commodity commodity1 = new Commodity();
commodity1.setId("1");
Order order1 = new Order();
order1.setCommodity(commodity1);
Commodity commodityInvalid = new Commodity();
commodityInvalid.setId("INVALID");
Order order2 = new Order();
order2.setCommodity(commodityInvalid);
Commodity commodity3 = new Commodity();
commodity3.setId("3");
Order order3 = new Order();
order3.setCommodity(commodity3);
Orders orders = new Orders();
orders.getOrderList().add(order1);
orders.getOrderList().add(order2);
orders.getOrderList().add(order3);
// STEP 2 - Check that all the Commodities are Set
System.out.println("\nCommodities - Before Validation");
for(Order order : orders.getOrderList()) {
System.out.println(order.getCommodity());
}
// STEP 3 - Create the XML Schema
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File("src/forum16953248/schema.xsd"));
// STEP 4 - Perform Validation with the Marshal Operation
Marshaller marshaller = jc.createMarshaller();
// STEP 4a - Set the Schema on the Marshaller
marshaller.setSchema(schema);
// STEP 4b - Set the CommodityValidator as the Listener and EventHandler
CommodityValidator commodityValidator = new CommodityValidator();
marshaller.setListener(commodityValidator);
marshaller.setEventHandler(commodityValidator);
// STEP 4c - Marshal to Anything
marshaller.marshal(orders, new DefaultHandler());
// STEP 5 - Check that the Invalid Commodity was Removed
System.out.println("\nCommodities - After Validation");
for(Order order : orders.getOrderList()) {
System.out.println(order.getCommodity());
}
}
}
OUTPUT
Below is the output from running the demo code. Node how after the marshal operation the invalid commodity was removed.
Commodities - Before Validation
forum16953248.Commodity#3bb505fe
forum16953248.Commodity#699c8551
forum16953248.Commodity#22f4bf02
Commodities - After Validation
forum16953248.Commodity#3bb505fe
null
forum16953248.Commodity#22f4bf02
XML SCHEMA (schema.xsd)
Below is the XML schema used for this example.
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
<element name="orders">
<complexType>
<sequence>
<element name="order" minOccurs="0" maxOccurs="unbounded">
<complexType>
<sequence>
<element name="commodity">
<complexType>
<attribute name="id" type="int"/>
</complexType>
</element>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
JAVA MODEL
Below is the object model I used for this example.
Orders
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Orders {
private List<Order> orderList = new ArrayList<Order>();
#XmlElement(name="order")
public List<Order> getOrderList() {
return orderList;
}
}
Order
public class Order {
private Commodity commodity;
public Commodity getCommodity() {
return commodity;
}
public void setCommodity(Commodity commodity) {
this.commodity = commodity;
}
}
Commodity
import javax.xml.bind.annotation.*;
public class Commodity {
private String id;
#XmlAttribute
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
One of my client to integrate provide an XML with attribute name "_1", "_2" ... etc.
e.g.
<element _1="attr1" _2="attr2">
using JAXB to generate the class, the getter method of the attribute will be get1() and get2()
However in my JSP pages, using JSTL and EL, sure I cannot access the value through
${variable.1}
How can I access the value using EL correctly?
You could use an external binding file to rename the property generate by JAXB:
schema.xsd
Below is a sample XML schema based on your post:
<?xml version="1.0" encoding="UTF-8"?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org"
xmlns:tns="http://www.example.org"
elementFormDefault="qualified">
<element name="element1">
<complexType>
<attribute name="_1" type="string" />
<attribute name="_2" type="string" />
</complexType>
</element>
</schema>
binding.xml
An external binding file is used to customize how Java classes are generated from the XML schema. Below we'll use an external binding file to rename the generated properties.
<jaxb:bindings
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
version="2.1">
<jaxb:bindings schemaLocation="schema.xsd">
<jaxb:bindings node="//xsd:attribute[#name='_1']">
<jaxb:property name="one"/>
</jaxb:bindings>
<jaxb:bindings node="//xsd:attribute[#name='_2']">
<jaxb:property name="two"/>
</jaxb:bindings>
</jaxb:bindings>
</jaxb:bindings>
XJC Call
Below is an example of how you reference the binding file when using the XJC tool.
xjc -b binding.xml schema.xsd
Element1
Below is what the generated class will look like:
package forum12259754;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "")
#XmlRootElement(name = "element1")
public class Element1 {
#XmlAttribute(name = "_1")
protected String one;
#XmlAttribute(name = "_2")
protected String two;
public String getOne() {
return one;
}
public void setOne(String value) {
this.one = value;
}
public String getTwo() {
return two;
}
public void setTwo(String value) {
this.two = value;
}
}
Use this notation:
${variable.["1"]}
I'm using a POJO object with XmlType for a custom XML adapter I built for marshaling a map of strings. I'm having issues however, with getting it to allow me to use null values properly. I was able to get it to work, but I'm not happy with the XML it is generating.
This is what I'm currently using that I would like to work, but as you can see in an sample XML result, it is not including the proper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" value
#XmlType(name="element")
public class RestMapElements {
#XmlAttribute(name="name")
public String key;
#XmlValue
public String value;
public RestMapElements(String key, String value) {
this.key = key;
this.value = value;
}
}
The resulting XML (slimmed to relevant data).
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
...
<element-list>
<item name="activated_date">2012-03-29 11:34:14.323</item>
<item name="some_null_value"/>
</element-list>
...
However, I was able to get it to work with this, I'm just not happy with the XML having to add an additional "value" tag inside of the item tag to get it to work. (side note, why is it naming it item instead of element like I tried to specify in the XmlType name declaration?)
#XmlType(name="element")
public class RestMapElements {
#XmlAttribute(name="name")
public String key;
#XmlElement(nillable = true)
public String value;
public RestMapElements(String key, String value) {
this.key = key;
this.value = value;
}
}
Again, the resulting XML (slimmed to relevant data).
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
...
<element-list>
<item name="activated_date"><value>2012-03-29 11:34:14.323</value></item>
<item name="some_null_value"><value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/></item>
</element-list>
...
Really, I can use the second as it works to solve my issue. I'm just wanting to use this as a learning experience to see if JAXB using annotations will allow be to bend this to what I'm looking for without having to add that additional value tag underneath an item tag just so I can support null values. Right now, when it unmarshals in the first example, I end up getting empty strings instead of null. In the second example, I get the null value I was expecting.
FYI: I'm currently using Jersey 1.11
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
You could use MOXy's #XmlNullPolicy extension to map this use case:
RestMapElements
package forum10415075;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
#XmlType(name="element")
public class RestMapElements {
#XmlAttribute(name="name")
public String key;
#XmlValue
#XmlNullPolicy(nullRepresentationForXml=XmlMarshalNullRepresentation.XSI_NIL)
public String value;
}
Root
package forum10415075;
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Root {
#XmlElementWrapper(name="element-list")
#XmlElement(name="item")
public List<RestMapElements> items = new ArrayList<RestMapElements>();
}
jaxb.properties
To use MOXy as your JAXB (JSR-222) provider you need to add a file called jaxb.properties in the same package as your domain model with the following content:
javax.xml.bind.context.factory = org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
package forum10415075;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum10415075/input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<element-list>
<item name="activated_date">2012-03-29 11:34:14.323</item>
<item name="some_null_value" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</element-list>
</root>
Note
If you are using Jersey in GlassFish 3.1.2 MOXy or WebLogic 12.1.1 is already included:
http://blog.bdoughan.com/2012/02/glassfish-312-is-full-of-moxy.html
http://blog.bdoughan.com/2011/12/eclipselink-moxy-is-jaxb-provider-in.html
I see your problem. An item element with xsi:nil="true" will be generated only by setting the corresponding RestMapElements entry in your ArrayList (or whatever) to null, losing the attribute. I think there isn't much of a solution to this.
One option would be to use your marshalling from the beginning of your post and unmarshal using the following:
If you're doing something like this:
#XmlElementWrapper(name="element-list")
#XmlElement(name="item")
public ArrayList<RestMapElements> list;
You can use a XmlAdapter to check if the value is an empty String and set it to null if it is:
#XmlElementWrapper(name="element-list")
#XmlElement(name="item")
#XmlJavaTypeAdapter(ItemAdapter.class)
public ArrayList<RestMapElements> list;
And ItemAdapter:
public class ItemAdapter extends XmlAdapter<RestMapElements, RestMapElements> {
#Override
public RestMapElements unmarshal(RestMapElements v) throws Exception {
if (v.value.equals("")) v.value = null;
return v;
}
}
Although this is still inelegant imho.
If you want to generate the proper xsi:nil="true" item elements, this is obviously not what you want though.
Good luck.
I'm trying to convert this XML to Java object and then updating key and value and then save it to XML.I can convert simple XML but this one has two attribute which is the same.
Can anybody help me to represent this xml in java class as Configuration.java?
XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="mode" value="1"/>
<add key="type" value="shs"/>
</appSettings>
</configuration>
Configuration.java
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Configuration {
String appSettings;
String add;
String key;
String value;
public String getAppSettings() { return appSettings; }
#XmlElement
public void setAppSettings(String appSettings) { this.appSettings = appSettings;}
public String getAdd() { return add; }
#XmlElement
public void setAdd(String add) { this.add = add; }
public String getKey() { return key; }
#XmlAttribute
public void setKey(String key) { this.key = key; }
public String getValue() { return value; }
#XmlAttribute
public void setValue(String value) { this.value = value; }
}
I suggest to:
specify the XSD
generate the JAXB classes using the following Maven plugin: http://java.net/projects/maven-jaxb2-plugin/pages/Home
Use JAXB if you want fine control over the XML to POJO creation. But you will have to specify the structure of your XML in an XSD first and let JAXB generate the Java classes for you.
Another way is to use XStream.
XStream xstream = new XStream();
Configuration config= (Configuration)xstream.fromXML(xml);
But you might have to update your Configuration class to use List for the add nodes as Kuldeep Jain said in his answer.
Edit: Take a look at the 2-minute XStream tutorial also - http://x-stream.github.io/tutorial.html
I think you will have to have a List for the follwing add nodes:
<add key="mode" value="1"/>
<add key="type" value="shs"/>
EDIT:
You may have a look at JAXB article for help.
I am using Jaxb2Marshaller to marshal Java beans through spring #ResponseBody annotation. For JSON marshaling was working fine. But for xml I was continuously getting HTTP 406 response. Little bit digging in Jaxb2Marshaller class reveals it checks for #XmlRootElement for bounded classes (see below snippet).
While generating java code from xsd my pojo does not contain #XmlRootElement and proper message converter was not identified by AnnotationMethodHandlerAdapter and finally result in 406.
Instead of auto generating java code from xsd I have created my own pojo class and used #XmlRootElement. Then marshaling works fine.
I want to understand why it is important of having #XmlRootElement check for bounded classes. Or any way to specify element as #XmlRootElement in xsd.
Code snippet from Jaxb2Marshaller:
public boolean supports(Class clazz) {
return supportsInternal(clazz, true);
}
private boolean supportsInternal(Class<?> clazz, boolean checkForXmlRootElement) {
if (checkForXmlRootElement && clazz.getAnnotation(XmlRootElement.class) == null) {
return false;
}
if (clazz.getAnnotation(XmlType.class) == null) {
return false;
}
if (StringUtils.hasLength(getContextPath())) {
String className = ClassUtils.getQualifiedName(clazz);
int lastDotIndex = className.lastIndexOf('.');
if (lastDotIndex == -1) {
return false;
}
String packageName = className.substring(0, lastDotIndex);
String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":");
for (String contextPath : contextPaths) {
if (contextPath.equals(packageName)) {
return true;
}
}
return false;
}
else if (!ObjectUtils.isEmpty(classesToBeBound)) {
return Arrays.asList(classesToBeBound).contains(clazz);
}
return false;
}
Edit:
Blaise answer helped me solve #XmlRootElement problem. But still if someone has any information about why check for XmlRootElement is required, will be a good info.
Why the #XmlRootElement Annotation is Checked For
Spring requires a root element when marshalling the object to XML. JAXB provides two mechanisms to do this:
The #XmlRootElement annotation
Wrapping the root object in an instance of JAXBElement.
Since the object is not wrapped in a JAXBElement Spring is ensuring that the other condition is met.
How Generate an #XmlRootElement
JAXB will generate an #XmlRootElement annotation for all global elements in an XML schema. The following will cause an #XmlElement:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="foo">
<xsd:complexType>
...
</xsd:complextType>
</xsd:element>
</xsd:schema>
When #XmlRootElement is not Generated
An #XmlRootElement annotation will not be generated for global types.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="foo" type="foo"/>
<xsd:complexType name="foo">
...
</xsd:complexType>
</xsd:schema>
Instead the global element(s) associated with the global types are captured in the ObjectFactory class (annotated with #XmlRegistry) in the form of #XmlElementDecl annotations. These annotations
package generated;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
private final static QName _Foo_QNAME = new QName("", "foo");
public Foo createFoo() {
return new Foo();
}
#XmlElementDecl(namespace = "", name = "foo")
public JAXBElement<Foo> createFoo(Foo value) {
return new JAXBElement<Foo>(_Foo_QNAME, Foo.class, null, value);
}
}
The #XmlElementDecl annotation provides similar information as #XmlRootElement and could be used for unmarshal operations. JAX-RS implementations probably do not leverage #XmlElementDecl however since marshal operations would require the object to be wrapped in a JAXBElement object to provide the root element name/namespace.
it's an known issue:
https://jira.springsource.org/browse/SPR-7931
"Checking for #XmlRootElement annotation should be made optional in Jaxb2Marshaller"
You can use JaxbElement for classes that does not have #XmlRootElement annotation. #XmlRootElement annotation is placed only to non referenced top level objects if you are generating your code from xsd
Edit
See #Blaise Doughan answer.
#XmlRootElement will be placed only if there is no reference to that type in another type.