Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play Framework 2 - Call Web Service and return an object

I have a model which I want to populate with details from a web service. I'd like to do this asynchronously so as not to block a server thread. Let's pretend it's a login service.

Now what I want to do is fire a request to the remote server and ultimately return a User model object. So the method signature would look something like this:

public static User loginUser(String username, String password) {

I understand that to make an asynchronous call to a web service I should use a Promise:

Promise<WS.Response> wsPromise = WS.url("http://myserver.com/login")
            .setContentType("application/json; charset=utf-8")
            .post("... the username and password ...");

Which doesn't start the request yet. I could call get() on this object to make a blocking call to the service. This works.

To do this asynchronously, I thought I'd need to map this and somehow execute it.

Promise<User> resultPromise = wsPromise.map(new F.Function<WS.Response, User>() {
    @Override
    public User apply(WS.Response response) throws Throwable {
        System.out.println(response.getBody());
        return new User(... based on something extracted from the returned JSON ...);
    }
});

Now how do I trigger this operation? If I call get() on the resultPromise, it makes the call but eventually fires a Timeout Exception. I can't use the async(...) method because that only returns me a Result.

Looking at other examples (https://github.com/jroper/play-promise-presentation/blob/master/src/main/java/controllers/Application.java), this seems to be the pattern. i.e. We always want to return a Result object. However, I can't query a Result object and I have no plan to send that specific object out to the user.

In fact, those examples seem to call a web service, map the JSON result to an object and then immediately map them back to the same JSON. Not much use when I want to pass the User (in my case) back to the calling function.

To be honest, I'm a little confused with the asynchronous nature of this anyway (you probably guessed that). In particular, this is really a blocking action as we have to wait for the web service to return a response. The documentation seems to indicate that using the Promise / Future patterns will avoid this blocking.

Bottom line is: How do I map the result of a web service call back to a model object without blocking a thread in the Play Framework server?

Feel free to abuse my lack of experience of the Play Framework...

like image 950
Steve N Avatar asked Oct 22 '22 07:10

Steve N


1 Answers

This answer might come way too late, but will post it in case someone else nowadays still wonder the same and want an answer to the question.

There are two ways to achieve this, even though both make use of the same JsonNode class:

  1. The first one is what you were expecting: "new User( ..something...)"

In this case you can make use of JsonNode's "get" method to obtain the specific data you need to create the User object (username and email in my example since you didn't specify the required fields).

Promise<User> resultPromise = wsPromise.map(new F.Function<WS.Response, User>() {
    @Override
    public User apply(WS.Response response) throws Throwable {
        System.out.println(response.getBody());
        JsonNode json = response.asJson();
        return new User(json.get("username"), json.get("email"));
    }
});
  1. The other option is in case you know the Web Service does return a valid Json representation of an User object.

Personally I think this option is way easier and relys on fromJson's method from Json class.

Promise<User> resultPromise = wsPromise.map(new F.Function<WS.Response, User>() {
    @Override
    public User apply(WS.Response response) throws Throwable {
        System.out.println(response.getBody());
        return Json.fromJson(response.asJson(), User.class);
    }
});

I hope this answer can help people wondering how to do this in a easy way.

like image 51
Santiago Alzate Avatar answered Nov 03 '22 05:11

Santiago Alzate