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.
See Question&Answers more detail:
os