cxf-issues mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Marko Voss (Issue Comment Edited) (JIRA)" <j...@apache.org>
Subject [jira] [Issue Comment Edited] (CXF-4205) Please add generic type to javax.ws.rs.core.Response implementation
Date Tue, 27 Mar 2012 15:24:28 GMT

    [ https://issues.apache.org/jira/browse/CXF-4205?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13239545#comment-13239545
] 

Marko Voss edited comment on CXF-4205 at 3/27/12 3:22 PM:
----------------------------------------------------------

Hello Sergey,

I could not live with the fact, that the client has to guess the return type and to perform
castings. I would prefer the client-side code to break, if the server-side interfaces change.
I took a look at the ResponseReader and noticed that it is not thread-safe if you use the
same JAX-RS interface "instance" created by the JAXRSClientFactory. Example:

@Path("/a")
public interface Foo {

  @Path("/{id}")
  @ElementClass(response = JaxbObj_A.class)
  Response retrieve_A(@PathParam("id") String id);

  @Path("/{id}/b")
  @ElementClass(response = JaxbObj_B.class)
  Response retrieve_B(@PathParam("id") String id);
}


Client-side code - first approach:
----------------------------------

public class MyHandler {

  private final URL serviceAddress;

  public MyHandler(final URL serviceAddress) {
    this.serviceAddress = serviceAddress;
  }

  public JaxbObj_A retrieve_A(final String id) {
    // we have to initialize the JAXRS-service for every method to ensure thread-safety
    ResponseReader r = new ResponseReader(JaxbObj_A.class);
    JaxbObj_A jaxbObj = (JaxbObj_A)JAXRSClientFactory.create(serviceAddress.toString(), Foo.class,
Collections.singletonList(r)).retrieve_A(id);
  }

  public JaxbObj_A retrieve_A(final String id) {
    // we have to initialize the JAXRS-service for every method to ensure thread-safety
    ResponseReader r = new ResponseReader(JaxbObj_B.class);
    JaxbObj_B jaxbObj = (JaxbObj_B)JAXRSClientFactory.create(serviceAddress.toString(), Foo.class,
Collections.singletonList(r)).retrieve_B(id);
  }
}

With this approach we are thread-safe as also shown on http://cxf.apache.org/docs/jax-rs-client-api.html.

As said, I liked to avoid this and did the following changes. First, I implemented a GenericResponse
extending the Response and then I wrote a GenericResponseReader supplied by the server for
the client.


Step 1: Implementation of the GenericResponse:
----------------------------------------------

public class GenericResponse<T> extends Response {

    private Response response;

    public GenericResponse(final ResponseBuilder responseBuilder, final T entityObject) {
        /*
        set the entity here to ensure, that the entity is of type T (no matter if the user
already put the entity into
        the responseBuilder.
         */
        this.response = responseBuilder.clone().entity(entityObject).build();
    }

    @Override
    public T getEntity() {
        return (T)this.response.getEntity();
    }

    @Override
    public int getStatus() {
        return this.response.getStatus();
    }

    @Override
    public MultivaluedMap<String, Object> getMetadata() {
        return this.response.getMetadata();
    }
}


Step 2: Adjust the JAX-RS interface:
------------------------------------

@Path("/a")
public interface Foo {

  @Path("/{id}")
  @ElementClass(response = JaxbObj_A.class)
  GenericResponse<JaxbObj_A> retrieve_A(@PathParam("id") String id);

  @Path("/{id}/b")
  @ElementClass(response = JaxbObj_B.class)
  GenericResponse<JaxbObj_B> retrieve_B(@PathParam("id") String id);
}

Step 3: Implementation of the GenericResponseReader for the client:
-------------------------------------------------------------------

public class GenericResponseReader implements MessageBodyReader<Response> {

    @Context
    private MessageContext context;

    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
MediaType mediaType) {
        return Response.class.isAssignableFrom(type);
    }

    public Response readFrom(Class<Response> type, Type genericType, Annotation[] annotations,
MediaType mediaType,
                             MultivaluedMap<String, String> httpHeaders, InputStream
entityStream)
            throws IOException, WebApplicationException {

        int status = Integer.valueOf(getContext().get(Message.RESPONSE_CODE).toString());

        Response.ResponseBuilder rb = Response.status(status);

        for (String header : httpHeaders.keySet()) {
            List<String> values = httpHeaders.get(header);
            for (String value : values) {
                rb.header(header, value);
            }
        }

        if (genericType != null && genericType instanceof ParameterizedType) {
            ParameterizedType p = ((ParameterizedType) genericType);
            if (p.getActualTypeArguments() != null && p.getActualTypeArguments().length
> 0) {
                Type genType = p.getActualTypeArguments()[0];

                Providers providers = getContext().getProviders();
                MessageBodyReader<?> reader =
                        providers.getMessageBodyReader((Class)genType, genType, annotations,
mediaType);
                if (reader == null) {
                    throw new ClientWebApplicationException("No reader for Response entity
"
                            + genType.getClass().getName());
                }
                Object entity = reader.readFrom((Class) genType, genType, annotations, mediaType,
httpHeaders, entityStream);
                return new GenericResponse(rb, entity);
            }
        }
        return null; // TODO
    }

    protected MessageContext getContext() {
        return context;
    }
}

