Reputation: 1059
suppose I have this class:
public class A {
private HashMap<String, B> map;
@XmlElement
private void setB(ArrayList<B> col) {
...
}
private ArrayList<B> getB() {
...
}
}
When trying to unmarshall an xml document to this class using JaxB I notice that instead of calling the setB() method and sending me the list of B instances JaxB actually calls the getB() and adds the B instances to the returned list. Why?
The reason I want the setter to be called is that the list is actually just a temporary storage from which I want to build the map field, so I thought to do it in the setter.
Thanks.
Upvotes: 14
Views: 20195
Reputation: 59566
The reason I want the setter to be called is that the list is actually
just a temporary storage from which I want to build the map field,
so I thought to do it in the setter.
JAXB can handle maps directly, hence, this could make the call to setB() a moot point. If that is an acceptable solution for you, see the example I maintain on my blog to create an adaptor for maps in JAXB.
Upvotes: 0
Reputation: 148977
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
The behaviour you are seeing will vary among JAXB implementations. If you do not initialize a value for the List
property then EclipseLink JAXB (MOXy) will call the set method as you expect.
For More Information
EXAMPLE
A
package forum1032152;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class A {
private ArrayList<B> b;
@XmlElement
public void setB(ArrayList<B> col) {
System.out.println("Called setB");
for(B b : col) {
System.out.println(b);
}
this.b = col;
}
public ArrayList<B> getB() {
return b;
}
}
B
package forum1032152;
public class B {
}
Demo
package forum1032152;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(A.class);
File xml = new File("src/forum1032152/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.unmarshal(xml);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<a>
<b></b>
<b></b>
</a>
Output
Called setB
forum1032152.B@8bdcd2
forum1032152.B@4e79f1
Upvotes: 5
Reputation: 7568
Hy,
you can use it with jaxb, it's work !!! (with Maven....)
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<args>
<arg>-Xcollection-setter-injector</arg>
</args>
<plugins>
<plugin>
<groupId>net.java.dev.vcc.thirdparty</groupId>
<artifactId>collection-setter-injector</artifactId>
<version>0.5.0-1</version>
</plugin>
</plugins>
<schemaDirectory>src/schemas</schemaDirectory>
<generateDirectory>src/main/java</generateDirectory>
<extension>true</extension>
</configuration>
</plugin>
and you get your setter for your Collection
Hope it would help people
bye
Upvotes: 6
Reputation: 4477
Jaxb2 UnMarshaller defines a listener interface which is called any time an object has been un-marshaled. You can define a custom listener to invoke setter methods on all collections (or on sub-objects). This should be pretty easy to do with any one of the bean utils classes. I'm looking for an existing implementation, though I don't see one.
JAXBContext context = JAXBContext.newInstance( classesToBeBound );
m_unmarshaller = context.createUnmarshaller();
m_unmarshaller.setListener(
new Unmarshaller.Listener() {
public void afterUnmarshal(Object target, Object parent) {
for (Property p : getBeanProperties(target.getClass()))
if (p.isCollectionType() || p.isCompositeType())
p.invokeSetter(p.invokeGetter());
}
});
If you are using the spring framework, its pretty straightforward:
new Unmarshaller.Listener() {
public void afterUnmarshal(Object target, Object parent) {
BeanWrapper wrapper = new BeanWrapperImpl(target);
for (PropertyDescriptor pd : wrapper.getPropertyDescriptors()) {
if (pd.getPropertyType() != null) {
if (!BeanUtils.isSimpleProperty(pd.getPropertyType())) {
try {
Method setter = pd.getWriteMethod();
if (setter != null) {
Method getter = pd.getReadMethod();
if (getter != null)
setter.invoke(target, getter.invoke(target));
}
}
catch (Exception ex) {
s_logger.error("can't invoke setter", ex);
}
}
}
}
}
}
Upvotes: 1
Reputation: 2033
JAXB has problems supporting interfaces and abstract classes; it usually doesn't know what subclass to instantiate. The problem is, it's a common pattern to have a class along the lines of:
ArrayList list;
@XMLElement
public List getList() {return this.list;}
To get around this, JAXB doesn't even try to instantiate the property class (e.g. List) derived from the getter/setter pair if it's a Collection. It just assumes that it's non-null and modifiable.
Probably the simplest work around is to mark your business interface with @XMLTransient and add a different getter/setter pair with @XMLElement for the view for the data that you want to expose to JAXB. I usually make these protected rather than public, because I don't care to have the somewhat-goofy JAXB behavior as part of my classes' public contract.
Upvotes: 3
Reputation: 486
thats the way jaxb is handling collections. you have to be sure you have a non null collection when jaxb try to unmarshal.
there is a plugin (never used it myself) but can be helpful: https://jaxb2-commons.dev.java.net/collection-setter-injector/
Upvotes: 8