Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 CompletionStage exceptionally rethrow exception

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) }
    }
like image 469
hackmabrain Avatar asked Nov 18 '16 16:11

hackmabrain


People also ask

Does CompletableFuture throw exception?

The CompletableFuture. join() method is similar to the get method, but it throws an unchecked exception in case the Future does not complete normally.


1 Answers

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));
like image 138
Holger Avatar answered Oct 11 '22 22:10

Holger