I have this class:
Class B {
private String D;
private String E;
}
Using XStream, I would like to generate XML like this, where elements A and B are generated in the XML, even though they don't exist in the java.:
<A>
<B>
<C>
<D/>
<E/>
</C>
</B>
</A>
Possible?
You can implement and register a custom converter in the XStream instance.
For example:
XStream xstream = new new XStream(...);
xstream.registerConverter(new BConverter());
xstream.toXML(new B(),new BufferedWriter(...));
Example of the converter implementation:
class BConverter implements com.thoughtworks.xstream.converters.Converter{
#Override
public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext mc) {
B target=(B)o;
writer.startNode("A");
writer.startNode("B");
writer.startNode("C");
writer.startNode("D");
writer.setValue(target.getD());
writer.endNode();//end node D
writer.startNode("E");
writer.setValue(target.getE());
writer.endNode();//end node E
writer.endNode();//end node C
writer.endNode();//end node B
writer.endNode();//end node A
}
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext uc) {
//unmarshalizing logic here
}
#Override
public boolean canConvert(Class type) {
return type.equals(B.class);
}
}
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Since you are looking for an annotation based solution, you may be interested in the #XmlPath extension in MOXy.
B
The #XmlPath annotation allows you to specify your mapping as an XPath.
package forum11334385;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement(name="A")
#XmlAccessorType(XmlAccessType.FIELD)
class B {
#XmlPath("B/C/D/text()")
private String D;
#XmlPath("B/C/E/text()")
private String E;
}
jaxb.properties
To specify MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
package forum11334385;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(B.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum11334385/input.xml");
B b = (B) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(b, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<A>
<B>
<C>
<D>Foo</D>
<E>Bar</E>
</C>
</B>
</A>
For More Information
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
http://blog.bdoughan.com/2011/08/binding-to-json-xml-geocode-example.html
http://blog.bdoughan.com/2011/03/map-to-element-based-on-attribute-value.html
Related
I have set up a minimal working example of the problem I have. These are the JAXB classes.
moxytest/A.java
package moxytest;
#XmlRootElement
public class A {
#XmlElement(name = "b")
public List<B> bs;
#XmlElement(name = "c")
public List<C> cs;
}
moxytest/B.java
package moxytest;
public class B {
#XmlAttribute
#XmlID
public String id;
#XmlAttribute
public EnumD md;
}
moxytest/C.java
package moxytest;
public class C {
#XmlAttribute
#XmlIDREF
public B b;
}
moxytest/EnumD.java
package moxytest;
#XmlEnum
public enum EnumD {
VALUE1, VALUE2, VALUE3
}
Example input:
<?xml version="1.0" encoding="UTF-8" ?>
<a>
<b id="b1" md="VALUE1"/>
<b id="b2" md="VALUE2"/>
<b id="b3" md="VALUE3"/>
<c b="b2"/>
<c b="b1"/>
<c b="b3"/>
</a>
So C elements are referencing B elements by id, and B elements have an Enum attribute.
This line of Java code
JAXBContext context = JAXBContext.newInstance(A.class);
Produces an exception with the following message:
The #XmlAttribute property b in type moxytest.C must reference a type that maps to text in XML. moxytest.B cannot be mapped to a text value.
I have been debugging and reading some lines of MOXy source code. That is how I was able to set up this minimal example. The JDK implementation works fine.
EDIT:
I am using EclipseLink 2.6.0 (thanks Santhosh Kumar Tekuri)
I tested your code with following maven dependency:
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.0</version>
</dependency>
i placed jaxb.properties in same package where model classes exist. this file contains:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
and it works fine. below is my unmarshalling code:
public static void main(String[] args) throws Exception{
JAXBContext context = JAXBContext.newInstance(A.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Object obj = unmarshaller.unmarshal(new File("input.xml"));
System.out.println(obj);
}
make sure you are using same moxy version I am using.
The problem is in the following :
I get the soap response with empty element inside (e.g. ... <someDate /> ... )
and as a result exception is being throwed when JAXB wants to parse this element
instead to set the appropriate field with null value.
How to configure JAXB to treat empty elements as null ?
Can we do this with JAXB only (not using some third-party workarounds)
Base Problem
Empty String is not a valid value for the xsd:date type. To be valid with the XML schema an optional element should be represented as an absent node.,
Why the Base Problem is Impacting You
All JAXB implementations will recognize that empty String is not a valid value for xsd:date. They do this by reporting it to an instance of ValidationEventHandler. You can see this yourself by doing the following:
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setEventHandler(new ValidationEventHandler() {
#Override
public boolean handleEvent(ValidationEvent event) {
System.out.println(event);
return true;
}
});
The implementation of JAX-WS you are using, leverages EclipseLink MOXy as the JAXB provider. And in the version you are using MOXy will by default throw an exception when a ValidationEvent of severity ERROR is encountered instead of FATAL_ERROR like the reference implementation. This has since been fixed in the following bug:
http://bugs.eclipse.org/369994
Work Around
If you are using the JAXB APIs directly you could simply override the default ValidationEventHandler. In a JAX-WS environment a XmlAdapter can be used to provide custom conversion logic. We will leverage an XmlAdapter to override how the conversion to/from Date is handled.
XmlAdapter (DateAdapter)
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateAdapter extends XmlAdapter<String, Date>{
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
#Override
public Date unmarshal(String v) throws Exception {
if(v.length() == 0) {
return null;
}
return dateFormat.parse(v);
}
#Override
public String marshal(Date v) throws Exception {
if(null == v) {
return null;
}
return dateFormat.format(v);
}
}
Java Model (Root)
The XmlAdapter is referenced using the #XmlJavaTypeAdapter annotation. If you wish this XmlAdapter to apply to all instances of Date you can register it at the package level (see: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html).
import java.util.Date;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
#XmlSchemaType(name = "date")
#XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
private Date abc;
#XmlSchemaType(name="date")
#XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
private Date qwe;
}
Demo Code
Below is a standalone example you can run to see that everything works.
jaxb.properties
In a standalone example to use MOXy as your JAXB provider you need to include a file called jaxb.propeties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<abc></abc>
<qwe>2013-09-05</qwe>
</root>
Demo
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/forum18617998/input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
Note that in the marshalled XML the Date field that was null was marshalled as an absent element (see: http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html).
<?xml version="1.0" encoding="UTF-8"?>
<root>
<qwe>2013-09-05</qwe>
</root>
I have a org.w3c.dom.Element that I'm returning from my XmlAdapter for a custom #XmlElement and I'd like to include it as part of a JAXB object as arbitrary XML (I'm aware I'll have to hand-craft the XSD). However, JAXB complains with
org.w3c.dom.Element is an interface, and JAXB can't handle interfaces.
Apparently the w3c XML types are not supported as Java types, which is a shame. But further than this, I get the same error when I use javax.xml.transform.Result which is apparently supported.
How can I include arbitrary XML elements as elements in JAXB?
Note: as per https://forums.oracle.com/thread/1668210 I've also tried
MessageFactory factory = MessageFactory.newInstance();
message = factory.createMessage();
SOAPElement element = message.getSOAPBody().addDocument(doc);
but that is also giving the same error.
TL;DR
You can have an XmlAdapter that converts you domain object to an instance of org.w3c.dom.Element as long as you specify the value type as Object (not Element).
Below is a full example.
XmlAdapter
A field/property of type java.lang.Object will keep unknown content as DOM nodes. You can leverage this in your use case by specifying the value type in your XmlAdapter as Object. You will need to ensure that the root element returned from the marshal method matches the field/property as defined by the #XmlElement annotation.
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class BarAdapter extends XmlAdapter<Object, Bar>{
private DocumentBuilder documentBuilder;
public BarAdapter() {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
documentBuilder = dbf.newDocumentBuilder();
} catch(Exception e) {
// TODO - Handle Exception
}
}
#Override
public Bar unmarshal(Object v) throws Exception {
Bar bar = new Bar();
Element element = (Element) v;
bar.value = element.getTextContent();
return bar;
}
#Override
public Object marshal(Bar v) throws Exception {
Document document = documentBuilder.newDocument();
Element root = document.createElement("bar");
root.setTextContent(v.value);
return root;
}
}
Java Model
Foo
The #XmlJavaTypeAdapter annotation is used to reference the XmlAdapter.
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
#XmlJavaTypeAdapter(BarAdapter.class)
private Bar bar;
}
Bar
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Bar {
String value;
}
Demo Code
Demo
Since there is a cost to creating the DocumentBuilderFactory we can leverage JAXB's ability to handle stateful instances of XmlAdapter by setting an instance on the Marshaller.
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum18272059/input.xml");
Foo foo = (Foo) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setAdapter(new BarAdapter());
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar>Hello World</bar>
</foo>
I am writing a code to set XmlTransient at runtime using MOXy. Here is the part of the code which is adapted from the example on http://blog.bdoughan.com/2011/06/moxy-extensible-models-refresh-example.html
public void setXmlTransient(Class<?> domainClass, String propertyName) {
XmlTransient xmlTransient = new XmlTransient();
xmlTransient.setJavaAttribute(propertyName);
JavaType javaType = getJavaType(domainClass);
javaType.getJavaAttributes().getJavaAttribute().add(objectFactory.createXmlTransient(xmlTransient));
}
Since I am doing this programmatically, I need to be able to create the propertyName exactly the same way as MOXy does. For most getter method names, like getOrder, the property name is done by removing get from the method name and change upper-case O to lower-case o, i.e. property name is order. However, I am hitting the case which my getter method is getXInA but xInA doesn't seem to be a valid property name. MOXy throws a warning like
Ignoring attribute [xInA] on class [Atom] as no Property was generated for it.
Does anyone know what the rules are used by MOXy for creating the property name from getters? or know where I can find out about this without reading the MOXy source code?
SHORT ANSWER
Because there are two capital letters in a row the property name is going to be XInA.
LONG ANSWER
Domain Model (Foo)
Below is a sample Java class with the property from your question.
package forum14945664;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Foo {
private String x;
public String getXInA() {
return x;
}
public void setXInA(String x) {
this.x = x;
}
}
MetadataSource (ExampleMetadataSource)
MetadataSource is a programmatic way to provide MOXy with the mapping metadata.
package forum14945664;
import java.util.*;
import org.eclipse.persistence.jaxb.metadata.MetadataSourceAdapter;
import org.eclipse.persistence.jaxb.xmlmodel.*;
import org.eclipse.persistence.jaxb.xmlmodel.JavaType.*;
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings.*;
public class ExampleMetadataSource extends MetadataSourceAdapter {
private ObjectFactory objectFactory;
private Map<Class<?>, JavaType> javaTypes;
private XmlBindings xmlBindings;
public ExampleMetadataSource() {
objectFactory = new ObjectFactory();
javaTypes = new HashMap<Class<?>, JavaType>();
xmlBindings = new XmlBindings();
xmlBindings.setPackageName("forum14945664");
xmlBindings.setJavaTypes(new JavaTypes());
}
#Override
public XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader) {
return xmlBindings;
}
public JavaType getJavaType(Class<?> clazz) {
JavaType javaType = javaTypes.get(clazz);
if(null == javaType) {
javaType = new JavaType();
javaType.setName(clazz.getSimpleName());
javaType.setJavaAttributes(new JavaAttributes());
xmlBindings.getJavaTypes().getJavaType().add(javaType);
javaTypes.put(clazz, javaType);
}
return javaType;
}
public void setXmlTransient(Class<?> domainClass, String propertyName) {
XmlTransient xmlTransient = new XmlTransient();
xmlTransient.setJavaAttribute(propertyName);
JavaType javaType = getJavaType(domainClass);
javaType.getJavaAttributes().getJavaAttribute().add(objectFactory.createXmlTransient(xmlTransient));
}
}
Specify MOXy as JAXB Provider (jaxb.properties)
To specify MOXy as the JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry.
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
In the demo code below we will create a JAXBContext based on the domain model and we will marshal an instance to XML. Then we will use the MetadataSource to make the property transient, refresh the JAXBContext and marshal the instance again.
package forum14945664;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.jaxb.JAXBHelper;
public class Demo {
public static void main(String[] args) throws Exception {
ExampleMetadataSource metadata = new ExampleMetadataSource();
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, metadata);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Foo foo = new Foo();
foo.setXInA("Hello World");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
metadata.setXmlTransient(Foo.class, "XInA");
JAXBHelper.getJAXBContext(jc).refreshMetadata();
marshaller.marshal(foo, System.out);
}
}
Output
First we see the XInA property marshalled, then after we make it transient we see that it is not in the XML from the second marshal operation.
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<XInA>Hello World</XInA>
</foo>
<?xml version="1.0" encoding="UTF-8"?>
<foo/>
For given structure of classes (skipped annotations and so on):
class A {
public B getObjectB {}
}
class B {
public String getHello { return " world"; }
}
I have no problems with generating correct XSD and XML output:
<A>
<B>
<hello>world</hello>
</B>
</A>
The thing is, I need to break it a little: first of all, XSD should remain as is - full. But while marshalling of A, I need to get somtething like this:
<A>
someStringForA
</A>
(so B is rendered as some calculated String). At the same time, while marshalling B (as a root), I still need to get "normal" output.
I tried with XmlAdapters, but using #XmlJavaTypeAdapter change the XSD too. Setting adapters through Marshaller.setAdapter(...) apparently (http://stackoverflow.com/questions/6110757/jaxb-xml-adapters-work-via-annotations-but-not-via-setadapter/6112149#6112149) will not work.
Some kind of solution would be, if there were possibility to "turn off" the #XmlJavaTypeAdapter (XSD is generated "manually" by JUnit, some switch or even hack is allowed)
Thanks for any help!
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Alternate Mapping - oxm.xml
If you are using MOXy as your JAXB provider you could use an external mapping file to provide an alternate mapping for your domain model (see: http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html).
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="forum13843624">
<java-types>
<java-type name="A">
<java-attributes>
<xml-value java-attribute="objectB"/>
</java-attributes>
</java-type>
<java-type name="B">
<java-attributes>
<xml-value java-attribute="hello"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
Java Model
A
import javax.xml.bind.annotation.*;
#XmlRootElement(name="A")
class A {
private B b;
#XmlElement(name="B")
public B getObjectB() {
return b;
}
public void setObjectB(B b) {
this.b = b;
}
}
B
import javax.xml.bind.annotation.XmlElement;
class B {
#XmlElement
public String getHello() {
return " world";
}
}
jaxb.properties
To specify MOXy as your JAXB provider you need to include a file named jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo Code
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
B b = new B();
A a = new A();
a.setObjectB(b);
JAXBContext jc = JAXBContext.newInstance(A.class);
marshal(jc, a);
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "forum13843624/oxm.xml");
JAXBContext jc2 = JAXBContext.newInstance(new Class[] {A.class}, properties);
marshal(jc2, a);
}
private static void marshal(JAXBContext jc, A a) throws Exception {
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(a, System.out);
}
}
Output
Below is the output from running the demo code. Note how the same object graph is marshalled two different ways.
<?xml version="1.0" encoding="UTF-8"?>
<A>
<B>
<hello> world</hello>
</B>
</A>
<?xml version="1.0" encoding="UTF-8"?>
<A> world</A>