Add/Override behavior on Jaxb generated classes by extending them - jaxb

I have a web server responding with xml data and a client consuming it.
Both share the same domain code. One of the domain objects looks like this:
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
#XmlRootElement(name = "image")
public class Image {
private String filename;
private ImageTypeEnum type;
#XmlElement(name = "imageUri")
public String getAbsoluteUri() {
// some complex computation
return uri;
}
}
When I try to unmarshal the response from the server into this object, since there's no setter for absoluteUri, I don't have the imageUri in the class. So I extend it like this:
public class FEImage extends Image{
private String imageUri;
public String getAbsoluteUri() {
return imageUri;
}
public void setAbsoluteUri(String imageUri) {
this.imageUri = imageUri;
}
}
My ObjectFactory
#XmlRegistry
public class ObjectFactory {
public Image createImage(){
return new FEImage();
}
}
My code to unmarshal is here:
JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty("com.sun.xml.bind.ObjectFactory",new ObjectFactory());
((JAXBElement)unmarshaller.unmarshal((InputStream) response.getEntity())).getValue();
However, the setAbsoluteUri doesn't seem to be getting called in FEImage while unmarshalling. When I add a dummy setAbsoluteUri in Image.java, everything works as expected.
Can someone tell me how can I cleanly extend from Image.java?

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
A JAXB implementation is not required to use the ObjectFactory class when instantiating an object. You can configure instantiation to be done via a factory class using the #XmlType annotation:
#XmlType(factoryClass=ObjectFactory.class, factoryMethod="createImage")
public class Image {
private String filename;
private ImageTypeEnum type;
#XmlElement(name = "imageUri")
public String getAbsoluteUri() {
// some complex computation
return uri;
}
}
http://blog.bdoughan.com/2011/06/jaxb-and-factory-methods.html
If you do the above, then your JAXB implementation will still use the Image class to derive the metadata so it will not solve your problem. An alternate approach would be to use an XmlAdapter for this use case:
http://blog.bdoughan.com/2010/12/jaxb-and-immutable-objects.html
Better still, when a property on your domain object does not have a setter, you can tell your
JAXB implementation (EclipseLink MOXy, Metro, Apache JaxMe, etc) to use field (instance variable) access instead using #XmlAccessorType(XmlAccessType.FIELD):
#XmlAccessorType(XmlAccessType.FIELD)
public class Image {
}
http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
UPDATE #1
If you are not able to modify the domain objects, then you may be interested in MOXy's externalized metadata. This extension provides a means via XML to provide JAXB metadata for classes where you cannot modify the source.
For More Information
http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html
http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy/Runtime/XML_Bindings
UPDATE #2 - Based on results of chat
Image
Below is the implementation of the Image class that I will use for this example. For the complex computation of getAbsoluteUri() I simply add the prefix "CDN" to the filename:
package forum7552310;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
#XmlRootElement(name = "image")
public class Image {
private String filename;
private ImageTypeEnum type;
#XmlElement(name = "imageUri")
public String getAbsoluteUri() {
return "CDN" + filename;
}
}
binding.xml
Below is the MOXy binding document I put together. In this file I do a few things:
Set XmlAccessorType to FIELD
Mark the absoluteURI property to be XmlTransient since we will be mapping the filename field instead.
Specify that an XmlAdapter will be used with the filename field. This is to apply the logic that is done in the getAbsoluteUri() method.
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="forum7552310">
<java-types>
<java-type name="Image" xml-accessor-type="FIELD">
<java-attributes>
<xml-element java-attribute="filename" name="imageUri">
<xml-java-type-adapter value="forum7552310.FileNameAdapter"/>
</xml-element>
<xml-transient java-attribute="absoluteUri"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
FileNameAdapter
Below is the implementation of the XmlAdapter that applies the same name algorithm as the getAbsoluteUri() method:
package forum7552310;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class FileNameAdapter extends XmlAdapter<String, String> {
#Override
public String marshal(String string) throws Exception {
return "CDN" + string;
}
#Override
public String unmarshal(String adaptedString) throws Exception {
return adaptedString.substring(3);
}
}
Demo
Below is the demo code demonstrating how to apply the binding file when creating the JAXBContext:
package forum7552310;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum7552310/binding.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Image.class}, properties);
File xml = new File("src/forum7552310/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
Image image = (Image) unmarshaller.unmarshal(xml);
System.out.println(image.getAbsoluteUri());
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(image, System.out);
}
}
jaxb.properties
You need to include a file named jaxb.properties with the following contents in the same package as your Image class:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
input.xml
Here is the XML input I used:
<?xml version="1.0" encoding="UTF-8"?>
<image>
<imageUri>CDNURI</imageUri>
</image>
Output
And here is the output from running the demo code:
CDNURI
<?xml version="1.0" encoding="UTF-8"?>
<image>
<imageUri>CDNURI</imageUri>
</image>

Related

JAX WS, JAXB and Null elements with attributes

