web-dev-qa-db-fra.com

Utilisation des fournisseurs par défaut / MessageBodyWriters dans Jersey 2

En commençant avec Jersey, j'ai essayé de reproduire l'exemple simple dans la dernière documentation de Jersey ' construction de réponses '. Pour autant que je sache, cette partie devrait montrer comment Response et ResponseBuilder peuvent être utilisés pour renvoyer facilement une réponse en combinaison avec Entity<T> pour le contenu de la réponse.

Maintenant, la documentation indique que plusieurs types de données sont pris en charge par défaut (ici: ' Représentations et Java types '). String prime parmi eux, correspondant à tout type de média.

De toutes les variantes que j'ai essayées, la suivante est la plus simple:

@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();
}

J'ai toujours eu la même erreur (trace de pile complète ci-dessous) lors de l'appel de la demande: 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.

Je pense que cela signifie qu'un fournisseur approprié n'a pas été trouvé pour ce type générique d'entité. Cependant, String doit être pris en charge OOTB?

J'ai trouvé que StringMessageProvider est probablement l'implémentation Jersey 1 de ce fournisseur, et les classes connexes les plus proches que j'ai trouvées dans mes bibliothèques Jersey 2 sont les classes dans org.glassfish.jersey.message.internal en jersey-commun. Parmi les nombreux fournisseurs, il y a le StringMessageProvider, qui me semble être un fournisseur potentiel pour cela.

J'ai recherché le problème, et bien qu'il y ait beaucoup de gens qui l'obtiennent en essayant incorrectement d'utiliser un fournisseur personnalisé, je n'ai rien trouvé sur les fournisseurs par défaut OOTB ne fonctionne pas ..

J'ai vérifié mes bibliothèques, et en ce moment, j'ai les dépendances suivantes dans mon pom (entre autres):

  • jersey-container-servlet-core
  • client-maillot
  • maillot-commun
  • jersey-server

J'ai regardé en ligne mais cela semble être tout ce dont j'ai besoin, bien que je n'ai pas avec certitude trouvé les bonnes classes de fournisseur pour String et JAXB/JSON dans les bocaux.

Le contexte

  • Projet Maven
  • avec Tomcat servlet-api 6.0.29
  • Version 2.6 de toutes les bibliothèques de maillots mentionnées
  • Kepler Eclipse
  • Utilisation du plug-in Tomcat6 maven pour exécuter Tomcat intégré (fonctionne très bien jusqu'à présent)

Demande de violon utilisé pour tester

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

Et encore une fois essayé plusieurs variantes.

Trace de pile complète

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)

ÉDITER

La même erreur (pour application/json) se produit maintenant que j'ai annoté une classe avec @XmlRootElement et essayez de le renvoyer dans une méthode conforme à la documentation de Jersey:

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

    return foo;
}

Foo est annoté avec @XmlRootElement.

J'ai également ajouté jersey-media-json-jackson en tant que dépendance, que je peux voir contient un fournisseur JSONJaxb explicite. Cependant, cela ne semble pas être repris d'une manière ou d'une autre.

10
Mark Tielemans

Premier numéro:

javax.ws.rs.client.Entity est une classe côté client. La spécification JAX-RS ne dit rien de son utilisation côté serveur. Mais je peux confirmer avec de nombreux tests différents, que le résultat sera similaire à ce que vous voyez (avec Jersey au moins). Avec Resteasy, il enverra simplement la Entity.toString()

Comme cela ne fonctionne pas pour Resteasy ou Jersey, je ne dirai pas que c'est un bogue, mais peut-être une erreur dans la documentation de Jersey, qui en a donné un exemple d'utilisation:

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

Ce qui précède a également échoué pour moi. Mais vous n'avez pas tort de dire

... plusieurs types de données sont pris en charge par défaut

comme ils sont. Pour que votre exemple fonctionne, changez simplement la Entity.text("someContent") en simplement "someContent"

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

Et juste pour être complet, tilisation côté client pourrait ressembler à quelque chose comme

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

qui fonctionne très bien.

Deuxième problème:

Jusqu'à (je crois) Jersey 2.9, le module jersey-media-json-jackson N'est pas configuré automatiquement. Donc, avec 2.6, nous devons installer la configuration soit par l'analyse des packages dans la web.xml Ou dans la sous-classe Application. Dans les deux cas, un web.xml est requis . Comme indiqué ici en ce qui concerne un environnement de servlet 2.x, qui est Tomcat 6.

Dans l'environnement Servlet 2.5, vous devez déclarer explicitement le conteneur Jersey Servlet dans le fichier descripteur de déploiement web.xml de votre application Web.

Donc, pour rechercher les classes de fournisseur JSON, vous devez spécifier le package dans le paramètre d'initiation jersey.config.server.provider.packages. Un exemple web.xml serait quelque chose comme ça

<?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>

Vous êtes également autorisé à utiliser une sous-classe Application (à partir de laquelle ResourceConfig s'étend). Il suffit de le spécifier dans le web.xml. Un exemple de configuration pourrait être quelque chose comme

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>

Remarque: Tout cela a été testé par rapport à votre même environnement, en plus d'utiliser Eclipse. J'utilise Netbeans, mais cela ne devrait faire aucune différence. De plus, les seules dépendances Maven dont j'avais besoin étaient

<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>

Sur une autre note, pour simplifier le développement, je viens de créer un simple archétype Maven avec les coordonnées suivantes

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

Vous pouvez également voir Création d'un nouveau projet à partir de l'archétype Maven

23
Paul Samsotha

Dans mon cas, j'ai ajouté la dépendance jersey-media-json-jackson. Ça a marché pour moi.

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.20</version>
</dependency>
1
Raghavendra b

En ce qui concerne le format texte/simple, cela fonctionne-t-il?

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

Pour la sortie JSON, j'ai ces dépendances

    <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>

ainsi qu'une implémentation jaxb. Tout fera l'affaire, j'utilise

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

Je définis également un fournisseur de mappeur d'objets, bien que je ne sois pas sûr à 100% qu'il soit requis (sauf si vous souhaitez personnaliser):

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;
    }
}

De plus, je pense que vous devez enregistrer la fonctionnalité Jackson:

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

Je dois noter que tout est configuré à l'aide de Jersey 2.11.

1
Don Bottstein