Step 4: Client-side adjustments:
--------------------------------

- We need one instance of the GenericResponseReader only in contrast to the ResponseReader
- Thread-safety (if I am correct on this here)
- We need one instance of the JAX-RS interface created by the JAXRSClientFactory only in contrast
of the previous approach.

Code:

public class MyHandler {

  private final Foo jaxrsService;

  public MyHandler(final URL serviceAddress) {
    this.jaxrsService = JAXRSClientFactory.create(serviceAddress.toString(), Foo.class, Collections.singletonList(new
GenericResponseReader()))
  }

  public JaxbObj_A retrieve_A(final String id) {
    GenericResponse<JaxbObj_A> response = jaxrsService.retrieve_A(id);
    return response.getEntity();
  }

  public JaxbObj_B retrieve_B(final String id) {
    GenericResponse<JaxbObj_B> response = jaxrsService.retrieve_B(id);
    return response.getEntity();
  }
}


Et voila! It works. :-)

- On server-side, the CXF will handle the GenericResponse like the normal Response. No need
to do anything. (I hope so; Did not notice any faults)
- On client-side, one has to use the GenericResponseReader as a provider.

However, I am not sure, if I covered all cases of generic types in the GenericResponseReader.

We are using CXF version 2.5.1.

So this is basically, what I was hoping to get from my Jira issue. :-)

I will attach the two classes "GenericResponse" and "GenericResponseReader" to this issue.
Maybe you like to add them to CXF.
                
      was (Author: marko voss):
    Hello Sergey,

I could not live with the fact, that the client has to guess the return type and to perform
castings. I would prefer the client-side code to break, if the server-side interfaces change.
I took a look at the ResponseReader and noticed that it is not thread-safe if you use the
same JAX-RS interface "instance" created by the JAXRSClientFactory. Example:

@Path("/a")
public interface Foo {

  @Path("/{id}")
  @ElementClass(response = JaxbObj_A.class)
  Response retrieve_A(@PathParam("id") String id);

  @Path("/{id}/b")
  @ElementClass(response = JaxbObj_B.class)
  Response retrieve_B(@PathParam("id") String id);
}


Client-side code - first approach:
----------------------------------

public class MyHandler {

  private final URL serviceAddress;

  public MyHandler(final URL serviceAddress) {
    this.serviceAddress = serviceAddress;
  }

  public JaxbObj_A retrieve_A(final String id) {
    // we have to initialize the JAXRS-service for every method to ensure thread-safety
    ResponseReader r = new ResponseReader(JaxbObj_A.class);
    JaxbObj_A jaxbObj = (JaxbObj_A)JAXRSClientFactory.create(serviceAddress.toString(), Foo.class,
Collections.singletonList(r)).retrieve_A(id);
  }

  public JaxbObj_A retrieve_A(final String id) {
    // we have to initialize the JAXRS-service for every method to ensure thread-safety
    ResponseReader r = new ResponseReader(JaxbObj_B.class);
    JaxbObj_B jaxbObj = (JaxbObj_B)JAXRSClientFactory.create(serviceAddress.toString(), Foo.class,
Collections.singletonList(r)).retrieve_B(id);
  }
}

With this approach we are thread-safe as also shown on http://cxf.apache.org/docs/jax-rs-client-api.html.

As said, I liked to avoid this and did the following changes. First, I implemented a GenericResponse
extending the Response and then I wrote a GenericResponseReader supplied by the server for
the client.


Step 1: Implementation of the GenericResponse:
----------------------------------------------

public class GenericResponse<T> extends Response {

    private Response response;

    public GenericResponse(final ResponseBuilder responseBuilder, final T entityObject) {
        /*
        set the entity here to ensure, that the entity is of type T (no matter if the user
already put the entity into
        the responseBuilder.
         */
        this.response = responseBuilder.clone().entity(entityObject).build();
    }

    @Override
    public T getEntity() {
        return (T)this.response.getEntity();
    }

    @Override
    public int getStatus() {
        return this.response.getStatus();
    }

    @Override
    public MultivaluedMap<String, Object> getMetadata() {
        return this.response.getMetadata();
    }
}


Step 2: Adjust the JAX-RS interface:
------------------------------------

@Path("/a")
public interface Foo {

  @Path("/{id}")
  @ElementClass(response = JaxbObj_A.class)
  GenericResponse<JaxbObj_A> retrieve_A(@PathParam("id") String id);