I am trying to consume a web service using JAXWS and wsimport. The WSIMPORT tool generated all the required classes and I can invoke the service without any issues.
However, I noticed in cases where response contains a nil element with valid attribute values, JAXWS fails to unmarshall it and throws a NullPointerException. I used SOAP UI to help debug and here's what I found. The response returns the following XML (Excerpt):
<externalIdentifiers>
<identifierType code="2" name="Passport" xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<identifierValue/>
<issuingCountry xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</externalIdentifiers>
In my Java code, when trying to read the "name" property of identifier type as above, it throws a NPE:
if(id.getIdentifierType() == null)
{
System.out.println("NULL");
}
System.out.println("Identifier Type: " + id.getIdentifierType().getName());
Output:
NULL
Exception in thread "main" java.lang.NullPointerException
To me that does looks a reasonable response as in the response, identifierType is set as xsi:nil="true". That is also perfectly valid XML as per W3C. Question is, how do I read the attribute values such as code and name in such a case?
Below is how you can support this use case:
Java Model
ExternalIdentifiers
You can change the identifierType property to be of type JAXBElement<IdentifierType> instead of IdentifierType. To do this you will need to annotate the property with #XmlElementRef.
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ExternalIdentifiers {
#XmlElementRef(name="identifierType")
private JAXBElement<IdentifierType> identifierType;
public JAXBElement<IdentifierType> getIdentifierType() {
return identifierType;
}
}
ObjectFactory
You will need a corresponding #XmlElementDecl annotation on a create method in a class annotated with #XmlRegistry.
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
#XmlElementDecl(name="identifierType")
public JAXBElement<IdentifierType> createIdentifierType(IdentifierType identifierType) {
return new JAXBElement(new QName("identifierType"), IdentifierType.class, identifierType);
}
}
Demo Code
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<externalIdentifiers>
<identifierType code="2" name="Passport" xsi:nil="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
</externalIdentifiers>
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(ExternalIdentifiers.class, ObjectFactory.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum18834036/input.xml");
ExternalIdentifiers externalIdentifiers = (ExternalIdentifiers) unmarshaller.unmarshal(xml);
System.out.println(externalIdentifiers.getIdentifierType().getValue().getName());
}
}
Output
Passport
Note
Currently there is a bug in EclipseLink JAXB (MOXy) regarding this use case:
http://bugs.eclipse.org/404944

JAXB using MOXy - how to override property serialization behavior?

I have two classes
class First{
private Date date;
public Date getDate(){
return date;
}
...
}
and
class Second extends First{
#XmlAttribute
#XmlJavaTypeAdapter(value = DateAdapter.class, type = Date.class)
public Date getDate() {
return super.getDate();
}
}
Where DateAdapter just translates Date to Long and back.
I'm serializing an instance of the Second class and it seems to be that DateAdapter is ignored. I mean I get string "2013-05-22T13:32:40.664" instead of its Long representation.
If I'll move the #XmlJavaTypeAdapter annotation to the First class, it works OK, but my problem is that First can't be modified and that is basically the reason I created the wrapper class Second.
How can I make XmlJavaTypeAdapter be recognized?
You can use EclipseLink JAXB (MOXy)'s external binding file to provide metadata for classes that cannot be modified:
oxm.xml
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="com.example.foo">
<java-types>
<java-type name="First">
<java-attributes>
<xml-element java-attribute="date">
<xml-java-type-adapter value="com.example.foo.DateAdapter"/>
</xml-element>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
Demo
Then you can bootstrap your JAXBContext using a property specifying the metadata location:
import java.util.*;
import javax.xml.bind.JAXBContext;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "com/example/foo/oxm.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {First.class, Second.class}, properties);
}
}
For More Information
http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html

unmarshalling into single class using eclipselink

i have created a Company class that does produce xml like below using marshalling :
<?xml version="1.0" encoding="UTF-8"?>
<ns2:company xmlns:ns2="http://www.example.com/">
<ns2:employee>
<job>sogi</job>
<name>togi</name>
<age>22</age>
</ns2:employee>
</ns2:company>
Note:I used #XmlPath("employee/job/text()") tag in Company class to get the required path.
but when unmarshalling i use the same Company class,i do not get the correct object values.Instead i get null values.
You need to include namespace information in the #XmlPath annotation.
package-info
Since your XML document has namespace qualification, you will need to leverage the package level #XmlSchema annotation to specify the namespace information.
#XmlSchema(
namespace="http://www.example.com/",
xmlns={
#XmlNs(namespaceURI = "http://www.example.com/", prefix = "foo")
}
)
package forum14848450;
import javax.xml.bind.annotation.*;
Company
In the #XmlPath mapping for the fragments of the XmlPath that are namespace qualified you need to leverage the prefixes you defined on the #XmlSchema annotation.
package forum14848450;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement
public class Company {
#XmlPath("foo:employee/job/text()")
private String employeeJob;
#XmlPath("foo:employee/name/text()")
private String employeeName;
#XmlPath("foo:employee/age/text()")
private int employeeAge;
}
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
The demo code below will unmarshal the document from your question, and then marshal it back to XML.
package forum14848450;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Company.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14848450/input.xml");
Company company = (Company) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(company, System.out);
}
}
Output
Below is the output from running the demo code. Note how the output document uses the prefixes defined in the #XmlPath annotation.
<?xml version="1.0" encoding="UTF-8"?>
<foo:company xmlns:foo="http://www.example.com/">
<foo:employee>
<job>sogi</job>
<name>togi</name>
<age>22</age>
</foo:employee>
</foo:company>
For More Information
http://blog.bdoughan.com/2010/09/xpath-based-mapping-geocode-example.html

