Problem with JAX-RS annotated interface between client and service
-------------------------------------------------------------------
Key: CXF-4064
URL: https://issues.apache.org/jira/browse/CXF-4064
Project: CXF
Issue Type: Bug
Components: JAX-RS
Affects Versions: 2.3.3
Environment: $ uname -a
Darwin dented.local 10.8.0 Darwin Kernel Version 10.8.0: Tue Jun 7 16:33:36
PDT 2011; root:xnu-1504.15.3~1/RELEASE_I386 i386
$ java -version
java version "1.6.0_29"
Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-10M3527)
Java HotSpot(TM) Client VM (build 20.4-b02-402, mixed mode)
Reporter: David Liszewski
When one is developing both server and client components, it is highly
desirable to adhere to DRY principles and declare a JAX-RS resource interface
exactly once. The User Guide (Index > RESTful Services > JAX-RS > JAX-RS
Client API) explicitly says that it should be possible to share an annotated
interface between client and server:
{quote}
With the proxy-based API, one can reuse on the client side the interfaces or
even the resource classes which have already been designed for processing the
HTTP requests on the server side (note that a {{cglib}}-nodeps dependency will
need to be available on the classpath for proxies created from concrete
classes). When reused on the client side, they simply act as remote proxies.
{quote}
However, when I modified the basic JAX-RS sample to do exactly that, two (GETs)
of the four methods do not work. In fact, the only modification made was to
refactor the interface with annotations out of the server implementation class,
without even attempting a proxy client.
_Note_: I definitely reproduced this with {{2.3.3}} but I think same occurs in
2.5.x releases.
This is what {{mvn -Pclient}} prints out running {{./samples/jax_rs/basic}}:
{noformat}
Sent HTTP GET request to query customer info
java.io.FileNotFoundException:
http://localhost:9000/customerservice/customers/123
at
sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1434)
at java.net.URL.openStream(URL.java:1010)
at demo.jaxrs.client.Client.main(Client.java:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:291)
at java.lang.Thread.run(Thread.java:680)
Sent HTTP GET request to query sub resource product info
java.io.FileNotFoundException:
http://localhost:9000/customerservice/orders/223/products/323
at
sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1434)
at java.net.URL.openStream(URL.java:1010)
at demo.jaxrs.client.Client.main(Client.java:64)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:291)
at java.lang.Thread.run(Thread.java:680)
Sent HTTP PUT request to update customer info
Response status code: 200
Response body:
Sent HTTP POST request to add customer
Response status code: 200
Response body:
<?xml version="1.0" encoding="UTF-8"
standalone="yes"?><Customer><id>124</id><name>Jack</name></Customer>
{noformat}
The added interface {{ICustomerService.java}} and modified {{Client.java}} and
{{CustomerService.java}} classes are shown below. {{Client.java}} was modified
to catch any exceptions thrown and continue with the next function point:
{code:title=ICustomerService.java|borderStyle=solid}
package demo.jaxrs;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import demo.jaxrs.server.Customer;
import demo.jaxrs.server.Order;
@Path("/customerservice/")
public interface ICustomerService {
@GET
@Path("/customers/{id}/")
public Customer getCustomer(@PathParam("id") String id);
@PUT
@Path("/customers/")
public Response updateCustomer(Customer customer);
@POST
@Path("/customers/")
public Response addCustomer(Customer customer);
@DELETE
@Path("/customers/{id}/")
public Response deleteCustomer(@PathParam("id") String id);
@Path("/orders/{orderId}/")
public Order getOrder(@PathParam("orderId") String orderId);
}
{code}
{code:title=Client.java|borderStyle=solid}
package demo.jaxrs.client;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.FileRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.io.CachedOutputStream;
public final class Client {
private Client() {
}
public static void main(String args[]) {
// Sent HTTP GET request to query all customer info
/*
* URL url = new URL("http://localhost:9000/customers");
* System.out.println("Invoking server through HTTP GET to query all
* customer info"); InputStream in = url.openStream(); StreamSource
* source = new StreamSource(in); printSource(source);
*/
// Sent HTTP GET request to query customer info
try {
System.out.println("Sent HTTP GET request to query customer info");
URL url = new
URL("http://localhost:9000/customerservice/customers/123");
InputStream in = url.openStream();
System.out.println(getStringFromInputStream(in));
} catch (Exception e) {
e.printStackTrace();
}
// Sent HTTP GET request to query sub resource product info
try{
System.out.println("\n");
System.out.println("Sent HTTP GET request to query sub resource
product info");
URL url = new
URL("http://localhost:9000/customerservice/orders/223/products/323");
InputStream in = url.openStream();
System.out.println(getStringFromInputStream(in));
} catch (Exception e) {
e.printStackTrace();
}
// Sent HTTP PUT request to update customer info
System.out.println("\n");
System.out.println("Sent HTTP PUT request to update customer info");
Client client = new Client();
String inputFile =
client.getClass().getResource("update_customer.xml").getFile();
File input = new File(inputFile);
PutMethod put = new
PutMethod("http://localhost:9000/customerservice/customers");
RequestEntity entity = new FileRequestEntity(input, "text/xml;
charset=ISO-8859-1");
put.setRequestEntity(entity);
HttpClient httpclient = new HttpClient();
try {
int result = httpclient.executeMethod(put);
System.out.println("Response status code: " + result);
System.out.println("Response body: ");
System.out.println(put.getResponseBodyAsString());
} catch (Exception e) {
e.printStackTrace();
} finally {
// Release current connection to the connection pool once you are
// done
put.releaseConnection();
}
// Sent HTTP POST request to add customer
System.out.println("\n");
System.out.println("Sent HTTP POST request to add customer");
inputFile = client.getClass().getResource("add_customer.xml").getFile();
input = new File(inputFile);
PostMethod post = new
PostMethod("http://localhost:9000/customerservice/customers");
post.addRequestHeader("Accept" , "text/xml");
entity = new FileRequestEntity(input, "text/xml; charset=ISO-8859-1");
post.setRequestEntity(entity);
httpclient = new HttpClient();
try {
int result = httpclient.executeMethod(post);
System.out.println("Response status code: " + result);
System.out.println("Response body: ");
System.out.println(post.getResponseBodyAsString());
} catch (Exception e) {
e.printStackTrace();
} finally {
// Release current connection to the connection pool once you are
// done
post.releaseConnection();
}
System.out.println("\n");
System.exit(0);
}
private static String getStringFromInputStream(InputStream in) throws
Exception {
CachedOutputStream bos = new CachedOutputStream();
IOUtils.copy(in, bos);
in.close();
bos.close();
return bos.getOut().toString();
}
}
{code}
{code:title=CustomerService.java|borderStyle=solid}
package demo.jaxrs.server;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import demo.jaxrs.ICustomerService;
public class CustomerService implements ICustomerService {
long currentId = 123;
Map<Long, Customer> customers = new HashMap<Long, Customer>();
Map<Long, Order> orders = new HashMap<Long, Order>();
public CustomerService() {
init();
}
public Customer getCustomer(@PathParam("id") String id) {
System.out.println("----invoking getCustomer, Customer id is: " + id);
long idNumber = Long.parseLong(id);
Customer c = customers.get(idNumber);
return c;
}
public Response updateCustomer(Customer customer) {
System.out.println("----invoking updateCustomer, Customer name is: " +
customer.getName());
Customer c = customers.get(customer.getId());
Response r;
if (c != null) {
customers.put(customer.getId(), customer);
r = Response.ok().build();
} else {
r = Response.notModified().build();
}
return r;
}
public Response addCustomer(Customer customer) {
System.out.println("----invoking addCustomer, Customer name is: " +
customer.getName());
customer.setId(++currentId);
customers.put(customer.getId(), customer);
return Response.ok(customer).build();
}
public Response deleteCustomer(@PathParam("id") String id) {
System.out.println("----invoking deleteCustomer, Customer id is: " +
id);
long idNumber = Long.parseLong(id);
Customer c = customers.get(idNumber);
Response r;
if (c != null) {
r = Response.ok().build();
customers.remove(idNumber);
} else {
r = Response.notModified().build();
}
return r;
}
public Order getOrder(@PathParam("orderId") String orderId) {
System.out.println("----invoking getOrder, Order id is: " + orderId);
long idNumber = Long.parseLong(orderId);
Order c = orders.get(idNumber);
return c;
}
final void init() {
Customer c = new Customer();
c.setName("John");
c.setId(123);
customers.put(c.getId(), c);
Order o = new Order();
o.setDescription("order 223");
o.setId(223);
orders.put(o.getId(), o);
}
}
{code}
Here is a {{diff}} output, should that be more helpful:
{noformat}
$ diff -r basic/src basic_interface/src
Only in basic_interface/src/demo/jaxrs: ICustomerService.java
diff -r basic/src/demo/jaxrs/client/Client.java
basic_interface/src/demo/jaxrs/client/Client.java
40c40
< public static void main(String args[]) throws Exception {
---
> public static void main(String args[]) {
50,53c50,57
< System.out.println("Sent HTTP GET request to query customer info");
< URL url = new
URL("http://localhost:9000/customerservice/customers/123");
< InputStream in = url.openStream();
< System.out.println(getStringFromInputStream(in));
---
> try {
> System.out.println("Sent HTTP GET request to query customer
> info");
> URL url = new
> URL("http://localhost:9000/customerservice/customers/123");
> InputStream in = url.openStream();
> System.out.println(getStringFromInputStream(in));
> } catch (Exception e) {
> e.printStackTrace();
> }
56,60c60,68
< System.out.println("\n");
< System.out.println("Sent HTTP GET request to query sub resource
product info");
< url = new
URL("http://localhost:9000/customerservice/orders/223/products/323");
< in = url.openStream();
< System.out.println(getStringFromInputStream(in));
---
> try{
> System.out.println("\n");
> System.out.println("Sent HTTP GET request to query sub resource
> product info");
> URL url = new
> URL("http://localhost:9000/customerservice/orders/223/products/323");
> InputStream in = url.openStream();
> System.out.println(getStringFromInputStream(in));
> } catch (Exception e) {
> e.printStackTrace();
> }
72d79
<
77a85,86
> } catch (Exception e) {
> e.printStackTrace();
99a109,110
> } catch (Exception e) {
> e.printStackTrace();
105d115
<
diff -r basic/src/demo/jaxrs/server/CustomerService.java
basic_interface/src/demo/jaxrs/server/CustomerService.java
32,33c32,34
< @Path("/customerservice/")
< public class CustomerService {
---
> import demo.jaxrs.ICustomerService;
>
> public class CustomerService implements ICustomerService {
42,43d42
< @GET
< @Path("/customers/{id}/")
51,52d49
< @PUT
< @Path("/customers/")
67,68d63
< @POST
< @Path("/customers/")
78,79d72
< @DELETE
< @Path("/customers/{id}/")
96d88
< @Path("/orders/{orderId}/")
{noformat}
--
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