Rethrowing an exception inside of exceptionally
method of CompletionStage seems not to be allowed.
I need to check for a certain kind of exception and if not I need to rethrow it back :
Future<JsonNode> futureSite = someClient.getSite(siteId, queryParams);
CompletionStage<JsonNode> outcome = FutureConverters.toJava(futureSite);
return outcome.thenApplyAsync((siteJson) -> {
Site site = Json.fromJson(siteJson, Site.class);
try {
return function.apply(site);
} catch (RequestException e) {
return e.result;
}
}, httpExecutionContext.current()).exceptionally(throwable -> {
if(throwable instanceof SomeClientException) {
if(((SomeClientException) throwable).httpStatusCode == 404) {
return entityNotFound("Site", siteId);
}
}
// let JSON parsing failures and other errors bubble up, TODO play2.5
throw throwable;
});
throw throwable
errors out saying unhandledException java.lang.Throwable
What interface would possibly allow to rethrow back the exception ? Or is there a better way around ?
Update :
I tried with advice from Holger as below, but I am still not sure at what point could I actually rethrow this :
BaseController.java :
protected class RequestException extends Exception {
private static final long serialVersionUID = -2154871100650903869L;
public Result result;
public RequestException(Result result) {
this.result = result;
}
}
@FunctionalInterface
protected interface RequestFunction<T, R> {
R apply(T t) throws RequestException;
}
protected CompletionStage<Result> performWithSite(final Long siteId, RequestFunction<Site, Result> function) {
QueryParams queryParams = QueryParams.create();
Future<JsonNode> futureSite = someClient.getSite(siteId, queryParams);
CompletionStage<JsonNode> outcome = FutureConverters.toJava(futureSite);
return handleSpecific(
outcome.thenApplyAsync(siteJson -> {
Site site = Json.fromJson(siteJson, Site.class);
try {
return function.apply(site);
} catch (RequestException e) {
return e.result;
}
}, httpExecutionContext.current()),
throwable -> throwable instanceof SomeClientException
&& ((SomeClientException)throwable).httpStatusCode == 404,
() -> entityNotFound("Site", siteId));
}
protected Result entityNotFound(String entityName, String id) {
// building our custom error model.
Error e = new Error(
Http.Status.NOT_FOUND,
ErrorCode.ENTITY_NOT_FOUND,
ErrorCodes.NOT_FOUND, new String[]{entityName, id});
return notFound(e.asJson());
}
So, the essence of above code is I need to reach out to someClient to check if the site exists and the client can throw a SomeClientException.
Controller.java
public Result destroy(Long siteId, Long productId){
return performWithSite(siteId, site -> {
productWriter.deleteProduct(siteId, productId);
return noContent();
}).toCompletableFuture().exceptionally(e -> {
Logger.error(e+"exception");
throw e.getCause();
// above line errors out as unhandledException java.lang.throwable, I need the NotFoundException which is contained within the CompletionException to be thrown.
});
}
// In the above controller code, after I do the remote call to the client to check if the site exists, I need to go ahead and delete the product.
productWriter.deleteProduct(siteId, productId)
might still throw a NotFoundException
or something else which I need to rethrow it back ...
so that any exception which is re-thrown from the controller code is picked up our custom exception handler in the call chain.
Here is my test case :
"return 404 when deleting a nonexistent Product" {
when(productDAO.findBySiteAndProductId(anyLong(), anyLong())) thenReturn null
a[NotFoundException] should be thrownBy { controller.destroy(0L, 1L) }
}
The CompletableFuture. join() method is similar to the get method, but it throws an unchecked exception in case the Future does not complete normally.
Afaik, there is no builtin solution. All function types you can pass to the chaining methods of CompletionStage
are restricted to unchecked exceptions. You can built your own utility method:
public static <T> CompletionStage<T> handleSpecific(
CompletionStage<T> previousStage, Predicate<Throwable> p, Supplier<T> s) {
CompletableFuture<T> result = new CompletableFuture<>();
previousStage.whenComplete((value,throwable)->{
if(throwable == null) result.complete(value);
else {
Throwable t = throwable;
if(t instanceof CompletionException) {
t = t.getCause();
if(t == null) t = throwable;
}
if(p.test(t)) result.complete(s.get());
else result.completeExceptionally(throwable);
}
});
return result;
}
This solution can be used like:
return handleSpecific(
outcome.thenApplyAsync(siteJson -> {
Site site = Json.fromJson(siteJson, Site.class);
try {
return function.apply(site);
} catch (RequestException e) {
return e.result;
}
}, httpExecutionContext.current()),
throwable -> throwable instanceof SomeClientException
&& ((SomeClientException)throwable).httpStatusCode == 404,
() -> entityNotFound("Site", siteId));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With