users@jersey.java.net

Re: [Jersey] Issue with Provider redirection

From: Paul Sandoz <Paul.Sandoz_at_oracle.com>
Date: Mon, 9 Aug 2010 17:58:48 +0200

On Aug 9, 2010, at 4:53 PM, Pavel Bucek wrote:

> On 8/9/10 3:26 PM, Paul Sandoz wrote:
>>
>>
>> On Aug 9, 2010, at 2:58 PM, Pavel Bucek wrote:
>>
>>> Hello,
>>>
>>> I took a look into this problem and.. if you declare which mime
>>> types your message writer produces, it works as expected:
>>>
>>> @Provider
>>> @Produces("application/xml,text/xml,*/*")
>>> public class MyProvider<T extends Apple> implements
>>> MessageBodyWriter<T>
>>>
>>> (consider this as a quick workaround, I'm still investigating
>>> about this..)
>>>
>>
>> This looks like a bug as user registered providers should override
>> any of those supplied by Jersey regardless of the @Produces
>> annotation.
>
> I don't see any place where Jersey implementation somehow differs in
> handling user registered providers versus jersey provided.
>
> See ProviderServices.java
> private Set<ProviderClass> getProviderAndServiceClasses(Class<?>
> service)
>
> Do we need to change that?

We might be able to avoid that by splitting the following call:

     public <T> Set<T> getProvidersAndServices(Class<T> provider) {

into:

     public <T> Set<T> getProviders(Class<T> provider).

and:

     public <T> Set<T> getServices(Class<T> provider) {


> Or add method which will decide whether some particular provider is
> user provided? And if is, put it on top of matching queue in all
> media types?
>

Take a look at MessageBodyFactory and say a method initReaders:

     private void initReaders() {
         this.readerProviders = new KeyComparatorHashMap<MediaType,
List<MessageBodyReader>>(
                 MEDIA_TYPE_COMPARATOR);

         for (MessageBodyReader provider :
providerServices.getProvidersAndServices(MessageBodyReader.class)) {
             List<MediaType> values = MediaTypes.createMediaTypes(
                     provider.getClass().getAnnotation(Consumes.class));
             for (MediaType type : values)
                 getClassCapability(readerProviders, provider, type);
         }

         DistanceComparator<MessageBodyReader> dc = new
DistanceComparator<MessageBodyReader>(MessageBodyReader.class);
         for (Map.Entry<MediaType, List<MessageBodyReader>> e :
readerProviders.entrySet()) {
             Collections.sort(e.getValue(), dc);
         }
     }


The function getProvidersAndServices will return providers in the
following order:

   1) application registered singletons

   2) application registered classes

   3) META-INF/service registered classes


Notice that a map of media type to list of MBR is utilized. And here
is how look up works:

     public <T> MessageBodyReader<T> getMessageBodyReader(Class<T> c,
Type t,
             Annotation[] as,
             MediaType mediaType) {
         MessageBodyReader p = null;
         if (mediaType != null) {
             p = _getMessageBodyReader(c, t, as, mediaType, mediaType);
             if (p == null)
                 p = _getMessageBodyReader(c, t, as, mediaType,
                         new MediaType(mediaType.getType(),
MediaType.MEDIA_TYPE_WILDCARD));
         }
         if (p == null)
             p = _getMessageBodyReader(c, t, as, mediaType,
MediaTypes.GENERAL_MEDIA_TYPE);

         return p;
     }

     private <T> MessageBodyReader<T> _getMessageBodyReader(Class<T>
c, Type t,
             Annotation[] as,
             MediaType mediaType, MediaType lookup) {
         List<MessageBodyReader> readers = readerProviders.get(lookup);
         if (readers == null)
             return null;
         for (MessageBodyReader p : readers) {
             if (p.isReadable(c, t, as, mediaType))
                 return p;
         }
         return null;
     }


So if the most acceptable media type is application/xml we first look
up those MBRs that produce "application/xml", then "application/*"
then "*/*".

A map is used to optimize the lookup.

In this case it seems we need two maps, one for application registered
providers and one for the Jersey runtime service providers.

Or alternatively find a more optimal algorithm. We need to be careful
to ensure we are spec compatible. The most specific media type takes
precedence over the least specific and then for the same media type
sorting is performed for the parameterized type (based on the distance
between that type and Object, larger distance sorted first).

Paul.

