[
https://issues.apache.org/jira/browse/CXF-4205?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13239545#comment-13239545
]
Marko Voss commented on CXF-4205:
---------------------------------
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
>
> 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