Martin GOYOT Martin GOYOT - 3 months ago 29
Java Question

Action composition and async in Play Framework 2.5 in Java

I want to create an action that I can use with the

@With
annotation style. This action will need to proceed to an RPC call so if I understood correctly the documentation I should rather put this in an async way.

This is what I tried to do until now:

public class GetUserIdAction extends play.mvc.Action.Simple {

@Override
public CompletionStage<Result> call(Http.Context context) {
String token = "";

if (StringUtils.isEmpty(token)) {
return delegate.call(context);
}

CompletionStage<Http.Context> promiseOfUpdatedContext = CompletableFuture.supplyAsync(() -> setUserIdForToken(context, token));

return promiseOfUpdatedContext.thenApply(ctx -> delegate.call(ctx));
}

private Http.Context setUserIdForToken(Http.Context context, String token) {
context.args.put("user_id", authenticationManager.getUserIdForToken(token));
// The AuthenticationManager is issuing an RPC call and thus may take some time to complete.
return context;
}
}


Set aside the fact that
token
is always empty and
authenticationManager
is not set, this is just a quick meaningless example, my IDE is complaining on the
thenApply
part. For what I understand, it is expecting a
CompletionStage<Result>
and gets something more like a
CompletionStage<CompletionStage<Result>>
.

What is a way to deal with it? Cause here all I want is to put some information in the Context and then continue the
delegate.call
chain.

Or maybe I'm trying to do something stupid and composed actions are already asynchronous?

Answer

You have a CompletionStage<Something> and want to end with a CompletionStage<Result>. The easiest way to achieve that is using thenCompose.

Here is an example, with a small change: I have a CompletableFuture to get the token and only then I add it to the HttpContext

@Override
public CompletionStage<Result> call(final Http.Context context) {
    final String token = "";

    if (StringUtils.isEmpty(token)) {
        return delegate.call(context);
    }

    return CompletableFuture.supplyAsync(() -> {
        // do something to fetch that token
        return "your_new_token";
    }).thenCompose(tokenReceived -> {
        context.args.put("user_id", tokenReceived);
        return delegate.call(context);
    });
}