[ 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