JAXB afterUnmarshall not called when using extended class

java, jaxb, moxy, unmarshalling, xml

Solution

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.

The reason that `afterUnmarshal` is not being called on `BaseBeanEx` is that the metadata was built on the `BaseBean` class. To get your use case to work you need to let your JAXB impl know that you really want to map to instances of `BaseBeanEx`.

OPTION #1 - Any JAXB Implementation using Annotations

Root

You can use the `@XmlElement` annotation to override the type of a field/property. In the example below the signature of the method is `List<BaseBean>`, but the `@XmlElement` annotation informs the JAXB implementation the property should be interpreted as `List<BaseBeanEx>`.

package forum10174513;

import java.util.List;
import javax.xml.bind.annotation.*;

@XmlRootElement
public class Root {

    private List<BaseBean> baseBeans;

    @XmlElement(name="base-bean", type=BaseBeanEx.class)
    public List<BaseBean> getBaseBeans() {
        return baseBeans;
    }

    public void setBaseBeans(List<BaseBean> baseBeans) {
        this.baseBeans = baseBeans;
    }

}

OPTION #2 - Using MOXy's External Mapping Document

The BaseBean is in a List somewhere in the datastructure and can't be changed.

If you can't modify your domain model and are using MOXy as your JAXB provider then you can leverage its external mapping document to apply metadata without modifying your domain model.

bindings.xml

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum10174513">
    <java-types>
        <java-type name="Root">
            <java-attributes>
                <xml-element 
                    java-attribute="baseBeans"
                    name="base-bean" 
                    type="forum10174513.BaseBeanEx"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Demo

Below is some code that demonstrates how to bootstrap a `JAXBContext` that leverages the external mapping document. There is currently a bug where classes only referenced through the external mapping document won't have there event methods registered (http://bugs.eclipse.org/376876). You can work around this issue by explicitly including this class in the list of classes used to create the `JAXBContext`.

package forum10174513;

import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(1);
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum10174513/bindings.xml");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Root.class, BaseBeanEx.class}, properties);

        File xml = new File("src/forum10174513/input.xml");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Root root = (Root) unmarshaller.unmarshal(xml);
    }

}

BaseBean

package forum10174513;

public class BaseBean {
}

BaseBeanEx

package forum10174513;

import javax.xml.bind.Unmarshaller;

public class BaseBeanEx extends BaseBean {

    public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
        System.out.println("AFTER UNMARSHAL WAS CALLED");
    }

}

Output

Below is the output that was generated by running the demo code.

AFTER UNMARSHAL WAS CALLED
AFTER UNMARSHAL WAS CALLED

For More Information

- http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html

- http://blog.bdoughan.com/2011/05/jaxb-and-interface-fronted-models.html

- http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

Problem

I wrote a bean (`BaseBeanEx`) extending a JAXB annotated bean (`BaseBean`). The `BaseBean` is in a List somewhere in the datastructure and can't be changed. The Software does an explicit cast to `BaseBeanEx` whenever it is needed. I also wrote an `ObjectFactory` to create `BaseBeanEx` instead of `BaseBean`. This all works fine, but now I added a `afterUnmarshal` method to `BaseBeanEx` which never gets called. Is this a bug or is this according to the specs? If later is the case, is there some elegant work around? I'm using the default JAXB engine.

Original source