REST service that accepts and returns object. How to write client?
Asked Answered
R

2

13

I have declared two REST web services. One which simply returns a object. And other which accepts an object and returns another object. POJO Order.java is used.

@XmlRootElement
public class Order {

   private String id;

   private String description;

   public Order() {
   }

   @XmlElement
   public String getId() {
          return id;
   }
   @XmlElement
   public String getDescription() {
          return description;
   }

    // Other setters and methods
}

Webservice is defined as

@Path("/orders")
public class OrdersService {
// Return the list of orders for applications with json or xml formats
@Path("/oneOrder")
@GET
@Produces({MediaType.APPLICATION_JSON})
public  Order getOrder_json() {
  System.out.println("inside getOrder_json");
  Order o1 = OrderDao.instance.getOrderFromId("1");
  System.out.println("about to return one order");
  return o1;
}

@Path("/writeAndIncrementOrder")
@GET
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})
public  Order writeAndIncrementOrder(Order input) {
  System.out.println("inside writeAndIncrementOrder");
  Order o1 = new Order();
  o1.setId(input.getId()+1000);
  o1.setDescription(input.getDescription()+"10000");
  System.out.println("about to return one order");
  return o1;
 }

I could write client code to call the web service that does not accept anything but returns object. Client code is as follows

import java.net.URI;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.jersey.client.ClientConfig;

public class Test {

public static void main(String[] args) {

WebTarget target2 = client.target(getBaseURI()).path("rest").path("orders");
String o2 = target2.path("oneOrder").request().accept(MediaType.APPLICATION_JSON).get(String.class);
        System.out.println(o2);
}

private static URI getBaseURI() {
    return UriBuilder.fromUri("http://localhost:8090/FirstRESTProject").build();
  }

But I do not understand how to call other service which accepts as well as returns object. I tried different solutions given on internet. But nothing worked for me. Some solution works only for sending object and some works only for accepting. But none worked for doing both in one call.

EDIT As suggested in below answer I registered JacksonJaxbJsonProvider.class But auto-conversion into Order object is not happening.

        String o2 = target2.path("oneOrder").request().accept(MediaType.APPLICATION_JSON).get(String.class);

        client.register(JacksonJaxbJsonProvider.class);
        Order o4 = target2.path("oneOrder").request().accept(MediaType.APPLICATION_JSON).get(Order.class);

In above program I successfully get string as {"id":"1","description":"This is the 1st order"} But getting direct object throws error MessageBodyReader not found for media type=application/json, type=class shopping.cart.om.Order, genericType=class shopping.cart.om.Order.

Rhizotomy answered 14/3, 2015 at 8:0 Comment(7)
The current code you have for your client is for Jersey/JAX-RS 2, but your web.xml implies you are using Jersey 1. What's the deal? Are the server and client the same project? If so, you might want to make some changes to your dependencies. If you need help with that, please show your dependencies, and specify whether you want to use Jersey 1 or 2. The two don't play well together in the same project. Even if you are only using 2 for the client.Ez
I am really confused. Every suggestion I get I try to modify code. Then I have to add couple of jars. Now my client and server both have many jars. I do not know which all needed and which all is correct combination.Rhizotomy
Are you using Maven? And which version of Jersey are you trying to use?Ez
Try target2.register(JacksonJaxbJsonProvider.class)Ez
I am not using Maven. I am creating projects in Eclipse.And both are different projects.Rhizotomy
I strongly suggest learning Maven. With maven, all you need to do is add two dependencies, and it will pull in all required 30 or so jars. Better then guessing and running into jar hell.Ez
@peeskillet thanks for your suggestions. Switching to Maven had some initial glitches. But once I overcame them; it was far easy to create and use REST services. Below answer given by Dmitry worked very wellRhizotomy
E
17

If you take a little bit of time to understand the WebTarget API, as well as the different types returned from calls to WebTarget's method, you should get a better understanding of how to make calls. It may be a little confusing, as almost all the example use method chaining, as it's a very convenient way, but doing this, you miss all the actual classes involved in create and sending the request. Let break it down a bit

WebTarget target = client.target(getBaseURI()).path("rest").path("orders");

WebTarget.path() simply returns the WebTarget. Nothing interesting there.

target.path("oneOrder").request().accept(MediaType.APPLICATION_JSON).get(String.class)

