2

Currently, I have this JsonBodyHandler used to parse the response body if the response is successful. I also have a model defined if the request is not successful. So based on the status code, I need to map the response to either the expected or exception model classes.

  private static class JsonBodyHandler<R> implements HttpResponse.BodyHandler<Supplier<R>> {

    private final Class<R> returnClz;

    public JsonBodyHandler(Class<R> returnClz) {
      this.returnClz = returnClz;
    }

    private static <R> HttpResponse.BodySubscriber<Supplier<R>> asJson(Class<R> returnClz) {
      HttpResponse.BodySubscriber<InputStream> upstream =
          HttpResponse.BodySubscribers.ofInputStream();

      return HttpResponse.BodySubscribers.mapping(
          upstream, inputStream -> toSupplierOfType(inputStream, returnClz));
    }

    private static <R> Supplier<R> toSupplierOfType(InputStream inputStream, Class<R> returnClz) {
      return () -> {
        try (InputStream stream = inputStream) {
          ObjectMapper objectMapper = new ObjectMapper();
          return objectMapper.readValue(stream, returnClz);
        } catch (IOException e) {
          throw new UncheckedIOException(e);
        }
      };
    }

    @Override
    public HttpResponse.BodySubscriber<Supplier<R>> apply(HttpResponse.ResponseInfo responseInfo) {
      return asJson(returnClz);
    }
  }

I'm sending requests as follows:

  <B, R> R exchange(URI uri, String method, B body, Class<R> returnClz) {
    Builder httpRequestBuilder = HttpRequest.newBuilder().uri(uri);
    addHeaders(httpRequestBuilder);

    var httpRequest =
        "GET".equals(method)
            ? httpRequestBuilder.GET().build()
            : httpRequestBuilder.method(method, getBodyPublisher(body)).build();

    var httpClient = HttpClient.newHttpClient();
    JsonBodyHandler<R> bodyHandler = new JsonBodyHandler<>(returnClz);
    Supplier<R> responseSupplier = httpClient.send(httpRequest, bodyHandler).body();
    return responseSupplier.get();
  }

What I want to do is something as follows:

    BodySubscriber<Supplier<R>> successBodySubscriber = JsonBodyHandler.asJson(returnClz);
    BodySubscriber<Supplier<ExceptionModel>> failureBodySubscriber =
        JsonBodyHandler.asJson(ExceptionModel.class);
    BodyHandler<Supplier> jsonBodyHandler =
        (rspInfo) -> rspInfo.statusCode() == 200 ? successBodySubscriber : failureBodySubscriber;

    HttpResponse<Supplier> httpResponse = httpClient.send(httpRequest, jsonBodyHandler);
    if (httpResponse.statusCode() != 200) {
      Supplier<ExceptionModel> responseSupplier = httpResponse.body();
      throw ClientServiceError.invalidResponse(responseSupplier.get());
    }

    Supplier<R> responseSupplier = httpResponse.body();
    return responseSupplier.get();

This doesn't work, I get compile time error at line BodyHandler<Supplier> jsonBodyHandler = (rspInfo) -> rspInfo.statusCode() == 200 ? successBodySubscriber : failureBodySubscriber;:

Incompatible types. Found: 'java.net.http.HttpResponse.BodySubscriber<java.util.function.Supplier>', required: 'java.net.http.HttpResponse.BodySubscriber<java.util.function.Supplier>'

Incompatible types. Found: 'java.net.http.HttpResponse.BodySubscriber<java.util.function.Supplier<com.project.proxy.impl.ExceptionModel>>', required: 'java.net.http.HttpResponse.BodySubscriber<java.util.function.Supplier>'

I am using Java 11. Cannot upgrade the version. I believe there should be a better way to do this. Any suggestions will be appreciated.

Keshavram Kuduwa
  • 942
  • 10
  • 40
  • Please elaborate on “doesn’t work”. – Abhijit Sarkar Apr 30 '23 at 20:07
  • It doesn't compile. Also, this is using Raw Types. – Keshavram Kuduwa Apr 30 '23 at 20:15
  • At the very least, I believe it would be better to do the status code check inside the `apply` method of your `JsonBodyHandler` class. – Slaw Apr 30 '23 at 20:56
  • 1
    Then I would try to unify the result type, maybe behind an interface. If using Java 17+ I might even try to implement this using a [sealed interface](https://openjdk.org/jeps/409), then have two [records](https://openjdk.org/jeps/395) to describe the result as a success or a failure. The `Success` record could hold the real object (of type `R` in your example), and the `Failure` record could just carry an error message as a `String`. If allowed to use preview features, then perhaps [pattern matching for switch](https://openjdk.org/jeps/433) (currently its 4th preview in Java 20) [cont.] – Slaw Apr 30 '23 at 21:02
  • 1
    [cont.] could be used to process the result. Though a basic `instanceof` check could work instead (also see [pattern matching for instanceof](https://openjdk.org/jeps/394)). Note I'm mostly just musing here. There are likely some details that would need to be worked out. But this is a pattern I've seen in Kotlin quite a few times (i.e., sealed class/interface, limited number of `data class` and/or `object` implementations, using a `when` expression to process the result). – Slaw Apr 30 '23 at 21:05
  • "Doesn't compile"is not helpful. Provide the exact error and Java version at the least. – aled Apr 30 '23 at 21:26
  • @Aled, I have updated the question and added the Java version as well as the compile-time errors. – Keshavram Kuduwa May 01 '23 at 05:51
  • @Slaw I'm using Java 11 and cannot upgrade it. – Keshavram Kuduwa May 01 '23 at 05:52
  • Well, most of what I said only makes the implementation somewhat more straightforward. You should be able to implement something similar to what I described in Java 11 as well. – Slaw May 01 '23 at 06:03
  • @Slaw The `apply` method expects `BodySubscriber>`, if I have a check there and return `BodySubscriber>` I'll still get a compile-time error. Also, the JsonBodyHandler is used to map n number of Responses and I cannot unify them using a single interface. – Keshavram Kuduwa May 01 '23 at 07:03
  • What about something like this? https://gitlab.com/-/snippets/2535393 (it doesn't make use of a `Supplier`, but adapting the code should be relatively easy) – Slaw May 01 '23 at 20:27

0 Answers0