Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using default Providers/MessageBodyWriters in Jersey 2

Just starting with Jersey, I've been trying to reproduce the simple example in the latest Jersey documentation 'building responses'. This part, as far as I understand, should show how Response and ResponseBuilder can be used to easily return a response in combination with Entity<T> for response content.

Now, the documentation states that several data types are by default supported (here: 'Representations and Java types'). String prime among them, matching any media type.

Of all the variations I've tried, the following is the simplest:

@POST
public Response post() {
    URI createdUri;
    try {
        createdUri = new URI("http://test.lan");
    } catch (final URISyntaxException e) {
        throw new WebApplicationException(e);
    }

    return Response.created(createdUri).entity(Entity.text("someContent")).build();
}

I've always gotten the same error (full stacktrace below) on calling the request: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=text/plain, type=class javax.ws.rs.client.Entity, genericType=class javax.ws.rs.client.Entity.

I believe it's saying that a suitable provider wasn't found for this Entity generic-type. However, String should be supported OOTB?

I found that StringMessageProvider is probably the Jersey 1 implementation of this provider, and the closest related classes I found in my Jersey 2 libraries arer the classes in org.glassfish.jersey.message.internal in jersey-common. Among the many providers there is the StringMessageProvider, which appears to me like a potential intended provider for this.

I've looked up the issue, and while there's plenty of people who get this when incorrectly trying to use a custom Provider, I found nothing about the default OOTB providers not working..

I've checked my libs, and right now I have the following dependencies in my pom (among others):

  • jersey-container-servlet-core
  • jersey-client
  • jersey-common
  • jersey-server

I've looked online but this seems to be all I need, although I have not with certainty found the right provider classes for String and JAXB/JSON in the jars.

Context

  • Maven project
  • with tomcat servlet-api 6.0.29
  • Version 2.6 of all mentioned jersey libs
  • Eclipse kepler
  • Using tomcat6 maven plugin to run embedded tomcat (works fine so far)

Fiddler request used to test

POST HTTP/1.1
User-Agent: Fiddler
Host: 127.0.0.1
Content-Length: 0

And again tried several variations.

Full stacktrace

06-Jan-2015 21:13:54 org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
SEVERE: MessageBodyWriter not found for media type=text/plain, type=class javax.ws.rs.client.Entity, genericType=class javax.ws.rs.client.Entity.
06-Jan-2015 21:13:54 org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet TestService threw exception
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=text/plain, type=class javax.ws.rs.client.Entity, genericType=class javax.ws.rs.client.Entity.
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:247)
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
    at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:103)
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
    at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:88)
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
    at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1154)
    at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:571)
    at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:378)
    at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:368)
    at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:262)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:319)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:236)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1028)
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:373)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:219)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:602)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
    at java.lang.Thread.run(Thread.java:662)

EDIT

The same error (for application/json) ocurs now that I've annotated a class with @XmlRootElement and try to return it in a method per the Jersey docs:

@GET
@Produces(MediaType.APPLICATION_JSON) 
public Foo sampleFoo() {
    Foo foo = new Foo();

    return foo;
}

Where Foo is annotated with @XmlRootElement.

I also added jersey-media-json-jackson as dependency, which I can see contains an explicit JSONJaxb provider. However, it doesn't seem to be picked up somehow.

like image 966
Mark Tielemans Avatar asked Jan 06 '15 20:01

Mark Tielemans


People also ask

What is jersey client used for?

Jersey is an open source framework for developing RESTFul Web Services. It also has great inbuilt client capabilities.

What is Jaxrs Jersey?

The JAX-RS (JSR 311: The Java API for RESTful Web Services) specification provides a standardized Java-based approach to implementing REST-style web services. Jersey is the reference implementation of JAX-RS and I provide a brief introduction to JAX-RS via Jersey in this blog post.

What is @provider annotation in Java?

The @Provider annotation is used for anything that is of interest to the JAX-RS runtime, such as MessageBodyReader and MessageBodyWriter. For HTTP requests, the MessageBodyReader is used to map an HTTP request entity body to method parameters.

What is Jersey config?

A Jersey Configuration is a class that extends from ResourceConfig from the jersey library. A Jersey Configuration class will typically define an @ApplicationPath annotation specifying path to connect via REST.


2 Answers

