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.
jaxb package. An ObjectFactory allows you to programatically construct new instances of the Java representation for XML content. The Java representation of XML content can consist of schema derived interfaces and classes representing the binding of schema type definitions, element declarations and model groups.
JAXB definitionsMarshalling is the process of transforming Java objects into XML documents. Unmarshalling is the process of reading XML documents into Java objects. The JAXBContext class provides the client's entry point to the JAXB API. It provides API for marshalling, unmarshalling and validating.
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:
@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 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);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With