mulesoft-labs / raml-for-jax-rs

This project is all about two way transformation of JAX-RS-annotated Java code to RAML API description and back.
Other
296 stars 181 forks source link

Generated client proxy is not working with Jersey #439

Closed amreladawy closed 4 years ago

amreladawy commented 4 years ago

Using Jersey client with the generated code. It is failing to deserialise the response with the error

java.lang.IllegalArgumentException: Cannot find a deserializer for non-concrete Map type [map type; class javax.ws.rs.core.MultivaluedMap, [simple type, class java.lang.String] -> [collection type; class java.util.List, contains [simple type, class java.lang.String]]]
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.createMapDeserializer(BasicDeserializerFactory.java:1358)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:387)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)

The generated resource is as


@Path("/orders")
public interface Orders {
  @POST
  @Produces("application/json")
  @Consumes("application/json")
  PostOrdersResponse postOrders(Order entity);

The responseDelegate is

 class PostOrdersResponse extends ResponseDelegate {
    private PostOrdersResponse(Response response, Object entity) {
      super(response, entity);
    }

    private PostOrdersResponse(Response response) {
      super(response);
    }

    public static HeadersFor201 headersFor201() {
      return new HeadersFor201();
    }

    public static PostOrdersResponse respond201WithApplicationJson(Order entity,
        HeadersFor201 headers) {
      Response.ResponseBuilder responseBuilder = Response.status(201).header("Content-Type", "application/json");
      responseBuilder.entity(entity);
      headers.toResponseBuilder(responseBuilder);
      return new PostOrdersResponse(responseBuilder.build(), entity);
    }

The response is as

{  "id":"73222344623" }

It turned out that Jackson is trying to deserialise this response as the return type of the postOrders method and it fails.

It's worth noting that changing the signature of the postOrders as following will pass


@Path("/orders")
public interface Orders {
  @POST
  @Produces("application/json")
  @Consumes("application/json")
  Order postOrders(Order entity);
jpbelang commented 4 years ago

Are you using the client or server ? You have me really confused, because you are showing me server stubs. Is your project accessible somewhere ?

If you are using servers, I have examples in the project, like here https://github.com/mulesoft-labs/raml-for-jax-rs/blob/master/raml-to-jaxrs/examples/maven-examples/raml-defined-example/src/main/java/server/humanity/StartServer.java

If you are using this for the client (I've used the Jersey client a couple of times but not like you are using it), the response objects will certainly get in the way, because they are to be used by the server.

If you want to suppress the response types to see if that could help

                    <generateTypesWith>
                        <value>simpleResponseObjects</value>
                        <value>jackson2</value>
                        <value>equalsAndHashCode</value>
                    </generateTypesWith>
amreladawy commented 4 years ago

Sorry about the confusion. I am using client stub.

Using simpleResponseObjects changed the generated signature as following


@Path("/orders")
public interface Orders {
  @POST
  @Produces("application/json")
  @Consumes("application/json")
  OrderSuccess postOrders(Order entity, @Context HttpServletResponse httpServletResponse);
}

How can I provide the HttpServletResponse? Actually, why do I have to? Removing HttpServletResponse from the signature works fine.

amreladawy commented 4 years ago

@jpbelang can I generate the client without HttpServletResponse in the methods' signature?

jpbelang commented 4 years ago

Short answer is no, not natively. The simpleResponseObjects plugin is the closes I have. Unless you write your own plugin.

You could do this by starting from raml-to-jaxrs/jaxrs-code-generator/src/main/java/org/raml/jaxrs/generator/extension/resources/SimpleResponseObjectExtension.java fixing what you need fixed. If you need help doing that, I'm more than willing to help...

amreladawy commented 4 years ago

Can I build a custom simpleResponseObject plugin in my code and it will be picked up and used by the maven plugin?

What are the steps to do so?

I would prefer to extend the raml-for-jax-rs with a custom plugin instead of checking out the whole project and keep maintaining a separate branch.

jpbelang commented 4 years ago

Sorry, we were on holiday yesterday. I'll come back at noon with a quick example and some instructions. Short answers: Yes you can, and it can (should?) be an extension that lives in your project.....

jpbelang commented 4 years ago

There is an example project in the RAML for jaxrs project. You need to imagine that this is your project and not part of RAML for jaxrs.

https://github.com/mulesoft-labs/raml-for-jax-rs/tree/master/raml-to-jaxrs/examples/maven-examples/features

The plugins directory is where the plugin lives. The feature directory is where the actual project lives. You should start from the simple response plugin, because it's pretty close to the plugin you want. Just copy it from raml-to-jaxrs/jaxrs-code-generator/src/main/java/org/raml/jaxrs/generator/extension/resources/SimpleResponseObjectExtension.java into your own project, rename it and adjust. I think removing the @Context annotation should be almost enough.

After that there is one thing you need to be careful with: You plugin project needs to contain a ramltojaxrs-plugin.properties. Look at raml-to-jaxrs/jaxrs-code-generator/src/main/resources/META-INF/ramltojaxrs-plugin.properties (the features project has it wrong for what you want to do...). raml for jaxrs uses this file to discover your plugin.

amreladawy commented 4 years ago

Thank you @jpbelang for your help and detailed instructions. I will follow these instructions and will update with the progress.

amreladawy commented 4 years ago

@jpbelang that helped a lot. I will close the issue.

jpbelang commented 4 years ago

Just so you know, it's also possible to plug into the type generation part of the system.