First Issue:

javax.ws.rs.client.Entity is a client side class. The JAX-RS spec doesn't say anything about its usage on the server side. But I can confirm with many different tests, that the result will be similar to what you are seeing (with Jersey at least). With Resteasy, it will just send out the Entity.toString()

Since this doesn't work for either Resteasy or Jersey, I won't say it's a bug, but possible a mistake in the Jersey documentation, which exampled it usage as follows:

@POST
@Consumes("application/xml")
public Response post(String content) {
  URI createdUri = ...
  String createdContent = create(content);
  return Response.created(createdUri)
                         .entity(Entity.text(createdContent)).build();
}

The above failed for me also. But you are not wrong in saying

...several data types are by default supported

as they are. To get your example to work, simply change the Entity.text("someContent") to simply "someContent"

return Response.created(createdUri).entity("someContent").build();

And just for completeness, client side usage might look something like

Response response = webTarget.request().post(Entity.text("Hello World"));

which works just fine.

Second Issue:

Up until (I believe) Jersey 2.9, the jersey-media-json-jackson module is not auto-configured. So with 2.6, we need to set up the configuration either through package scanning in the web.xml or in the Application subclass. Either way, a web.xml is required. As stated here in regards to a 2.x servlet environment, which Tomcat 6 is.

In Servlet 2.5 environment, you have to explicitly declare the Jersey container Servlet in your Web application's web.xml deployment descriptor file.

So to scan for the JSON provider classes, you should specify the package in the jersey.config.server.provider.packages init-param. An example web.xml would be something like this

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
                       http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>
            org.glassfish.jersey.servlet.ServletContainer
        </servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>
                thepackage.of.your.resources,
                org.codehaus.jackson.jaxrs      <!-- Jackson providers -->
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
</web-app>

You are also allowed to use an Application subclass (which ResourceConfig extends from). We just need to specify it in the web.xml. An example configuration might be something like

public class MyApplication extends ResourceConfig {  
    public MyApplication() {
        register(JacksonFeature.class);
        packages("thepackage.of.your.resources");
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                       http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <servlet>
        <servlet-name>MyApplication</servlet-name>
        <servlet-class>
            org.glassfish.jersey.servlet.ServletContainer
        </servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>jersey2.tomcat6.MyApplication</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>MyApplication</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
</web-app>

Note: All of this was tested against your same environment, besides using Eclipse. I am using Netbeans, though it shouldn't make any difference. Also the only Maven dependencies I needed were

<dependencies>
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet-core</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>${jersey.version}</version>
    </dependency>
</dependencies>

<jersey.version>2.6</jersey.version>

On another note, to simplify development, I just created a simple Maven archetype with the following coordinates

GroupId:     org.glassfish.jersey.archetypes
ArtifactId:  jersey-quickstart-webapp
Version:     2.6

You can also see Creating a New Project from Maven Archetype

like image 69
Paul Samsotha Avatar answered Nov 10 '22 09:11

Paul Samsotha


As far as the text/plain format, does this work?

return Response.created(createdUri).type(MediaType.TEXT_PLAIN).entity("someContent").build();

For JSON output, I have these dependencies

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>

as well as a jaxb implementation. Any will do, I use

    <dependency>
        <groupId>com.sun.xml.bind</groupId>
        <artifactId>jaxb-impl</artifactId>
    </dependency>

I also define an object mapper provider, though I'm not 100% sure that it's required (unless you want to customize):

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;

@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
    ObjectMapper mapper;

    public ObjectMapperProvider() {
        mapper = new ObjectMapper();
        mapper.configure( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false );
        mapper.configure( SerializationFeature.INDENT_OUTPUT, true );
        mapper.configure( SerializationFeature.WRITE_NULL_MAP_VALUES, true );
        mapper.configure( SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true );
        mapper.setAnnotationIntrospector( new JaxbAnnotationIntrospector(TypeFactory.defaultInstance()) );
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return mapper;
    }
}

Also, I think you need to register the Jackson feature:

@ApplicationPath("")
public class Application extends ResourceConfig {
    public Application() {
        register( JacksonFeature.class );
    }
}

I should note that this is all configured using Jersey 2.11.

like image 29
Don Bottstein Avatar answered Nov 10 '22 09:11

Don Bottstein