list(ParameterizedType type);
+
+ /**
+ * Return the response as a stream of beans.
+ *
+ * Typically the response is expected to be {@literal application/x-json-stream}
+ * newline delimited json payload.
+ *
+ * Note that for this stream request the response content is not deemed
+ * 'loggable' by avaje-http-client. This is because the entire response
+ * may not be available at the time of the callback. As such {@link RequestLogger}
+ * will not include response content when logging stream request/response
+ *
+ * If the HTTP statusCode is not in the 2XX range a HttpException is throw which contains
+ * the HttpResponse. This is the cause in the CompletionException.
+ *
+ * @param type The parameterized type of the bean to convert the response content into.
+ * @return The stream of beans from the response
+ * @throws HttpException when the response has error status codes
+ */
+ Stream stream(ParameterizedType type);
+
+
/**
* Return the response with check for 200 range status code.
*
diff --git a/client/src/main/java/io/avaje/http/client/JsonbBodyAdapter.java b/client/src/main/java/io/avaje/http/client/JsonbBodyAdapter.java
index b678072..610c877 100644
--- a/client/src/main/java/io/avaje/http/client/JsonbBodyAdapter.java
+++ b/client/src/main/java/io/avaje/http/client/JsonbBodyAdapter.java
@@ -1,13 +1,15 @@
package io.avaje.http.client;
-import io.avaje.jsonb.JsonType;
-import io.avaje.jsonb.Jsonb;
-
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
+
/**
- * avaje jsonb BodyAdapter to read and write beans as JSON.
+ * Avaje Jsonb BodyAdapter to read and write beans as JSON.
*
*
{@code
*
@@ -21,9 +23,9 @@
public final class JsonbBodyAdapter implements BodyAdapter {
private final Jsonb jsonb;
- private final ConcurrentHashMap, BodyWriter>> beanWriterCache = new ConcurrentHashMap<>();
- private final ConcurrentHashMap, BodyReader>> beanReaderCache = new ConcurrentHashMap<>();
- private final ConcurrentHashMap, BodyReader>> listReaderCache = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap> beanWriterCache = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap> beanReaderCache = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap> listReaderCache = new ConcurrentHashMap<>();
/**
* Create passing the Jsonb to use.
@@ -36,7 +38,7 @@ public JsonbBodyAdapter(Jsonb jsonb) {
* Create with a default Jsonb that allows unknown properties.
*/
public JsonbBodyAdapter() {
- this.jsonb = Jsonb.newBuilder().build();
+ this.jsonb = Jsonb.builder().build();
}
@SuppressWarnings("unchecked")
@@ -51,6 +53,18 @@ public BodyReader beanReader(Class cls) {
return (BodyReader) beanReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(jsonb.type(cls)));
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public BodyReader beanReader(ParameterizedType cls) {
+ return (BodyReader) beanReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(jsonb.type(cls)));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public BodyReader> listReader(ParameterizedType cls) {
+ return (BodyReader>) listReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(jsonb.type(cls).list()));
+ }
+
@SuppressWarnings("unchecked")
@Override
public BodyReader> listReader(Class cls) {
diff --git a/client/src/test/java/io/avaje/http/client/HelloControllerTest.java b/client/src/test/java/io/avaje/http/client/HelloControllerTest.java
index 516b118..6b308ad 100644
--- a/client/src/test/java/io/avaje/http/client/HelloControllerTest.java
+++ b/client/src/test/java/io/avaje/http/client/HelloControllerTest.java
@@ -394,7 +394,7 @@ void get_notFound() {
final HttpResponse hres = request.GET().asString();
assertThat(hres.statusCode()).isEqualTo(404);
- assertThat(hres.body()).contains("Not found");
+ assertThat(hres.body()).contains("Not Found");
HttpClientContext.Metrics metrics = clientContext.metrics(true);
assertThat(metrics.totalCount()).isEqualTo(1);
assertThat(metrics.errorCount()).isEqualTo(1);
From 27f9c81a618707726483a5cb319557e2623d8749 Mon Sep 17 00:00:00 2001
From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
Date: Wed, 28 Dec 2022 19:53:09 -0500
Subject: [PATCH 2/5] Update BodyAdapter.java
---
client/src/main/java/io/avaje/http/client/BodyAdapter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/main/java/io/avaje/http/client/BodyAdapter.java b/client/src/main/java/io/avaje/http/client/BodyAdapter.java
index 012e503..33e82f8 100644
--- a/client/src/main/java/io/avaje/http/client/BodyAdapter.java
+++ b/client/src/main/java/io/avaje/http/client/BodyAdapter.java
@@ -47,6 +47,6 @@ default BodyReader beanReader(ParameterizedType type) {
* @param type The bean type to convert the content to.
*/
default BodyReader> listReader(ParameterizedType type) {
- throw new UnsupportedOperationException("Parameterized types not supported for this adapter");
+ throw new UnsupportedOperationException("Parameterized types not supported for this Body Adapter");
}
}
From eaf229559dbfd6c37920e9ec7be2eca39ff941d8 Mon Sep 17 00:00:00 2001
From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
Date: Wed, 28 Dec 2022 20:01:57 -0500
Subject: [PATCH 3/5] Revert "Update BodyAdapter.java"
This reverts commit 27f9c81a618707726483a5cb319557e2623d8749.
---
client/src/main/java/io/avaje/http/client/BodyAdapter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/main/java/io/avaje/http/client/BodyAdapter.java b/client/src/main/java/io/avaje/http/client/BodyAdapter.java
index 33e82f8..012e503 100644
--- a/client/src/main/java/io/avaje/http/client/BodyAdapter.java
+++ b/client/src/main/java/io/avaje/http/client/BodyAdapter.java
@@ -47,6 +47,6 @@ default BodyReader beanReader(ParameterizedType type) {
* @param type The bean type to convert the content to.
*/
default BodyReader> listReader(ParameterizedType type) {
- throw new UnsupportedOperationException("Parameterized types not supported for this Body Adapter");
+ throw new UnsupportedOperationException("Parameterized types not supported for this adapter");
}
}
From 7c283af8195801392148c86835f2fc8b5c8b71e0 Mon Sep 17 00:00:00 2001
From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
Date: Wed, 28 Dec 2022 21:52:15 -0500
Subject: [PATCH 4/5] heh, forgot about the async and httpcall
---
.../java/io/avaje/http/client/DHttpAsync.java | 22 ++++++
.../java/io/avaje/http/client/DHttpCall.java | 67 +++++++++++++++++--
.../avaje/http/client/DHttpClientRequest.java | 22 ++++++
.../avaje/http/client/HttpAsyncResponse.java | 26 +++++++
.../avaje/http/client/HttpCallResponse.java | 25 +++++++
5 files changed, 156 insertions(+), 6 deletions(-)
diff --git a/client/src/main/java/io/avaje/http/client/DHttpAsync.java b/client/src/main/java/io/avaje/http/client/DHttpAsync.java
index 3ab02cb..770f6d3 100644
--- a/client/src/main/java/io/avaje/http/client/DHttpAsync.java
+++ b/client/src/main/java/io/avaje/http/client/DHttpAsync.java
@@ -1,6 +1,7 @@
package io.avaje.http.client;
import java.io.InputStream;
+import java.lang.reflect.ParameterizedType;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -78,4 +79,25 @@ public CompletableFuture> stream(Class type) {
.performSendAsync(false, HttpResponse.BodyHandlers.ofLines())
.thenApply(httpResponse -> request.asyncStream(type, httpResponse));
}
+
+ @Override
+ public CompletableFuture bean(ParameterizedType type) {
+ return request
+ .performSendAsync(true, HttpResponse.BodyHandlers.ofByteArray())
+ .thenApply(httpResponse -> request.asyncBean(type, httpResponse));
+ }
+
+ @Override
+ public CompletableFuture> list(ParameterizedType type) {
+ return request
+ .performSendAsync(true, HttpResponse.BodyHandlers.ofByteArray())
+ .thenApply(httpResponse -> request.asyncList(type, httpResponse));
+ }
+
+ @Override
+ public CompletableFuture> stream(ParameterizedType type) {
+ return request
+ .performSendAsync(false, HttpResponse.BodyHandlers.ofLines())
+ .thenApply(httpResponse -> request.asyncStream(type, httpResponse));
+ }
}
diff --git a/client/src/main/java/io/avaje/http/client/DHttpCall.java b/client/src/main/java/io/avaje/http/client/DHttpCall.java
index 3ee7a8e..31c94e9 100644
--- a/client/src/main/java/io/avaje/http/client/DHttpCall.java
+++ b/client/src/main/java/io/avaje/http/client/DHttpCall.java
@@ -1,6 +1,7 @@
package io.avaje.http.client;
import java.io.InputStream;
+import java.lang.reflect.ParameterizedType;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -64,6 +65,21 @@ public HttpCall> stream(Class type) {
return new CallStream<>(type);
}
+ @Override
+ public HttpCall bean(ParameterizedType type) {
+ return new CallBean<>(type);
+ }
+
+ @Override
+ public HttpCall> list(ParameterizedType type) {
+ return new CallList<>(type);
+ }
+
+ @Override
+ public HttpCall> stream(ParameterizedType type) {
+ return new CallStream<>(type);
+ }
+
private class CallVoid implements HttpCall> {
@Override
public HttpResponse execute() {
@@ -132,46 +148,85 @@ public CompletableFuture> async() {
private class CallBean implements HttpCall {
private final Class type;
+ private final ParameterizedType genericType;
+ private final boolean isGeneric;
+
CallBean(Class type) {
+ this.isGeneric = false;
this.type = type;
+ this.genericType = null;
}
+
+ CallBean(ParameterizedType type) {
+ this.isGeneric = true;
+ this.type = null;
+ this.genericType = type;
+ }
+
@Override
public E execute() {
- return request.bean(type);
+ return isGeneric ? request.bean(genericType) : request.bean(type);
}
+
@Override
public CompletableFuture async() {
- return request.async().bean(type);
+ return isGeneric ? request.async().bean(genericType) : request.async().bean(type);
}
}
private class CallList implements HttpCall> {
private final Class type;
+ private final ParameterizedType genericType;
+ private final boolean isGeneric;
+
CallList(Class type) {
+ this.isGeneric = false;
this.type = type;
+ this.genericType = null;
}
+
+ CallList(ParameterizedType type) {
+ this.isGeneric = true;
+ this.type = null;
+ this.genericType = type;
+ }
+
@Override
public List execute() {
- return request.list(type);
+ return isGeneric ? request.list(genericType) : request.list(type);
}
+
@Override
public CompletableFuture> async() {
- return request.async().list(type);
+ return isGeneric ? request.async().list(genericType) : request.async().list(type);
}
}
private class CallStream implements HttpCall> {
private final Class type;
+ private final ParameterizedType genericType;
+ private final boolean isGeneric;
+
CallStream(Class type) {
+ this.isGeneric = false;
this.type = type;
+ this.genericType = null;
+ }
+
+ CallStream(ParameterizedType type) {
+ this.isGeneric = true;
+ this.type = null;
+ this.genericType = type;
}
+
@Override
public Stream execute() {
- return request.stream(type);
+ return isGeneric ? request.stream(genericType) : request.stream(type);
}
+
@Override
public CompletableFuture> async() {
- return request.async().stream(type);
+ return isGeneric ? request.async().stream(genericType) : request.async().stream(type);
}
}
diff --git a/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java b/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java
index 5fc3e6c..22d885f 100644
--- a/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java
+++ b/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java
@@ -532,6 +532,28 @@ protected Stream asyncStream(Class type, HttpResponse>
return response.body().map(bodyReader::readBody);
}
+ protected E asyncBean(ParameterizedType type, HttpResponse response) {
+ afterAsyncEncoded(response);
+ return context.readBean(type, encodedResponseBody);
+ }
+
+ protected List asyncList(ParameterizedType type, HttpResponse response) {
+ afterAsyncEncoded(response);
+ return context.readList(type, encodedResponseBody);
+ }
+
+ protected Stream asyncStream(
+ ParameterizedType type, HttpResponse> response) {
+ responseTimeNanos = System.nanoTime() - startAsyncNanos;
+ httpResponse = response;
+ context.afterResponse(this);
+ if (response.statusCode() >= 300) {
+ throw new HttpException(response, context);
+ }
+ final BodyReader bodyReader = context.beanReader(type);
+ return response.body().map(bodyReader::readBody);
+ }
+
private void afterAsyncEncoded(HttpResponse response) {
responseTimeNanos = System.nanoTime() - startAsyncNanos;
httpResponse = response;
diff --git a/client/src/main/java/io/avaje/http/client/HttpAsyncResponse.java b/client/src/main/java/io/avaje/http/client/HttpAsyncResponse.java
index 45cea96..7e6dee8 100644
--- a/client/src/main/java/io/avaje/http/client/HttpAsyncResponse.java
+++ b/client/src/main/java/io/avaje/http/client/HttpAsyncResponse.java
@@ -1,6 +1,7 @@
package io.avaje.http.client;
import java.io.InputStream;
+import java.lang.reflect.ParameterizedType;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -334,4 +335,29 @@ default CompletableFuture> withHandler(HttpResponse.BodyHand
* @return The CompletableFuture of the response
*/
CompletableFuture> stream(Class type);
+
+ /**
+ * Process expecting a bean response body (typically from json content).
+ *
+ * @param type The parameterized type to convert the content to
+ * @return The CompletableFuture of the response
+ */
+ CompletableFuture bean(ParameterizedType type);
+
+ /**
+ * Process expecting a list of beans response body (typically from json content).
+ *
+ * @param type The parameterized type to convert the content to
+ * @return The CompletableFuture of the response
+ */
+ CompletableFuture> list(ParameterizedType type);
+
+ /**
+ * Process response as a stream of beans (x-json-stream).
+ *
+ * @param type The parameterized type to convert the content to
+ * @return The CompletableFuture of the response
+ */
+ CompletableFuture> stream(ParameterizedType type);
+
}
diff --git a/client/src/main/java/io/avaje/http/client/HttpCallResponse.java b/client/src/main/java/io/avaje/http/client/HttpCallResponse.java
index d081a32..96cccbe 100644
--- a/client/src/main/java/io/avaje/http/client/HttpCallResponse.java
+++ b/client/src/main/java/io/avaje/http/client/HttpCallResponse.java
@@ -1,6 +1,7 @@
package io.avaje.http.client;
import java.io.InputStream;
+import java.lang.reflect.ParameterizedType;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.stream.Stream;
@@ -186,4 +187,28 @@ default HttpCall> withHandler(HttpResponse.BodyHandler bo
*/
HttpCall> stream(Class type);
+ /**
+ * A bean response to execute async or sync.
+ *
+ * @param type The parameterized type to convert the content to
+ * @return The HttpCall to allow sync or async execution
+ */
+ HttpCall bean(ParameterizedType type);
+
+ /**
+ * Process expecting a list of beans response body (typically from json content).
+ *
+ * @param type The parameterized type to convert the content to
+ * @return The HttpCall to execute sync or async
+ */
+ HttpCall> list(ParameterizedType type);
+
+ /**
+ * Process expecting a stream of beans response body (typically from json content).
+ *
+ * @param type The parameterized type to convert the content to
+ * @return The HttpCall to execute sync or async
+ */
+ HttpCall> stream(ParameterizedType type);
+
}
From 61619e55eb14de11611b8358b8bb2ac00bdf57e0 Mon Sep 17 00:00:00 2001
From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
Date: Sat, 31 Dec 2022 13:42:43 -0600
Subject: [PATCH 5/5] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f87cec7..5513b53 100644
--- a/README.md
+++ b/README.md
@@ -310,7 +310,7 @@ public final class ExampleRetry implements RetryHandler {
final var code = response.statusCode();
- if (retryCount >= MAX_RETRIES || code >= 400) {
+ if (retryCount >= MAX_RETRIES || code <= 400) {
return false;
}