views:

2973

answers:

5

Hi,

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.

+2  A: 

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/

silmx
+1 for pointing out the way jaxb does collections, but that plugin won't help. That's a plugin for the XJC code generatror,m and doesn't change JAXB's runtime behaviour.
skaffman
I read some old email thread which claimed that this behavior was being fixed in JaxB 2.1. I would expect that there is a property that on JaxbContext.newInstance(classes, *properties*) which control the marshaling behavior, but I can't find it.
Justin
A: 

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.

gibbss
A: 

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

LE GALL Benoît
A: 

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);
                             }
                         }
                 }
             }
         }
    }
Justin
A: 

You can just use an array instead of List )

alles