Marshalling collections when using XmlElementWrapper

I have a collection on my class that uses #XmlElementWrapper to wrap the collection in a extra element.
So, my class looks something like this:
class A {
#XmlElement(name = "bee")
#XmlElementWrapper
public List<B> bees;
}
And my XML then looks something like:
<a>
<bees>
<bee>...</bee>
<bee>...</bee>
</bees>
</a>
Great, this is what I wanted. However, when I try and marshall into JSON, I get this:
{
"bees": {
"bee": [
....
]
}
}
And I don't want that extra "bee" key there.
Is it possible to somehow have MOXy ignore the XmlElement part when doing this marshalling? because I still need the name to be "bees" and not "bee", and I don't want both.
I'm using MOXy 2.4.1 and javax.persistence 2.0.0.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
oxm.xml
You could use MOXy's external mapping document to provide an alternate mapping for your JSON-binding (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="forum14002508">
<java-types>
<java-type name="A">
<java-attributes>
<xml-element java-attribute="bees" />
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
Demo
In the demo code below we will create two instances of JAXBContext. The first is build solely on the JAXB annotations that we will use for XML. The second is built on the JAXB annotations and uses MOXy's external mapping file to override the mapping for the bees property on the A class.
package forum14002508;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
List<B> bees = new ArrayList<B>();
bees.add(new B());
bees.add(new B());
A a = new A();
a.bees = bees;
JAXBContext jc1 = JAXBContext.newInstance(A.class);
Marshaller marshaller1 = jc1.createMarshaller();
marshaller1.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller1.marshal(a, System.out);
Map<String, Object> properties = new HashMap<String, Object>(3);
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "forum14002508/oxm.xml");
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc2 = JAXBContext.newInstance(new Class[] {A.class}, properties);
Marshaller marshaller2 = jc2.createMarshaller();
marshaller2.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller2.marshal(a, System.out);
}
}
Output
Below is the output from running the demo code that matches your use case.
<a>
<bees>
<bee/>
<bee/>
</bees>
</a>
{
"bees" : [ {
}, {
} ]
}
For More Information
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html

How do I use JAXB #XmlValue on a subclass?

I want XML like this:
<simple>Foo</simple>
I can do this successfully via a JAXB class that looks like this:
#XmlRootElement(name="simple")
class Simple {
#XmlValue
public String contents;
}
But now I need to make the Simple class be a subclass of another class like so:
#XmlRootElement(name="simple")
class Simple extends OtherClass {
#XmlValue
public String contents;
}
That fails with #XmlValue is not allowed on a class that derives another class. I can't easily refactor the superclass away (because of the way we're using #XmlElementRef on a wrapper class). Is there a workaround that will let me annotate my subclass to generate that simple XML?
The accepted answer didn't work for me.
Everything is fine as described but I also needed to add the #XmlTransient to the superclass
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
This use case is supported by MOXy, and IMHO should be supported by the JAXB RI as well:
Simple
This class has a field mapped with #XmlValue and extends OtherClass:
package forum809827;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
#XmlRootElement(name="simple")
class Simple extends OtherClass {
#XmlValue
// #XmlValueExtension
// As of moxy 2.6, XmlValueExtension needs to be added for this to work
public String contents;
}
OtherClass
This is the super class. In MOXy the subclass can map a field/property with #XmlValue as long as the super class does not have any mappings to an XML element:
package forum809827;
import javax.xml.bind.annotation.XmlAttribute;
public class OtherClass {
#XmlAttribute
public String other;
}
Demo
package forum809827;
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(Simple.class);
Simple simple = new Simple();
simple.contents = "FOO";
simple.other = "BAR";
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(simple, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<simple xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" other="BAR">FOO</simple>
For More Information on Specifying MOXy as Your JAXB Provider
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
I was able to make this work by changing #XmlValue to #XmlMixed and changing the variable to a list. The resulting class should look like the following.
#XmlRootElement(name="simple")
class Simple extends OtherClass {
#XmlMixed
public List<String> contents;
}
This problem happened to me , and took me a little bit time.
Thanks to Blaise Doughan
I go through his blog and find the answer
you have to add a
jaxb.properties file with javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
in the same package in order to use MOXy
add moxy to your maven dependency or add moxy jar
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.moxy</artifactId>
<version>2.5.0</version>
</dependency>
then all set
I have sample here you can go though my project and take a look at
https://github.com/cicidi/HelloCCD/tree/master/Jaxb

Resources