5
votes

I may be misinterpreting the Jersey specification for the media type of a response when a method can produce one of several. According to https://jersey.java.net/documentation/latest/jaxrs-resources.html#d0e1785, I think that when two media types listed in an @Produces(..) annotation match the incoming Accept header, Jersey will honor any weights associated with those types in the annotation or will pick the first if the weights do not decide a winner.

The code below demonstrates how this is not the behavior in practice. In the final two cases, I expect a JSON response when the request is ambiguous but I get XML instead. Is my understanding of the documentation incorrect? Or is this possibly a defect in Jersey?

Things I have tried:

  1. Remove the @XmlRootElement annotation from the model. The final two cases then pass but the 2nd case diverges because there is no suitable writer.
  2. Return Object from the resource method. Result is no change in the pass/fail status of the cases.
  3. Remove the weights from the @Produces(..) annotation of the resource class (JSON media type is still listed first). Result is no change in the pass/fail status of the cases.

This example is built using Jersey 2.10 and Java 1.8_05 on Ubuntu 14.04.

package demo;

import java.net.URI;
import java.util.Arrays;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlRootElement;

import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;

import com.sun.net.httpserver.HttpServer;

public class DemonstrateAmbiguousMediaType {

    private static final String BASE_URI = "http://localhost:8092/";

    public static void main(final String[] args) {

        final HttpServer server = startServer();

        try {

            /*
             * These cases work fine.
             */
            check("application/json", "application/json");
            check("application/xml", "application/xml");

            /*
             * These cases should pass according to Jersey
             * documentation for @Produces
             * https://jersey.java.net/documentation/latest/jaxrs-resources.html#d0e1785
             * but they do not.
             */
            check("application/json", "application/*");
            check("application/json", "*/*");

        } finally {
            server.stop(0);
        }

    }

    private static void check(final String expected, final String... acceptTypes) {

        final MediaType atype = fetchAs(acceptTypes).getMediaType();
        final String actual = atype.getType() + "/" + atype.getSubtype();

        System.out.println(Arrays.asList(acceptTypes) + ":"
            + (expected.equals(actual) ? "pass" : "fail"));

    }

    private static Response fetchAs(final String[] acceptable) {

        return ClientBuilder.newClient()
                            .target(BASE_URI)
                            .path("model")
                            .request()
                            .accept(acceptable)
                            .get();

    }

    private static HttpServer startServer() {
        final ResourceConfig config = new ResourceConfig(Resource.class);
        final HttpServer rval = JdkHttpServerFactory.createHttpServer(
                URI.create(BASE_URI), config, false);
        rval.setExecutor(Executors.newCachedThreadPool(new ThreadFactory() {

            @Override
            public Thread newThread(Runnable r) {
                final Thread rval = new Thread(r);
                rval.setDaemon(true);
                return rval;
            }
        }));
        rval.start();
        return rval;
    }

    @XmlRootElement
    public static class Model {

        public int a = 10;
        public String b = "Bbb";

    }

    @Path("/model")
    @Produces({ "application/json; q=0.9", "application/xml; q=0.5" })
    public static class Resource {

        @GET
        public Model getModel() {
            return new Model();
        }

    }

}
1
I am fairly convinced now that this is a problem with the Jersey code or documentation so have filed an issue report there.aztecrex

1 Answers

2
votes

I mis-read the specification. In @Produces(..) annotations, the quality parameter name is qs not q. When the quality parameters are specified correctly, all cases above pass.

When the quality parameters are removed, however, the final cases do not pass. According to that same Jersey documentation, the first listed should be chosen but it is not.