I have defined the following ObjectFactory:
#XmlRegistry
public class ObjectFactory {
public Dogs createDogs() {
return new Dogs();
}
#XmlElementDecl(name = "dog")
public Dog createDog(DogType value) {
return new Dog(value);
}
#XmlElementDecl(name = "fido", substitutionHeadName = "dog", substitutionHeadNamespace = "")
public Dog createFido(DogType value) {
return new Dog("fido", value);
}
#XmlElementDecl(name = "barks", substitutionHeadName = "dog", substitutionHeadNamespace = "")
public Dog createBarks(DogType value) {
return new Dog("barks", value);
}
}
(Dogs class is trivial, Dog and DogType see below or here.)
I am unmarshalling the following XML:
<listOfDogs>
<dogs>
<dog>
<name>henry</name>
<sound>bark</sound>
</dog>
<fido>
<sound>woof</sound>
</fido>
<barks>
<sound>miau</sound>
</barks>
</dogs>
</listOfDogs>
I was sincerely expecting that JAXB will call my createFido(...) and createBarks(...) methods during unmarshalling. But this does not happen. The Dog constructor is invoked directly via reflection, the appropriate create... methods are not used.
My question is:
Why is ObjectFactory not called during unmarshalling?
Shouldn't it be? Or is ist just a dummy to hold the #XmlRegistry/#XmlElementDecl declarations?
I have also checked this question:
What is the ObjectFactory role during JAXB-Unmarshalling?
The solution there is to use #XmlType.factoryClass and factoryMethod. This will not work here because I don't want to statically link my DogType to the certain instantiation routine. I want it to be decided in the runtime based on the element name. My goal is to instantiate the same class but differently, depending on the element name.
Now some code to make it complete.
Root element class:
#XmlRootElement(name = "listOfDogs")
public class Dogs {
private List<JAXBElement<DogType>> dogs = new LinkedList<JAXBElement<DogType>>();
#XmlElementWrapper(name = "dogs")
#XmlElementRef(name = "dog")
public List<JAXBElement<DogType>> getDogs() {
return this.dogs;
}
#Override
public String toString() {
return "Dogs [dogs=" + dogs + "]";
}
}
Dog, the wrapper element class for the DogType:
public class Dog extends JAXBElement<DogType> {
public static final QName NAME = new QName("dog");
private static final long serialVersionUID = 1L;
public Dog(DogType value) {
super(NAME, DogType.class, value);
}
public Dog(String dogName, DogType value) {
super(NAME, DogType.class, value);
}
#Override
public QName getName() {
final DogType value = getValue();
if (value != null && value.getName() != null) {
return new QName(value.getName());
} else {
return super.getName();
}
}
}
DogType:
public class DogType {
private String name;
private String sound;
public String getName() {
return name;
}
public void setName(String dogName) {
this.name = dogName;
}
public String getSound() {
return sound;
}
public void setSound(String sound) {
this.sound = sound;
}
}
Test:
public class DogTest {
#Test
public void unmarshallsDogs() throws JAXBException {
final JAXBContext context = JAXBContext
.newInstance(ObjectFactory.class);
final Dogs dogs = (Dogs) context.createUnmarshaller().unmarshal(
getClass().getResource("dogs.xml"));
Assert.assertEquals(3, dogs.getDogs().size());
// Does not work
// Assert.assertEquals("henry", dogs.getDogs().get(0).getValue()
// .getName());
Assert.assertEquals("bark", dogs.getDogs().get(0).getValue().getSound());
// Does not work
// Assert.assertEquals("fido", dogs.getDogs().get(1).getValue()
// .getName());
Assert.assertEquals("woof", dogs.getDogs().get(1).getValue().getSound());
// Does not work
// Assert.assertEquals("barks", dogs.getDogs().get(2).getValue()
// .getName());
Assert.assertEquals("miau", dogs.getDogs().get(2).getValue().getSound());
}
}
The code is also available on GitHub here and here.
The short answer is because the factory methods are not generated into the #XmlType annotation to tell JAXB to do so:
#XmlRootElement(name = "listOfDogs")
#XmlType(factoryClass=ObjectFactory.class, factoryMethod="createDogs") // not generated
public class Dogs {
Shouldn't it be? Or is ist just a dummy to hold the
#XmlRegistry/#XmlElementDecl declarations?
In my opinion yes it should be used to instantiate the classes.
ObjectFactory is a throw back to JAXB 1.0. In JAXB 1.0 the spec defined what the generated interfaces looked like and implementations could back those generated interfaces with what ever impl they wanted to provide. Back then you needed to use the ObjectFactory class to create your model in a vendor independent way.
JAXB 2.0 switched to a POJO model where you were free to use the default constructor. If JAXB 1.0 had never existed would there be an ObjectFactory class, that's hard to tell. Since it previously existed the ObjectFactory class was kept for a couple of reasons:
It made it easier for people transitioning for people transitioning from JAXB 1.0 to interact with the generated model.
It provided a location to specify the multiple root elements for a class via #XmlElementDecl. The #XmlRegistry annotation is really just a marker annotation used to indicate the class that contains the #XmlElementDecl annotations without restricting it to a class called ObjectFactory.
Your Use Case
Your use case may be able to be achieved with an XmlAdapter, although its not clear to me what logic you are trying to have in the ObjectFactory.
XmlAdapter (DogAdapter)
Your custom logic goes on the XmlAdapter.
import javax.xml.bind.*;
import javax.xml.bind.annotation.adapters.*;
public class DogAdapter extends XmlAdapter<JAXBElement<DogType>, JAXBElement<DogType>> {
#Override
public JAXBElement<DogType> unmarshal(JAXBElement<DogType> v) throws Exception {
return new Dog(v.getName().getLocalPart(), v.getValue());
}
#Override
public JAXBElement<DogType> marshal(JAXBElement<DogType> v) throws Exception {
return v;
}
}
Dogs
The XmlAdapter is referenced from the #XmlJavaTypeAdapter annotation.
import java.util.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name = "listOfDogs")
public class Dogs {
private List<JAXBElement<DogType>> dogs = new LinkedList<JAXBElement<DogType>>();
#XmlElementWrapper(name = "dogs")
#XmlElementRef(name = "dog")
#XmlJavaTypeAdapter(DogAdapter.class)
public List<JAXBElement<DogType>> getDogs() {
return this.dogs;
}
#Override
public String toString() {
return "Dogs [dogs=" + dogs + "]";
}
}
ObjectFactory
ObjectFactory is now a dumb class that just holds the #XmlElementDecl annotations:
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
public Dogs createDogs() {
return new Dogs();
}
#XmlElementDecl(name = "dog")
public JAXBElement<DogType> createDog(DogType value) {
return new Dog(value);
}
#XmlElementDecl(name = "fido", substitutionHeadName = "dog", substitutionHeadNamespace = "")
public JAXBElement<DogType> createFido(DogType value) {
return new JAXBElement<DogType>(new QName("fido"), DogType.class, value);
}
#XmlElementDecl(name = "barks", substitutionHeadName = "dog", substitutionHeadNamespace = "")
public JAXBElement<DogType> createBarks(DogType value) {
return new JAXBElement<DogType>(new QName("barks"), DogType.class, value);
}
}
UPDATE
My question, however is more about the specification. According to the
spec, should the create* methods from the ObjectFactory be executed or
not?
In JAXB 2 there is no difference in a model created from scratch versus one generated from an XML Schema. As such you need to look to the spec at what it says about classes. According to what is reference below it comes down to no-arg constructor or a specified factory method.
From section 8.7.1.2 Mapping of the JAXB 2.2 (JSR-222) specification:
a class must have a public or protected no-arg constructor or a
factory method identified by {factoryClass(), factoryMethod()} unless
it is adapted using #XmlJavaTypeAdapter.
Related
I am using org.simpleframework.xml (http://simple.sourceforge.net/) to serialize Java Objects to XML.
What I would like to add is to add a comments area in the resulting XML, based on Annotations in the Java object.
So for example I would like to write some Java Object like:
#Root(name = "myclass")
public class MyClass {
#Element(required=true)
#Version(revision=1.1)
#Comment(text=This Element is new since, version 1.1, it is a MD5 encrypted value)
private String activateHash;
}
And the resulting xml would look like:
<myclass version="1.1">
<!-- This Element is new since, version 1.1, it is a MD5 encrypted value -->
<activateHash>129831923131s3jjs3s3jjk93jk1</activateHash>
</myclass>
There is an example in their docs on howto write a Visitor that will write a comments in the xml:
http://simple.sourceforge.net/download/stream/doc/tutorial/tutorial.php#intercept
However: How can I attach a Visitor to a Strategy at all?
And further the Visitor concept of simpleframework does not allow access to the raw parsing class.
In the Visitor there is only a method to overwrite:
public void write(Type type, NodeMap<OutputNode> node) { ... }
=> OutputNode does not give me a chance to read the Annotation of the Element that I am parsing. So how should one access the Annotations of the attribute.
Thanks!
Sebastian
Update as of 2012-11-05:
Answer by the author of org.simpleframework.xml:
This works
https://simple.svn.sourceforge.net/svnroot/simple/trunk/download/stream/src/test/java/org/simpleframework/xml/strategy/CommentTest.java
package org.simpleframework.xml.strategy;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.simpleframework.xml.Default;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.ValidationTestCase;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.stream.InputNode;
import org.simpleframework.xml.stream.NodeMap;
import org.simpleframework.xml.stream.OutputNode;
public class CommentTest extends ValidationTestCase {
#Retention(RetentionPolicy.RUNTIME)
private static #interface Comment {
public String value();
}
#Root
#Default
private static class CommentExample {
#Comment("This represents the name value")
private String name;
#Comment("This is a value to be used")
private String value;
#Comment("Yet another comment")
private Double price;
}
private static class CommentVisitor implements Visitor {
public void read(Type type, NodeMap<InputNode> node) throws Exception {}
public void write(Type type, NodeMap<OutputNode> node) throws Exception {
if(!node.getNode().isRoot()) {
Comment comment = type.getAnnotation(Comment.class);
if(comment != null) {
node.getNode().setComment(comment.value());
}
}
}
}
public void testComment() throws Exception {
Visitor visitor = new CommentVisitor();
Strategy strategy = new VisitorStrategy(visitor);
Persister persister = new Persister(strategy);
CommentExample example = new CommentExample();
example.name = "Some Name";
example.value = "A value to use";
example.price = 9.99;
persister.write(example, System.out);
}
}
Update as of 2012-11-01 20:16
this is the workaround that seems to get the desired effect - the necessary FieldHelper is described in (Get the value of a field, given the hierarchical path)
/**
* write according to this visitor
*/
public void write(Type type, NodeMap<OutputNode> node) {
OutputNode element = node.getNode();
Class ctype = type.getType();
String comment = ctype.getName();
if (!element.isRoot()) {
FieldHelper fh = new FieldHelper();
element.setComment(comment);
try {
if (type.getClass().getSimpleName().startsWith("Override")) {
type = (Type) fh.getFieldValue(type, "type");
}
if (type.getClass().getSimpleName().startsWith("Field")) {
Field field = (Field) fh.getFieldValue(type, "field");
System.out.println(field.getName());
Comment commentAnnotation = field.getAnnotation(Comment.class);
if (commentAnnotation != null) {
element.setComment(commentAnnotation.value());
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Here is how far I got with this. Unfortunately it does not work as expected. I have written an E-Mail to the author of the Simpleframwork for XML.
/**
* write according to this visitor
*/
public void write(Type type, NodeMap<OutputNode> node) {
OutputNode element = node.getNode();
Class ctype = type.getType();
String comment = ctype.getName();
if (!element.isRoot()) {
Comment commentAnnotation = type.getAnnotation(Comment.class);
if (commentAnnotation!=null)
element.setComment(commentAnnotation.value());
else
element.setComment(comment);
}
}
#Override
public void read(Type type, NodeMap<InputNode> nodeMap) throws Exception {
}
}
I declared the Comment annotation like this:
package com.bitplan.storage.simplexml;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Comment {
String value();
}
which is then usable like this:
#Comment("this is the unique identifier")
private long id;
adding the Visitor was possible like this:
/**
* get Serializer
*
* #return
*/
public Serializer getSerializer() {
Serializer serializer = null;
Strategy strategy=null;
VisitorStrategy vstrategy=null;
if ((idname != null) && (refname != null)) {
strategy = new CycleStrategy(idname, refname);
}
CommentVisitor cv=new CommentVisitor();
if (strategy==null) {
vstrategy=new VisitorStrategy(cv);
} else {
vstrategy=new VisitorStrategy(cv,strategy);
}
serializer = new Persister(vstrategy);
return serializer;
}
I have these classes:
#XStreamAlias("person")
public class PersonConfig {
private AnimalConfig animalConfig;
}
public interface AnimalConfig {}
#XStreamAlias("dog");
public class DogConfig extend AnimalConfig {}
#XStreamAlias("cat");
public class CatConfig extend AnimalConfig {}
And I would like to be able to deserialize this xml with the classes above:
<person>
<dog/>
<person>
As well as deserialize this xml too, with the same classes:
<person>
<cat/>
<person>
So that in both cases, the PersonConfig's field animalConfig is filled. In the first XML with a DogConfig instance and in the second XML with a CatConfig instance.
Is this possible by adding some annotation to make this work?
It seems XStream does not allow you to do it easily.
Your question is similar to this one, asking for managing something like a xsd:choice with XStream.
If you don't necessarily need to use XStream, JAXB will allow you to do it easily :
#XmlRootElement(name="person")
public class PersonConfig {
private AnimalConfig animalConfig;
#XmlElementRefs({
#XmlElementRef(name="cat", type=CatConfig.class),
#XmlElementRef(name="dog", type=DogConfig.class)
})
public AnimalConfig getAnimalConfig() {
return animalConfig;
}
public void setAnimalConfig(AnimalConfig animalConfig) {
this.animalConfig = animalConfig;
}
}
After some researches, listing all available classes for your property can be avoided if you choose to use the XmlAdapter.
In Blaise Doughan link, the example uses an abstract class, not an interface.
Edit :
As Blaise Doughan said in its comment, #XmlElementRef is better suited for this purpose. Code has been updated accordingly.
You can write a converter.
public class CustomConverter implements Converter {
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
// TODO: Get annotation value from object 'source' with name of tag via Reflection.
// Or add a method to the AnimalConfig interface giving you tag name to put to serialization output.
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
// TODO: use reflection to create animal object based on what you xml tag you have at hahd.
return context.convertAnother(context.currentObject(), SomeAnimalClazz.class);
}
public boolean canConvert(Class type) {
return type.equals(AnimalConfig.class);
}
}
There's a disadvantage: polymorphism will require you to use Java Reflection API and performance degradation.
This is quite easy. You just have to do it right and not like my previous speakers. When you process the annotations, XStream can assign those classes.
#XStreamAlias("person")
public class PersonConfig {
private AnimalConfig animalConfig;
public String toXml() {
XStream xstream = new XStream();
xstream.processAnnotations(DogConfig.class);
xstream.processAnnotations(CatConfig.class);
return xstream.toXML(this);
}
}
public interface AnimalConfig {}
#XStreamAlias("dog");
public class DogConfig implements AnimalConfig {}
#XStreamAlias("cat");
public class CatConfig implements AnimalConfig {}
It works out of the box, with out any annotations...
private static interface Test {
String getName();
Params getParams();
}
private static interface Params {
}
private static class OneParams implements Params {
private String oneValue;
public String getOneValue() {
return oneValue;
}
public void setOneValue(String oneValue) {
this.oneValue = oneValue;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("OneParams [oneValue=");
builder.append(oneValue);
builder.append("]");
return builder.toString();
}
}
private static class TwoParams implements Params {
private String twoValue;
public String getTwoValue() {
return twoValue;
}
public void setTwoValue(String twoValue) {
this.twoValue = twoValue;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("TwoParams [twoValue=");
builder.append(twoValue);
builder.append("]");
return builder.toString();
}
}
private static class OneTest implements Test {
private String name;
private Params params;
#Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public Params getParams() {
return params;
}
public void setParams(Params params) {
this.params = params;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("OneTest [name=");
builder.append(name);
builder.append(", params=");
builder.append(params);
builder.append("]");
return builder.toString();
}
}
---- now deserialize like this...
System.out
.println(ser
.deserialize("<XStreamTest_-OneTest><name>OneTest</name><params class=\"XStreamTest$OneParams\"><oneValue>1</oneValue></params></XStreamTest_-OneTest>"));
System.out
.println(ser
.deserialize("<XStreamTest_-OneTest><name>TwoTest</name><params class=\"XStreamTest$TwoParams\"><twoValue>2</twoValue></params></XStreamTest_-OneTest>"));
I am refactoring some code to use JAXB and reflection to output code to the client, it is currently using an XMLWriter and manually creating the tags each time.
The problem I am having is that due to constraints on the client side, I need to have empty elements in the XML for any null fields in the java class.
While I realize this problem can be solved by adding nillable=true to each JAXB XmlElement annotation, that is not the most practical, as I have a lot of those annotations.
I was hoping to find a way to set nillable=true as a global attribute (or as the default value). This would also make it easier for future colleagues to work on it, as they won't need to remember that every annotation should include the nillable attribute.
I haven't found much besides descriptions of the default behavior. I find it surprising that no one else has posted a similar question in the past. From what I have found, it doesn't seem to me that there is any built-in support for making the default configurable. Is this something that might be solved with a custom JAXB implementation or maybe a third party JAXB implementation?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
I have entered an enhancement request to have this behaviour added to EclipseLink JAXB (MOXy):
http://bugs.eclipse.org/368547
WORK AROUND
As a work around if all your mapped String fields/properties are mapped to XML elements then the following XmlAdapter approach may work for you:
NullStringAdapter
This XmlAdapter will marshal instances of String as an object called AdaptedString. AdaptedString contains the String value as well as a field mapped to the xsi:nil attribute. In the XmlAdapter we will set the value of that field based on whether or not the String value is null.
package forum8841221;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;
public class NullStringAdapter extends XmlAdapter<NullStringAdapter.AdaptedString, String> {
#Override
public AdaptedString marshal(String v) throws Exception {
AdaptedString adaptedString = new AdaptedString();
if(null == v) {
adaptedString.nil = true;
}
adaptedString.value = v;
return adaptedString;
}
#Override
public String unmarshal(AdaptedString v) throws Exception {
return v.value;
}
public static class AdaptedString {
#XmlAttribute(namespace="http://www.w3.org/2001/XMLSchema-instance")
public Boolean nil;
#XmlValue
#XmlJavaTypeAdapter(VoidStringAdapter.class)
public String value;
}
public static class VoidStringAdapter extends XmlAdapter<String, String> {
#Override
public String marshal(String v) throws Exception {
return v;
}
#Override
public String unmarshal(String v) throws Exception {
return v;
}
}
}
package-info
We can register that we want this XmlAdapter to apply to all the mapped String fields/properties on this package by registering the XmlAdapter at the package level.
#XmlJavaTypeAdapter(value=NullStringAdapter.class, type=String.class)
#XmlSchema(xmlns={#XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi")})
package forum8841221;
import javax.xml.bind.annotation.adapters.*;
import javax.xml.bind.annotation.*;
Root
Below is the domain class I have used for this example. It has several String properties, one of them is annotated with #XmlElement(nillable=true)
package forum8841221;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Root {
private String a;
private String b;
private String c;
private String d;
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
#XmlElement(nillable=true)
public String getD() {
return d;
}
public void setD(String d) {
this.d = d;
}
}
Demo
package forum8841221;
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(Root.class);
Root root = new Root();
root.setB("B");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<a xsi:nil="true"/>
<b>B</b>
<c xsi:nil="true"/>
<d xsi:nil="true"/>
</root>
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
I am using the Jackson ObjectMapper to deserialize some JSON into a Java class, which we'll call PlayerData. I would like to add a bit of logic to the PlayerData class to fix up some data after the fields have been loaded in. For example, some early JSON files used to use a "sex" flag instead of a "gender" falg, so if the sex flag is set but the gender flag is not set, I'd like to set the value of the gender field to be the value of the sex field.
Is there some sort of #PostConstruct or #AfterLoad annotation that I could affix to a method? Or perhaps an interface that I could implement? I didn't notice one in the documentation, but it seemed like an obvious feature.
Found this thru a link in the comments (credit: fedor.belov). This appears to allow you to run code post construct.
Adding a comment for people who end up here via
http://jira.codehaus.org/browse/JACKSON-645 or
http://jira.codehaus.org/browse/JACKSON-538 and are looking for a
method which is called after a deserializer completes. I was able to
achieve the desired effect by including an annotation and writing a
converter which uses the same class as input and output.
#JsonDeserialize(converter=MyClassSanitizer.class) // invoked after class is fully deserialized
public class MyClass {
public String field1;
}
import com.fasterxml.jackson.databind.util.StdConverter;
public class MyClassSanitizer extends StdConverter<MyClass,MyClass> {
#Override
public MyClass convert(MyClass var1) {
var1.field1 = munge(var1.field1);
return var1;
}
}
If you're not using the #JsonCreator, then Jackson will use the setter and getter methods to set the fields.
So if you define the following methods assuming that you have Sex and Gender enums:
#JsonProperty("sex")
public void setSex(final Sex sex) {
this.sex = sex;
if (gender == null) {
gender = (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
}
}
#JsonProperty("gender")
public void setGender(final Gender gender) {
this.gender = gender;
if (sex == null) {
sex = (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
}
}
it would work.
Update: You can find all of the annotations of Jackson library here.
Update2: Other solution:
class Example {
private final Sex sex;
private final Gender gender;
#JsonCreator
public Example(#JsonProperty("sex") final Sex sex) {
super();
this.sex = sex;
this.gender = getGenderBySex(sex)
}
#JsonFactory
public static Example createExample(#JsonProperty("gender") final Gender gender) {
return new Example(getSexByGender(gender));
}
private static Sex getSexByGender(final Gender) {
return (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
}
private static Gender getGenderBySex(final Sex) {
return (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
}
}
This is not supported out of the box, but you can easily create your #JsonPostDeserialize annotation for methods to be called after deserialization.
First, define the annotation:
/**
* Annotation for methods to be called directly after deserialization of the object.
*/
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonPostDeserialize {
}
Then, add the following registration and implementation code to your project:
public static void addPostDeserializeSupport(ObjectMapper objectMapper) {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDescription,
JsonDeserializer<?> originalDeserializer) {
return new CustomAnnotationsDeserializer(originalDeserializer, beanDescription);
}
});
objectMapper.registerModule(module);
}
/**
* Class implementing the functionality of the {#link JsonPostDeserialize} annotation.
*/
public class CustomAnnotationsDeserializer extends DelegatingDeserializer {
private final BeanDescription beanDescription;
public CustomAnnotationsDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
super(delegate);
this.beanDescription = beanDescription;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new CustomAnnotationsDeserializer(newDelegatee, beanDescription);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Object deserializedObject = super.deserialize(p, ctxt);
callPostDeserializeMethods(deserializedObject);
return deserializedObject;
}
private void callPostDeserializeMethods(Object deserializedObject) {
for (AnnotatedMethod method : beanDescription.getClassInfo().memberMethods()) {
if (method.hasAnnotation(JsonPostDeserialize.class)) {
try {
method.callOn(deserializedObject);
} catch (Exception e) {
throw new RuntimeException("Failed to call #JsonPostDeserialize annotated method in class "
+ beanDescription.getClassInfo().getName(), e);
}
}
}
}
}
Finally, modify your ObjectMapper instance with addPostDeserializeSupport, it will invoke all #JsonPostDeserialize annotated method of deserialized objects.
This is something that has actually been suggested couple of times earlier. So maybe filing an RFE would make sense; there are multiple ways in which this could work: obvious ones being ability to annotate type (#JsonPostProcess(Processor.class)) and ability to register post-processor through Module API (so that there's basically a callback when Jackson constructs deserializer, to let module specify post-processor to use if any). But perhaps there are even better ways to do this.