[ 
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

        

Reply via email to