views:

225

answers:

3

I need to perform certain operations on a class after it was unmarshalled (after it is constructed by JAXB, rather then by myself).

Is there such a functionality in JAXB?

If not, how could I achieve it?

A: 

It's not a 100% solution, but you can always register a XmlAdapter using @XmlJavaTypeAdapter annotation for this type.

The downside would be that you have to serialize the class yourself (?). I am not aware of any simple way of accessing and calling the default serialization mechanism. But with custom [XmlAdapter] you can control how is the type serialized and what happens before/after it.

Grzegorz Oledzki
+2  A: 

Though the the demanded functionality seems not to be present in JAXB, I managed to achieve something which goes into the right direction:

  • I'm using JSR-305's @PostConstruct annotation
    (it's just a nacked annotation, no functionality is provided by the JSR)
  • I add an unmasrshaller-listener to the unmarshaller, which gets invoked by JAXB every time an object was unmarshalled.
  • I inspect this object using Java reflection and search for the @PostConstruct annotation on a method
  • I execute the method

Tested. Works.

Here is the code. Sorry, I'm using some external reflection API to get all methods, but I think the idea is understandable:

Implementation

JAXBContext context = // create the context with desired classes

Unmarshaller unmarshaller = context.createUnmarshaller();

unmarshaller.setListener(new Unmarshaller.Listener() {

  @Override
  public void afterUnmarshal(Object object, Object arg1) {
    System.out.println("unmarshalling finished on: " + object);

    Class<?> type = object.getClass();
    Method postConstructMethod = null;

    for (Method m : ReflectionUtils.getAllMethods(type)) {
      if (m.getAnnotation(PostConstruct.class) != null) {
        if (postConstructMethod != null) {
          throw new IllegalStateException(
              "@PostConstruct used multiple times");
        }

        postConstructMethod = m;
      }
    }

    if (postConstructMethod != null) {
      System.out.println("invoking post construct: "
          + postConstructMethod.getName() + "()");

      if (!Modifier.isFinal(postConstructMethod.getModifiers())) {
        throw new IllegalArgumentException("post construct method ["
            + postConstructMethod.getName() + "] must be final");
      }

      try {
        postConstructMethod.setAccessible(true); // thanks to skaffman
        postConstructMethod.invoke(object);
      } catch (IllegalAccessException ex) {
        throw new RuntimeException(ex);
      } catch (InvocationTargetException ex) {
        throw new RuntimeException(ex);
      }
    }
  }

});

EDIT
Added a check for @PostConstruct-annotated method, to ensure it is final.
Do you think it's a useful restriction?

Usage

Here is how the concept might be used.

@XmlAccessorType(XmlAccessType.NONE)
public abstract class AbstractKeywordWithProps
    extends KeywordCommand {

  @XmlAnyElement
  protected final List<Element> allElements = new LinkedList<Element>();

  public AbstractKeywordWithProps() {
  }

  @PostConstruct
  public final void postConstruct() {
    // now, that "allElements" were successfully initialized,
    // do something very important with them ;)
  }

}

// further classes can be derived from this one. postConstruct still works!

Filed a feature request

https://jaxb.dev.java.net/issues/show_bug.cgi?id=698

java.is.for.desktop
Try calling `setAccessible(true)` on `postConstructMethod`, that should force it to be callable.
skaffman
Thanks! Incorporated the change into the code.
java.is.for.desktop
+2  A: 

You can use the 'class defined' event callbacks. Read more here http://java.sun.com/javase/6/docs/api/javax/xml/bind/Unmarshaller.html#unmarshalEventCallback
For example put this method in you JAXB object:

    //This method is called after all the properties (except IDREF) are unmarshalled for this object, 
    //but before this object is set to the parent object.
    void afterUnmarshal( Unmarshaller u, Object parent )
    {
        System.out.println( "After unmarshal: " + this.state );
    }
Oded Peer