  @Path("/{id}/b")
  @ElementClass(response = JaxbObj_B.class)
  GenericResponse<JaxbObj_B> retrieve_B(@PathParam("id") String id);
}

Step 3: Implementation of the GenericResponseReader for the client:
-------------------------------------------------------------------

public class GenericResponseReader implements MessageBodyReader<Response> {

    @Context
    private MessageContext context;

    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
MediaType mediaType) {
        return Response.class.isAssignableFrom(type);
    }

    public Response readFrom(Class<Response> type, Type genericType, Annotation[] annotations,
MediaType mediaType,
                             MultivaluedMap<String, String> httpHeaders, InputStream
entityStream)
            throws IOException, WebApplicationException {

        int status = Integer.valueOf(getContext().get(Message.RESPONSE_CODE).toString());

        Response.ResponseBuilder rb = Response.status(status);

        for (String header : httpHeaders.keySet()) {
            List<String> values = httpHeaders.get(header);
            for (String value : values) {
                rb.header(header, value);
            }
        }

        if (genericType != null && genericType instanceof ParameterizedType) {
            ParameterizedType p = ((ParameterizedType) genericType);
            if (p.getActualTypeArguments() != null && p.getActualTypeArguments().length
> 0) {
                Type genType = p.getActualTypeArguments()[0];

                Providers providers = getContext().getProviders();
                MessageBodyReader<?> reader =
                        providers.getMessageBodyReader((Class)genType, genType, annotations,
mediaType);
                if (reader == null) {
                    throw new ClientWebApplicationException("No reader for Response entity
"
                            + genType.getClass().getName());
                }
                Object entity = reader.readFrom((Class) genType, genType, annotations, mediaType,
httpHeaders, entityStream);
                return new GenericResponse(rb, entity);
            }
        }
        return null; // TODO
    }

    protected MessageContext getContext() {
        return context;
    }
}

Step 4: Client-side adjustments:
--------------------------------

- We need one instance of the GenericResponseReader only in contrast to the ResponseReader
- Thread-safety (if I am correct on this here)
- We need one instance of the JAX-RS interface created by the JAXRSClientFactory only in contrast
of the previous approach.

Code:

public class MyHandler {

  private final Foo jaxrsService;

  public MyHandler(final URL serviceAddress) {
    this.jaxrsService = JAXRSClientFactory.create(serviceAddress.toString(), Foo.class, Collections.singletonList(new
GenericResponseReader()))
  }

  public JaxbObj_A retrieve_A(final String id) {
    GenericResponse<JaxbObj_A> response = jaxrsService.retrieve_A(id);
    return response.getEntity();
  }

  public JaxbObj_A retrieve_A(final String id) {
    GenericResponse<JaxbObj_B> response = jaxrsService.retrieve_B(id);
    return response.getEntity();
  }
}


Et voila! It works. :-)

- On server-side, the CXF will handle the GenericResponse like the normal Response. No need
to do anything. (I hope so; Did not notice any faults)
- On client-side, one has to use the GenericResponseReader as a provider.

However, I am not sure, if I covered all cases of generic types in the GenericResponseReader.

We are using CXF version 2.5.1.

So this is basically, what I was hoping to get from my Jira issue. :-)

I will attach the two classes "GenericResponse" and "GenericResponseReader" to this issue.
Maybe you like to add them to CXF.
                  
> Please add generic type to javax.ws.rs.core.Response implementation
> -------------------------------------------------------------------
>
>                 Key: CXF-4205
>                 URL: https://issues.apache.org/jira/browse/CXF-4205
>             Project: CXF
>          Issue Type: Improvement
>          Components: JAX-RS
>    Affects Versions: 2.5.2
>            Reporter: Marko Voss
>            Assignee: Sergey Beryozkin
>         Attachments: GenericResponse.java, GenericResponseReader.java
>
>
> Let's assume, we have the following JAX-RS interface:
> @Path("/foo")
> public interface Foo {
>   @Path("{id})
>   JaxbObj retrieve(@PathParam("id");
> }
> Now, we want to change some headers for the response, so we have to change the interface
to this:
> @Path("/foo")
> public interface Foo {
>   @Path("{id})
>   Response retrieve(@PathParam("id");
> }
> In our scenario, we want to offer the customers a basic client library, so that they
do not need to implement mapping and everything again. Therefore we are reusing the JAX-RS
interfaces on the client-side. Thanks to the maven dependency techniques the client library
will also inherit the generated JAXB classes, CXF setup and everything else, the client requires
to communicate with the server.
> So the client will now have to deal with the Object supplied by the Response.getEntity()
method and kinda have to guess the type. If the Response type would be generic, there would
not be such an issue. (example: Response<JaxbObj>)
> Since you may not be responsible for the Response interface, maybe you could add an extended
interface or implementation.

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators: https://issues.apache.org/jira/secure/ContactAdministrators!default.jspa
For more information on JIRA, see: http://www.atlassian.com/software/jira

        

Mime
View raw message