users@jersey.java.net

strange "double dispatching" problem

From: Bryon Jacob <bryon_at_jacobtx.net>
Date: Tue, 20 Jan 2009 16:58:06 -0600

Hi all - I am having an extremely strange problem with Jersey
dispatching to one of my resource methods TWICE. The strangest thing
about it seems to be that the problem is related to the return type of
the method!

This problem is happening pretty deep in my resource hierarchy, so I'm
not pasting ALL of the code here - but I have boiled it down to a
pretty simple example. If I have this method defined:

     @GET
     @Path("/_/{whatever : .*}")
     public Entry getConfused(@PathParam("whatever")String whatever) {
         System.out.println(whatever);
         Entry entry = AbderaMarshaller.factory().newEntry();
         entry.setId(whatever);
         return entry;
     }

everything works fine -- that is, when I go to the URL http://localhost:8000/A/B/C/_/blah
  (where A/B/C is the path to the resource on which this method is
defined), I see "blah" printed to the console exactly once, and I get
an <entry> as a response. When I replace that method with this:

     @GET
     @Path("/_/{whatever : .*}")
     public Feed getConfused(@PathParam("whatever")String whatever) {
         System.out.println(whatever);
         Feed feed = AbderaMarshaller.factory().newFeed();
         Entry entry = AbderaMarshaller.factory().newEntry();
         entry.setId(whatever);
         feed.addEntry(entry);
         return feed;
     }

and go to the same URL, I get the <feed> element back, just as I
expected, but "blah" is printed to the console TWICE! How can this be?

The Entry and Feed elements are from Abdera's object model, and my
registered provider is my custom AbderaMarshaller class (source below)
- everything seems to work properly, no exceptions are being thrown,
and my provider is being called to marshall the Abdera objects to XML,
but with the second variation of the method, it is always called TWICE
when a matching URL is accessed.

Other variables that might be interesting...
- I'm using Jersey 1.0.1
- I'm using jersey-spring (also 1.0.1) to compose the application
with Spring 2.5 annotations.
- I'm running java 1.6, and I'm running all of this using the
com.sun.net.httpserver.HttpServer class.

I figure either I must be doing something blindingly dumb, or there's
a subtle bug in Jersey, since I can't imagine any reason for jersey to
dispatch to my methods twice (BTW - I did verify that this is exactly
what is happening... I did a Thread.dumpStack() in the failing method
above, and the exact same stack, starting somewhere in Jersey and
ending up with a call to my method, was repeated twice...)

A quick search of the list archives didn't turn up anything
relevant... so I thought I'd post it here and see if anyone had seen
this. Anyways - here's the source to my provider class - thanks for
any help y'all can offer!


@Provider
@Produces({APPLICATION_ATOM_XML, APPLICATION_XML, TEXT_XML})
@Consumes({APPLICATION_ATOM_XML, APPLICATION_XML, TEXT_XML})
public class AbderaMarshaller implements MessageBodyWriter,
MessageBodyReader {

     public void writeTo(Object o, Class aClass, Type type,
Annotation[] annotations, MediaType mediaType,
                         MultivaluedMap multivaluedMap, OutputStream
outputStream)
         throws IOException, WebApplicationException {
         ((ExtensibleElement) o).writeTo(outputStream);
     }

     public Object readFrom(Class aClass, Type type, Annotation[]
annotations, MediaType mediaType,
                            MultivaluedMap multivaluedMap, InputStream
inputStream)
         throws IOException, WebApplicationException {
         return parser().parse(inputStream).getRoot();
     }

     public boolean isWriteable(Class aClass, Type type, Annotation[]
annotations, MediaType mediaType) {
         return isAbderaExtensibleElement(aClass);
     }

     public long getSize(Object o, Class aClass, Type type,
Annotation[] annotations, MediaType mediaType) {
         return -1;
     }

     public boolean isReadable(Class aClass, Type type, Annotation[]
annotations, MediaType mediaType) {
         return isAbderaExtensibleElement(aClass);
     }

     private boolean isAbderaExtensibleElement(Class clazz) {
         return ExtensibleElement.class.isAssignableFrom((Class<?>)
clazz);
     }

     private static final transient ThreadLocal<Parser> PARSER = new
ThreadLocal<Parser>() {
         protected Parser initialValue() {
             return new FOMParser();
         }
     };

     public static Parser parser() {
         return PARSER.get();
     }

     private static final transient ThreadLocal<FOMFactory> FACTORY =
             new ThreadLocal<FOMFactory>() {
                 protected FOMFactory initialValue() {
                     return new FOMFactory();
                 }
             };

     public static FOMFactory factory() {
         return FACTORY.get();
     }

}