  • WebTarget.request() returns Invocation.Builder
  • Invocation.Builder.accept(..) returns Invocation.Builder
  • Invocation.Builder.get() calls its super class's SyncInvoker.get(), which makes the actual request, and returns a type, based on the argument we provide to get(Class returnType)

What you're doing with get(String.class) is saying that the response stream should be deserialized into a Sting type response. This is not a problem, as JSON is inherently just a String. But if you want to unmarshal it to a POJO, then you need to have a MessageBodyReader that knows how to unmarshal JSON to your POJO type. Jackson provides a MessageBodyReader in it's jackson-jaxrs-json-provider dependency

<dependency>
  <groupId>com.fasterxml.jackson.jaxrs</groupId>
  <artifactId>jackson-jaxrs-json-provider</artifactId>
  <version>2.4.0</version>
</dependency>

Most implementations will provider a wrapper for this module, like jersey-media-json-jackson for Jersey or resteasy-jackson-provider for Resteasy. But they are still using the underlying jackson-jaxrs-json-provider.

That being said, once you have that module on the classpath, is should be automatically registered, so the MessageBodyReader will be available. If not you can register it explicitly with the client, like client.register(JacksonJaxbJsonProvider.class). Once you have the Jackson support configured, then you can simply do something like

MyPojo myPojo = client.target(..).path(...).request().accept(..).get(MyPojo.class);

As for posting/sending data, you can again look at the different Invocation.Builder methods. For instance

Invocation.Builder builder = target.request();

If we want to post, look at the different post methods available. We can use

  • Response post(Entity<?> entity) - Our request might look something like

    Response response = builder.post(Entity.json(myPojo));
    

    You'll notice the Entity. All the post methods accept an Entity, and this is how the request will know what type the entity body should be, and the client will invoke the approriate MessageBodyWriter as well as set the appropriate header

  • <T> T post(Entity<?> entity, Class<T> responseType) - There's another overload, where we can specify the type to unmarshal into, instead of getting back a Response. We could do

    MyPojo myPojo = builder.post(Entity.json(myPojo), MyPojo.class)
    

Note that with Response, we call its readEntity(Class pojoType) method to read from the Response, the entity body. The advantage of this, is that the Response object comes with a lot of useful information we can use, like headers and such. Personally, I always get the Response

    Response response = builder.get();
    MyPojo pojo = response.readEntity(MyPojo.class);

As an aside, for your particular code you are showing, you most likely want to make it a @POST method. Remember @GET is mainly for retrieving data, PUT for updating, and POST for creating. That is a good rule of thumb to stick to, when first starting out. So you might change the method to

@Path("orders")
public class OrdersResource  {

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes({MediaType.APPLICATION_JSON})
    public Response createOrder(@Context UriInfo uriInfo, Order input) {
       Order order = orderService.createOrder(input);
       URI uri = uriInfo.getAbsolutePathBuilder().path(order.getId()).build();
       return Response.create(uri).entity(order).build();
    }
}

Then you can do

WebTarget target = client.target(BASE).path("orders");
Response response = target.request().accept(...).post(Entity.json(order));
Order order = response.readEntity(Order.class);
Ez answered 14/3, 2015 at 9:27 Comment(2)
thanks for a very detailed reply touching at concept level. I will take some time to understand the above concepts and will come back with my results. Thanks.Rhizotomy
Note the above is for Jersey/JAX-RS 2 client, which your original code is using. But now after your post of your web.xml and your exception, it appears you are using Jersey 1. So I am completely confused. For further help, I'll need some clarification. See my comment for the original postEz
S
4

You should use POST or PUT instead GET

try this code

final Client client = new Client();
    final Order input = new Order();
    input.setId("1");
    input.setDescription("description");
    final Order output = client.resource(
"http://localhost:8080/orders/writeAndIncrementOrder").
            header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON).
            entity(input).post(Order.class);
Schoolman answered 14/3, 2015 at 8:30 Comment(5)
I used com.sun.jersey.api.client.ClientSchoolman
I tried your suggestion. But it is throwing exception Caused by: com.sun.jersey.api.client.ClientHandlerException: A message body writer for Java type, class shopping.cart.om.Order, and MIME media type, application/json, was not foundRhizotomy
It looks like client side error. As even if my Server is stopped I still get same error. It means it does not even reach server side.Rhizotomy
try add <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-json</artifactId> <version>1.8</version> </dependency> to your pom.xml file set version to actual jersey versionSchoolman
thanks for your answer. Finally I could get the working client. I had to shift to maven as I was getting trapped into jungle of jars.Rhizotomy

© 2022 - 2024 — McMap. All rights reserved.