users@jersey.java.net

A Christmas Quiz: How to solve weird JAXB Unmarshalling Problem?

From: Markus Karg <markus.karg_at_gmx.net>
Date: Fri, 25 Dec 2009 17:22:49 +0100

Hello Jersey Community,

 

here is a Jersey Christmas Quiz for you: Why is our code not working?

 

We detected a weird problem with JAXB Unmarshalling in Jersey and just don't
know how to go on. :-(

 

The problem is quite complex to explain, so sorry for the length of this
email.

 

We have the following three simple and short JAXB objects (shortened for
easier readability):

 

@XmlRootElement(name = "lockinfo")

public class LockInfo {

      @XmlElement

      private Owner owner;

      public Owner getOwner() {return owner;}

}

 

@XmlRootElement

public class Owner {

      @XmlMixed @XmlAnyElement(lax = true)

      private LinkedList<Object> any;

      public List<Object> getAny() {return (List<Object>) any.clone();}

}

 

@XmlRootElement(name = "href")

public class HRef {

      @XmlValue

      private String value;

      public final String getValue() {return this.value;}

}

 

All of them are referenced in our JAXB Context:

 

@Provider @Produces(MediaType.APPLICATION_XML)

public class WebDavContextResolver implements ContextResolver<JAXBContext> {

      private JAXBContext context;

      public WebDavContextResolver() throws JAXBException {

            context = JAXBContext.newInstance(HRef.class, LockInfo.class,
Owner.class);

      }

      @Override

      public JAXBContext getContext(Class<?> cls) {return context;}

}

 

public class AddressBookApplication extends Application {

      @Override

      public Set<Class<?>> getClasses() {

            return new HashSet<Class<?>>(Arrays.asList(AddressBook.class));

      }

 

      @Override

      public Set<Object> getSingletons() {

            try {

                  return new HashSet<Object>(Arrays.asList(new
WebDavContextResolver()));

            } catch (JAXBException e) {return null;}

      }

}

 

So far it looks all correct.

 

When a client is sending a XML body like this one

 

<lockinfo><owner><href>foo\bar</href></owner></lockino>

 

what we expect to get in our resource's lockInfo variable obtained by @LOCK
public void lock(LockInfo lockInfo) should be a JAXB object tree like this
one:

 

LockInfo

!

+--- Owner

     !

     +--- HRef

 

But what we actually get is this one:

 

LockInfo

!

+--- Owner

     !

     +--- Simple DOM Element with name 'href' and content 'foo\bar'

 

We would understand to get a DOM Element if the any member wouldn't be
annotated by lax = true -- but actually it is annotated that way!

 

We would also understand to get a complete DOM-only tree like

 

Simple DOM Element with name 'lockinfo'

!

+--- Simple DOM Element with name 'owner'

     !

     +--- Simple DOM Element with name 'href' and content 'foo\bar'

 

but actually both container elements are correctly unmarshalled to JAXB
elements.

 

And it is getting even weird as soon as we replace our resource's method be
this one:

 

@LOCK

public void lock(InputStream entityStream, @Context UriInfo uriInfo,
@Context Providers providers, @Context HttpHeaders httpHeaders) throws
IOException {

      LockInfo lockInfo = providers .getMessageBodyReader(LockInfo.class,
LockInfo.class, new Annotation[0], new
MediaType.APPLICATION_XML_TYPE).readFrom(LockInfo.class, LockInfo.class, new
Annotation[0], new MediaType APPLICATION_XML_TYPE,
httpHeaders.getRequestHeaders(), entityStream);

 

Obviously this slightly modified code should come to the same result as the
unmodified code, as the exact same MessageBodyReader (and this, JAXB
context) is getting used. But that code absolutely correctly produces what
we all want (and this is our current workaround), a 100% pure JAXB tree!

 

LockInfo

!

+--- Owner

     !

     +--- HRef

 

Ain't that really weird?

 

If anybody has an idea how to solve this, please tell us! :-)

 

Merry Christmas

Markus