I need to marshall an object which includes a String variable.
The String var. contains an XML document, and it gets marshalled with escaping to XMLElement.
I'd like to marshall the String variable to base64 format, and back to String on unmarshall.
Is this posiible?
You can use an XmlAdapter to convert the String to/from a byte[] during the marshalling/unmarshalling process. By default JAXB will represent a byte[] as base64Binary.
XmlAdapter (Base64Adapter)
Below is an XmlAdapter that will convert between a String and a byte[].
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class Base64Adapter extends XmlAdapter<byte[], String> {
#Override
public String unmarshal(byte[] v) throws Exception {
return new String(v);
}
#Override
public byte[] marshal(String v) throws Exception {
return v.getBytes();
}
}
Java Model (Foo)
The XmlAdapter is configured using the #XmlJavaTypeAdapter annotation.
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Foo {
private String bar;
#XmlJavaTypeAdapter(Base64Adapter.class)
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Demo
In the demo code below we will create an instance of Foo and marshal it to XML.
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Foo foo = new Foo();
foo.setBar("<abc>Hello World</abc>");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
Output
Below is the output from running the demo code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo>
<bar>PGFiYz5IZWxsbyBXb3JsZDwvYWJjPg==</bar>
</foo>
Related
I am currently using JAXB to parse xml files. I generated the classes needed through an xsd file. However, the xml files I receive do not contain all the nodes declared in the generated classes. The following is an example of my xml file's structure:
<root>
<firstChild>12/12/2012</firstChild>
<secondChild>
<firstGrandChild>
<Id>
</name>
<characteristics>Description</characteristics>
<code>12345</code>
</Id>
</firstGrandChild>
</secondChild>
</root>
I am confronted with the following two cases :
The node <name> is present in the generated classes but not in the XML files
The node has no value
In both cases, the value is set to null. I would like to be able to differentiate when the node is absent from the XML file and when it's present but has a null value. Despite my searches, I didn't figure out a way to do so. Any help is more than welcome
Thank you so much in advance for your time and help
Regards
A JAXB (JSR-222) implementation won't call the set method for absent nodes. You could put logic in your set method to track whether or not it has been called.
public class Foo {
private String bar;
private boolean barSet = false;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
this.barSet = true;
}
}
UPDATE
JAXB will also treat empty nodes as having a value of empty String.
Java Model
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Root {
private String foo;
private String bar;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
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/forum15839276/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" standalone="yes"?>
<root>
<foo></foo>
</root>
I have a class, that is something like this:
public class Property {
private double floorArea;
public double getFloorArea() {
return floorArea;
}
#XmlElement
public void setFloorArea(double floorArea) {
this.floorArea = floorArea;
}
}
Which will give me something like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<property>
<floorArea>x</floorArea>
</property>
But I need something like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<property>
<floorArea>
<value>x</value>
</floorArea>
</property>
The API I am using requires it this way. My limited JAXB knowledge is preventing me from figuring this out. Any help is appreciated.
EDIT:
something I am researching. Would I need to create a value class with its own JAXB annotations for this to work? (and set floorArea to the type of value)?
Below is how your use case could be supported with an XmlAdapter using any JAXB (JSR-222) implementation.
XmlAdapter (DoubleValueAdapter)
An XmlAdapter is a mechanism that allows an object to be converted to another type of object . Then it is the converted object that is converted to/from XML.
package forum14045961;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DoubleValueAdapter extends XmlAdapter<DoubleValueAdapter.AdaptedDoubleValue, Double>{
public static class AdaptedDoubleValue {
public double value;
}
#Override
public AdaptedDoubleValue marshal(Double value) throws Exception {
AdaptedDoubleValue adaptedDoubleValue = new AdaptedDoubleValue();
adaptedDoubleValue.value = value;
return adaptedDoubleValue;
}
#Override
public Double unmarshal(AdaptedDoubleValue adaptedDoubleValue) throws Exception {
return adaptedDoubleValue.value;
}
}
Property
The #XmlJavaTypeAdapter annotation is used to specify the XmlAdapter. I needed to change double to Double so I moved the mapping to the field as to not affect the public API.
package forum14045961;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Property {
#XmlJavaTypeAdapter(DoubleValueAdapter.class)
private Double floorArea;
public double getFloorArea() {
return floorArea;
}
public void setFloorArea(double floorArea) {
this.floorArea = floorArea;
}
}
Demo
Below is some demo code to prove that everything works.
package forum14045961;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Property.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14045961/input.xml");
Property property = (Property) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(property, System.out);
}
}
input.xml/Output
Below is the input to and output from the demo code.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<property>
<floorArea>
<value>1.23</value>
</floorArea>
</property>
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Below is how you could map your use case using MOXy's #XmlPath extension:
Property
package forum14045961;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement
public class Property {
private double floorArea;
public double getFloorArea() {
return floorArea;
}
#XmlPath("floorArea/value/text()")
public void setFloorArea(double floorArea) {
this.floorArea = floorArea;
}
}
jaxb.properties
To specify MOXy as your JAXB (JSR-222) 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
Only the standard JAXB runtime APIs are required to read the objects from and write them back to XML when MOXy is used as the JAXB provider.
package forum14045961;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Property.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14045961/input.xml");
Property property = (Property) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(property, System.out);
}
}
input.xml/Output
Below is the input to and output from running the demo code.
<?xml version="1.0" encoding="UTF-8"?>
<property>
<floorArea>
<value>1.23</value>
</floorArea>
</property>
For More Information
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
Your hunch is correct the way described will generate the xml as you have displayed.
public class Property {
#XmlElement(required = true)
protected FloorArea floorArea;
public FloorArea getFloorArea() {
return floorArea;
}
public void setFloorArea(FloorArea value) {
this.floorArea = value;
}
}
And your FloorArea class would look something like the code snapshot below.
public class FloorArea {
#XmlElement(required = true)
protected String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
The goal is to produce the following XML with JAXB
<foo>
<bar>string data</bar>
<bar>binary data</bar>
</foo>
Is there a workaround to allow generic #XmlValue fields (I need to store byte[] and String data)? Below is what I desire:
#XmlRootElement
public class Foo {
private #XmlElement List<Bar> bars;
}
#XmlRootElement
public class Bar<T> {
private #XmlValue T value; // (*)
}
But I get this exception
(*) IllegalAnnotationException:
#XmlAttribute/#XmlValue need to reference a Java type that maps to text in XML.
You could leverage an XmlAdapter for this use case instead of #XmlValue:
BarAdapter
package forum8807296;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class BarAdapter extends XmlAdapter<Object, Bar<?>> {
#Override
public Bar<?> unmarshal(Object v) throws Exception {
if(null == v) {
return null;
}
Bar<Object> bar = new Bar<Object>();
bar.setValue(v);
return bar;
}
#Override
public Object marshal(Bar<?> v) throws Exception {
if(null == v) {
return null;
}
return v.getValue();
}
}
Foo
The XmlAdapter is associated with the bars property using the #XmlJavaTypeAdapter annotation:
package forum8807296;
import java.util.List;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Foo {
private List<Bar> bars;
#XmlElement(name="bar")
#XmlJavaTypeAdapter(BarAdapter.class)
public List<Bar> getBars() {
return bars;
}
public void setBars(List<Bar> bars) {
this.bars = bars;
}
}
Bar
package forum8807296;
public class Bar<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Demo
You can test this example using the following demo code:
package forum8807296;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Foo foo = new Foo();
List<Bar> bars = new ArrayList<Bar>();
foo.setBars(bars);
Bar<String> stringBar = new Bar<String>();
stringBar.setValue("string data");
bars.add(stringBar);
Bar<byte[]> binaryBar = new Bar<byte[]>();
binaryBar.setValue("binary data".getBytes());
bars.add(binaryBar);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
Output
Note how the output includes the xsi:type attributes to preserve the type of the value. You can eliminate the the xsi:type attribute by having your XmlAdapter return String instead of Object, if you do this you will need handle the conversion from String to the appropriate type yourself for the unmarshal operation:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo>
<bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">string data</bars>
<bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:base64Binary">YmluYXJ5IGRhdGE=</bars>
</foo>
I couldn't get #XmlValue working as I always got NullPointerException along the way—not sure why. I came up with something like the following instead.
Drop your Bar class entirely, because, as you want it to be able to contain anything you can simply represent it with Object.
#XmlRootElement(name = "foo", namespace = "http://test.com")
#XmlType(name = "Foo", namespace = "http://test.com")
public class Foo {
#XmlElement(name = "bar")
public List<Object> bars = new ArrayList<>();
public Foo() {}
}
Without telling JAXB which namespaces your types are using every bar element inside a foo would contain separate namespace declarations and stuff—the package-info.java and all the namespace stuff serves only fancification purposes only.
#XmlSchema(attributeFormDefault = XmlNsForm.QUALIFIED,
elementFormDefault = XmlNsForm.QUALIFIED,
namespace = "http://test.com",
xmlns = {
#XmlNs(namespaceURI = "http://test.com", prefix = ""),
#XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi"),
#XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema", prefix = "xs")})
package test;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
Running this simple test would spout-out something similar to your XML snippet.
public static void main(String[] args) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Foo.class);
Foo foo = new Foo();
foo.bars.add("a");
foo.bars.add("b".getBytes());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(foo, System.out);
}
Output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://test.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<bar xsi:type="xs:string">a</bar>
<bar xsi:type="xs:base64Binary">Yg==</bar>
</foo>
Is there a reason you don't simply construct a String with your byte[]? Do you truly need a generic?
The trick I'm usually using is to create schema with types you want and then use xjc to generate Java classes and see how annotations are used. :) I believe in XML schema proper type mapping for byte[] is 'base64Binary', so creating schema like this:
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/NewXMLSchema" xmlns:tns="http://www.example.org/NewXMLSchema" elementFormDefault="qualified">
<element name="aTest" type="base64Binary"></element>
</schema>
and running xjc we would get following code generated:
#XmlElementDecl(namespace = "http://www.example.org/NewXMLSchema", name = "aTest")
public JAXBElement<byte[]> createATest(byte[] value) {
return new JAXBElement<byte[]>(_ATest_QNAME, byte[].class, null, ((byte[]) value));
}
According to the documentation JAXB factory methods do not have arguments. Is there a JAXB implementation that allow me to create a factory method that receives as a parameter the class of the object I need to create ?
It happens that all my JAXB objects follow the same creation pattern (a particular byte code instrumentation), therefore I would like to encapsulate this in one single factory method having as a parameter the class of the JAXB object to create, avoiding in this way the creation of different factory methods for each JAXB class that basically do exactly the same thing.
I found someone asking the same question in an OTN forum: https://forums.oracle.com/forums/thread.jspa?messageID=9969927#9969927, but not a real answer has been proposed yet.
Thanks for any help
This is currently not possible using the standard JAXB APIs. I have entered the following enhancement request to have this behaviour added to EclipseLink JAXB (MOXy):
https://bugs.eclipse.org/363192
MOXy Specific Solution
You could leverage the #XmlCustomizer extension in EclipseLink JAXB (MOXy) to customize how the objects are instantiated. This mechanism is leveraged to tweak MOXy's underlying metadata.
CommonFactory
import java.util.Date;
public class CommonFactory {
public static Object create(Class<?> clazz) {
if(Foo.class == clazz) {
return new Foo(new Date());
} else if(Bar.class == clazz) {
return new Bar(new Date());
}
return null;
}
}
Foo.class
The Foo class is annotated normally except that we will use the #XmlCustomizer annotation to specify a DescriptorCustomizer that we are going to use to tweak MOXy's metadata.
import java.util.Date;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlCustomizer;
#XmlRootElement
#XmlType(factoryClass=CommonFactory.class, factoryMethod="create")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlCustomizer(FactoryCustomizer.class)
public class Foo {
private Date creationDate;
private Bar bar;
// Non-default constructor
public Foo(Date creationDate) {
this.creationDate = creationDate;
}
}
Bar
Again we will use the #XmlCustomizer annotation to reference the same DescriptorCustomizer that we did in the Foo class.
import java.util.Date;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlCustomizer;
#XmlType(factoryClass=CommonFactory.class, factoryMethod="create")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlCustomizer(FactoryCustomizer.class)
public class Bar {
private Date creationDate;
// Non-default constructor
public Bar(Date creationDate) {
this.creationDate = creationDate;
}
}
FactoryCustomizer
MOXy has the concept of an InstantiationPolicy to build new objects. In this example we will swap in our own instance InstantiationPolicy that can use parameterized factory methods:
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.descriptors.InstantiationPolicy;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.sessions.AbstractSession;
public class FactoryCustomizer implements DescriptorCustomizer{
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.setInstantiationPolicy(new MyInstantiationPolicy(descriptor));
}
private static class MyInstantiationPolicy extends InstantiationPolicy {
public MyInstantiationPolicy(ClassDescriptor descriptor) {
InstantiationPolicy defaultInstantiationPolicy = descriptor.getInstantiationPolicy();
this.factoryClassName = defaultInstantiationPolicy.getFactoryClassName();
this.factoryClass = defaultInstantiationPolicy.getFactoryClass();
this.methodName = defaultInstantiationPolicy.getMethodName();
}
#Override
public void initialize(AbstractSession session) throws DescriptorException {
super.initialize(session);
}
#Override
protected void initializeMethod() throws DescriptorException {
Class<?>[] methodParameterTypes = new Class[] {Class.class};
try {
this.method = PrivilegedAccessHelper.getMethod(factoryClass, methodName, methodParameterTypes, true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
#Override
public Object buildNewInstance() throws DescriptorException {
Object[] parameters = new Object[] {this.descriptor.getJavaClass()};
try {
return PrivilegedAccessHelper.invokeMethod(method, factory, parameters);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
Demo
import java.io.StringReader;
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();
Foo foo = (Foo) unmarshaller.unmarshal(new StringReader("<foo><bar/></foo>"));
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<creationDate>2011-11-08T12:35:43.198</creationDate>
<bar>
<creationDate>2011-11-08T12:35:43.198</creationDate>
</bar>
</foo>
For More Information
http://blog.bdoughan.com/2011/06/jaxb-and-factory-methods.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
JAXB runtime is failing to create JAXBContext for a Class whose member variable is defined as
#XmlElement(name = "EnumeraatioArvo")
private Enum<?> eenum;
How to handle such scenario in JAXB?
I agree with skaffman that this defeats the purpose on enums. If for some reason this is something you need to do, you could try the following:
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Root {
private Enum<?> eenum;
#XmlJavaTypeAdapter(EnumAdapter.class)
public Enum<?> getEenum() {
return eenum;
}
public void setEenum(Enum<?> eenum) {
this.eenum = eenum;
}
}
Below are two sample Enums we will use in this example:
public enum Compass {
NORTH, SOUTH, EAST, WEST
}
public enum Suit {
CLUBS, SPADES, DIAMONDS, HEARTS
}
We need to use an XmlAdapter on the eenum property. The XmlAdapter will need to know about all the possible types of Enums that are valid for this property.
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class EnumAdapter extends XmlAdapter<String, Enum> {
private Class[] enumClasses = {Compass.class, Suit.class};
#Override
public Enum unmarshal(String v) throws Exception {
for(Class enumClass : enumClasses) {
try {
return (Enum) Enum.valueOf(enumClass, v);
} catch(IllegalArgumentException e) {
}
}
return null;
}
#Override
public String marshal(Enum v) throws Exception {
return v.toString();
}
}
You can verify this works with the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<eenum>SPADES</eenum>
</root>
By using the following code:
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
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("input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}