> Pavel
>
>>
>> Paul.
>>
>>> Pavel
>>>
>>> On 8/9/10 10:47 AM, Guilhem wrote:
>>>>
>>>> Hi paul,
>>>>
>>>> i'm using jersey 1.3, I have made a little error in my previous
>>>> that i realize while creating a simple test case : my problem
>>>> don't happen when i use a non-standard MIME Type like:
>>>>
>>>> application/vnd.ogc.xml
>>>>
>>>> This problem happen when i use application/xml or text/xml, this
>>>> is probably a way to search.
>>>>
>>>> so I create a simple case and reproduce the bug, here are my
>>>> classes :
>>>>
>>>> my singleton
>>>>
>>>> package test.provider;
>>>>
>>>> import com.sun.jersey.spi.resource.Singleton;
>>>> import javax.ws.rs.GET;
>>>> import javax.ws.rs.Path;
>>>> import javax.ws.rs.core.Context;
>>>> import javax.ws.rs.core.Response;
>>>> import javax.ws.rs.core.UriInfo;
>>>> import test.model.Apple;
>>>>
>>>> @Path("sos")
>>>> @Singleton
>>>> public class MySingleton {
>>>>
>>>> @Context
>>>> private volatile UriInfo uriContext;
>>>>
>>>> @GET
>>>> public Response doGET() {
>>>> String outputFormat =
>>>> uriContext.getQueryParameters().getFirst("output");
>>>> if (outputFormat == null) {
>>>> outputFormat = "text/xml";
>>>> }
>>>> Apple a = new Apple();
>>>> return Response.ok(a, outputFormat).build();
>>>> }
>>>> }
>>>>
>>>>
>>>> my message body writer:
>>>>
>>>> package test.provider;
>>>>
>>>> import java.io.IOException;
>>>> import java.io.OutputStream;
>>>> import java.lang.annotation.Annotation;
>>>> import java.lang.reflect.Type;
>>>> import java.util.logging.Logger;
>>>> import javax.ws.rs.WebApplicationException;
>>>> import javax.ws.rs.core.MediaType;
>>>> import javax.ws.rs.core.MultivaluedMap;
>>>> import javax.ws.rs.ext.MessageBodyWriter;
>>>> import javax.ws.rs.ext.Provider;
>>>> import javax.xml.bind.JAXBContext;
>>>> import javax.xml.bind.JAXBException;
>>>> import javax.xml.bind.Marshaller;
>>>> import test.model.Apple;
>>>>
>>>> @Provider
>>>> public class MyProvider<T extends Apple> implements
>>>> MessageBodyWriter<T> {
>>>>
>>>> private static final Logger LOGGER =
>>>> Logger.getLogger("test.provider");
>>>>
>>>>
>>>> @Override
>>>> public boolean isWriteable(Class<?> type, Type type1,
>>>> Annotation[] antns, MediaType mt) {
>>>> System.out.println("isWritable:" +
>>>> Apple.class.isAssignableFrom(type));
>>>> return Apple.class.isAssignableFrom(type);
>>>> }
>>>>
>>>> @Override
>>>> public long getSize(T t, Class<?> type, Type type1,
>>>> Annotation[] antns, MediaType mt) {
>>>> System.out.println("get size");
>>>> return -1;
>>>> }
>>>>
>>>> @Override
>>>> public void writeTo(T t, Class<?> type, Type type1,
>>>> Annotation[] antns, MediaType mt, MultivaluedMap<String, Object>
>>>> mm, OutputStream out) throws IOException, WebApplicationException {
>>>> System.out.println("write to");
>>>> try {
>>>> Marshaller m =
>>>> JAXBContext.newInstance(Apple.class).createMarshaller();
>>>> m.marshal(t, out);
>>>> } catch (JAXBException ex) {
>>>> LOGGER.severe("JAXB exception while writing the
>>>> capabilities File");
>>>> }
>>>> }
>>>>
>>>> }
>>>>
>>>> and a simple model object
>>>>
>>>> package test.model;
>>>>
>>>> import javax.xml.bind.annotation.XmlElement;
>>>> import javax.xml.bind.annotation.XmlRootElement;
>>>>
>>>> @XmlRootElement(name="Apple")
>>>> public class Apple {
>>>>
>>>> @XmlElement
>>>> public String colour;
>>>>
>>>> }
>>>>
>>>> I use the system.out to see if i enter the messageBodyWriter
>>>>
>>>> if i make a request : http://localhost:8080/Test-provider/sos?output=application/xml
>>>> or http://localhost:8080/Test-provider/sos?output=text/xml or http://localhost:8080/Test-provider/sos
>>>>
>>>> ==> nothing in the log
>>>>
>>>> if i make a request : http://localhost:8080/Test-provider/sos?output=app/xml
>>>>
>>>> ==> in the log :
>>>> isWritable:true
>>>> get size
>>>> write to
>>>>
>>>> So the problem here i think is relative to unrecognized mime Type.
>>>>
>>>> Guilhem Legal
>>>>
>>>>
>>>> On 09/08/2010 08:06, Paul Sandoz wrote:
>>>>>
>>>>> Hi Guilhem,
>>>>>
>>>>> What version of Jersey are you using?
>>>>>
>>>>> It seems like when you use text/xml Jersey is processing and
>>>>> that results in an exception due to some JAXB error, possibly
>>>>> because by default Jersey will create a JAXBContext from the
>>>>> class and that may be lacking some additional information than
>>>>> if you created the JAXBContext.
>>>>>
>>>>> The code to process text/xml and application/xml goes through
>>>>> the same path so you should be able to override both or indeed
>>>>> any JAXB support supplied by Jersey if your own
>>>>> MessageBodyWriter implementation is registered. Would it be
>>>>> possible to send a simple reproducible test case?
>>>>>
>>>>> Note that you can also define your own
>>>>> ContextResolver<Marshaller> that can pool Marshaller instances
>>>>> per-thread (as they are not thread safe and reuse the existing
>>>>> JAXB support.
>>>>>
>>>>> Paul.
>>>>>
>>>>> On Aug 6, 2010, at 5:58 PM, Guilhem wrote:
>>>>>
>>>>>> Hello,
>>>>>>
>>>>>> I'm using jersey in a WebService to produce XML object
>>>>>> marshalled with JAXB.
>>>>>> I want to use my own JAXBContext so i create a Jersey provider
>>>>>> to handle the marshalling of my object.
>>>>>>
>>>>>> My problem is that when i use the MIME type aplication/xml, my
>>>>>> provider is well used, but when i use the MIME type text/XML
>>>>>> jersey generate his own marshaller and so failed to create the
>>>>>> JAXBContext (because it is a little hard).
>>>>>>
>>>>>>
>>>>>> here is my provider (simplified) :
>>>>>>
>>>>>> import**;
>>>>>>
>>>>>> @Provider
>>>>>> public class CapabilitiesWriter<T extends Capabilities>
>>>>>> implements MessageBodyWriter<T> {
>>>>>>
>>>>>> private static final MarshallerPool pool;
>>>>>> static {
>>>>>> MarshallerPool pool = // here i build a sort of marshaller
>>>>>> }
>>>>>>
>>>>>> @Override
>>>>>> public boolean isWriteable(Class<?> type, Type type1,
>>>>>> Annotation[] antns, MediaType mt) {
>>>>>> return Capabilities.class.isAssignableFrom(type);
>>>>>> }
>>>>>>
>>>>>> @Override
>>>>>> public long getSize(T t, Class<?> type, Type type1,
>>>>>> Annotation[] antns, MediaType mt) {
>>>>>> return -1;
>>>>>> }
>>>>>>
>>>>>> @Override
>>>>>> public void writeTo(T t, Class<?> type, Type type1,
>>>>>> Annotation[] antns, MediaType mt, MultivaluedMap<String,
>>>>>> Object> mm, OutputStream out) throws IOException,
>>>>>> WebApplicationException {
>>>>>> Marshaller m = // here i get my marshaller;
>>>>>> m.marshal(t, out);
>>>>>> }
>>>>>>
>>>>>> }
>>>>>>
>>>>>> and in my singleton I return a Response object like this :
>>>>>>
>>>>>> @GET
>>>>>> public Response doGET() throws JAXBException {
>>>>>> String currentMIME_Type = "something"; // here i have some
>>>>>> process to choose the MIME type
>>>>>> Capabilities capa = something; // again i
>>>>>> have some process building the object
>>>>>>
>>>>>> return Response.ok(capa, currentMIME_Type).build();
>>>>>> }
>>>>>>
>>>>>>
>>>>>> if "currentMIME_Type"=application/xml allright everything is
>>>>>> fine, i enter in the method writeTo of my provider
>>>>>> CapabilitiesWriter.
>>>>>>
>>>>>> if "currentMIME_Type"=text/xml i get a JAXB Exception at (I
>>>>>> have 2 stack trace, don't know why) :
>>>>>>
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .core
>>>>>> .provider
>>>>>> .jaxb
>>>>>> .AbstractRootElementProvider
>>>>>> .writeTo(AbstractRootElementProvider.java:152)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .spi.container.ContainerResponse.write(ContainerResponse.java:
>>>>>> 294)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .server
>>>>>> .impl
>>>>>> .application
>>>>>> .WebApplicationImpl._handleRequest(WebApplicationImpl.java:1140)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .server
>>>>>> .impl
>>>>>> .application
>>>>>> .WebApplicationImpl.handleRequest(WebApplicationImpl.java:1053)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .server
>>>>>> .impl
>>>>>> .application
>>>>>> .WebApplicationImpl.handleRequest(WebApplicationImpl.java:1043)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .spi.container.servlet.WebComponent.service(WebComponent.java:
>>>>>> 406)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .spi
>>>>>> .container
>>>>>> .servlet.ServletContainer.service(ServletContainer.java:477)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .spi
>>>>>> .container
>>>>>> .servlet.ServletContainer.service(ServletContainer.java:662)
>>>>>> at javax.servlet.http.HttpServlet.service(HttpServlet.java:
>>>>>> 722)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .catalina
>>>>>> .core
>>>>>> .ApplicationFilterChain
>>>>>> .internalDoFilter(ApplicationFilterChain.java:303)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .catalina
>>>>>> .core
>>>>>> .ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .catalina
>>>>>> .core.StandardWrapperValve.invoke(StandardWrapperValve.java:243)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .catalina
>>>>>> .core.StandardContextValve.invoke(StandardContextValve.java:201)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .catalina.core.StandardHostValve.invoke(StandardHostValve.java:
>>>>>> 163)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:
>>>>>> 108)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .catalina.valves.AccessLogValve.invoke(AccessLogValve.java:556)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .catalina
>>>>>> .core.StandardEngineValve.invoke(StandardEngineValve.java:118)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:402)
>>>>>> at
>>>>>> org
>>>>>> .apache
>>>>>> .coyote.http11.Http11Processor.process(Http11Processor.java:249)
>>>>>> at org.apache.coyote.http11.Http11Protocol
>>>>>> $Http11ConnectionHandler.process(Http11Protocol.java:267)
>>>>>> at org.apache.coyote.http11.Http11Protocol
>>>>>> $Http11ConnectionHandler.process(Http11Protocol.java:245)
>>>>>> at org.apache.tomcat.util.net.JIoEndpoint
>>>>>> $SocketProcessor.run(JIoEndpoint.java:260)
>>>>>> at java.util.concurrent.ThreadPoolExecutor
>>>>>> $Worker.runTask(ThreadPoolExecutor.java:886)
>>>>>> at java.util.concurrent.ThreadPoolExecutor
>>>>>> $Worker.run(ThreadPoolExecutor.java:908)
>>>>>> at java.lang.Thread.run(Thread.java:619)
>>>>>>
>>>>>>
>>>>>> at
>>>>>> com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException
>>>>>> $Builder.check(IllegalAnnotationsException.java:91)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .xml
>>>>>> .internal
>>>>>> .bind
>>>>>> .v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:
>>>>>> 436)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .xml
>>>>>> .internal
>>>>>> .bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:277)
>>>>>> at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl
>>>>>> $JAXBContextBuilder.build(JAXBContextImpl.java:1100)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .xml
>>>>>> .internal
>>>>>> .bind.v2.ContextFactory.createContext(ContextFactory.java:143)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .xml
>>>>>> .internal
>>>>>> .bind.v2.ContextFactory.createContext(ContextFactory.java:110)
>>>>>> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>>>>>> at
>>>>>> sun
>>>>>> .reflect
>>>>>> .NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:
>>>>>> 39)
>>>>>> at
>>>>>> sun
>>>>>> .reflect
>>>>>> .DelegatingMethodAccessorImpl
>>>>>> .invoke(DelegatingMethodAccessorImpl.java:25)
>>>>>> at java.lang.reflect.Method.invoke(Method.java:597)
>>>>>> at
>>>>>> javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:202)
>>>>>> at javax.xml.bind.ContextFinder.find(ContextFinder.java:376)
>>>>>> at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:
>>>>>> 574)
>>>>>> at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:
>>>>>> 522)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .core
>>>>>> .provider
>>>>>> .jaxb
>>>>>> .AbstractJAXBProvider
>>>>>> .getStoredJAXBContext(AbstractJAXBProvider.java:184)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .core
>>>>>> .provider
>>>>>> .jaxb
>>>>>> .AbstractJAXBProvider.getJAXBContext(AbstractJAXBProvider.java:
>>>>>> 177)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .core
>>>>>> .provider
>>>>>> .jaxb
>>>>>> .AbstractJAXBProvider.getMarshaller(AbstractJAXBProvider.java:
>>>>>> 155)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .core
>>>>>> .provider
>>>>>> .jaxb
>>>>>> .AbstractJAXBProvider.getMarshaller(AbstractJAXBProvider.java:
>>>>>> 134)
>>>>>> at
>>>>>> com
>>>>>> .sun
>>>>>> .jersey
>>>>>> .core
>>>>>> .provider
>>>>>> .jaxb
>>>>>> .AbstractRootElementProvider
>>>>>> .writeTo(AbstractRootElementProvider.java:145)
>>>>>>
>>>>>>
>>>>>>
>>>>>> so my question is, why the MIME type change the way of using
>>>>>> provider? and how redirect all mime type to my provider?
>>>>>>
>>>>>> Thanks,
>>>>>>
>>>>>> Guilhem legal
>>>>>>
>>>>>> ---------------------------------------------------------------------
>>>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>>>
>>>>>
>>>>>
>>>>> ---------------------------------------------------------------------
>>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>>
>>>>>
>>>>>
>>>>
>>>
>>
>