Skip to content

Commit d9d87df

Browse files
committed
add support for Jsonb generics
1 parent ab81c8c commit d9d87df

File tree

7 files changed

+135
-15
lines changed

7 files changed

+135
-15
lines changed

client/pom.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@
3939
<dependency>
4040
<groupId>io.avaje</groupId>
4141
<artifactId>avaje-jsonb</artifactId>
42-
<version>1.0-RC1</version>
42+
<version>1.1-RC2</version>
4343
<optional>true</optional>
4444
</dependency>
4545

4646
<dependency>
4747
<groupId>io.avaje</groupId>
4848
<artifactId>avaje-inject</artifactId>
49-
<version>8.6</version>
49+
<version>8.10</version>
5050
<optional>true</optional>
5151
</dependency>
5252

@@ -76,14 +76,14 @@
7676
<dependency>
7777
<groupId>io.javalin</groupId>
7878
<artifactId>javalin</artifactId>
79-
<version>4.1.1</version>
79+
<version>5.2.0</version>
8080
<scope>test</scope>
8181
</dependency>
8282

8383
<dependency>
8484
<groupId>io.avaje</groupId>
8585
<artifactId>avaje-http-api</artifactId>
86-
<version>1.16</version>
86+
<version>1.20</version>
8787
<scope>test</scope>
8888
</dependency>
8989

@@ -125,7 +125,7 @@
125125
<path>
126126
<groupId>io.avaje</groupId>
127127
<artifactId>avaje-inject-generator</artifactId>
128-
<version>8.6</version>
128+
<version>8.10</version>
129129
</path>
130130
</annotationProcessorPaths>
131131
</configuration>

client/src/main/java/io/avaje/http/client/BodyAdapter.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.avaje.http.client;
22

3+
import java.lang.reflect.ParameterizedType;
34
import java.util.List;
45

56
/**
@@ -23,11 +24,29 @@ public interface BodyAdapter {
2324
*/
2425
<T> BodyReader<T> beanReader(Class<T> type);
2526

27+
/**
28+
* Return a BodyReader to read response content and convert to a bean.
29+
*
30+
* @param type The bean type to convert the content to.
31+
*/
32+
default <T> BodyReader<T> beanReader(ParameterizedType type) {
33+
throw new UnsupportedOperationException("Parameterized types not supported for this adapter");
34+
}
35+
36+
2637
/**
2738
* Return a BodyReader to read response content and convert to a list of beans.
2839
*
2940
* @param type The bean type to convert the content to.
3041
*/
3142
<T> BodyReader<List<T>> listReader(Class<T> type);
3243

44+
/**
45+
* Return a BodyReader to read response content and convert to a list of beans.
46+
*
47+
* @param type The bean type to convert the content to.
48+
*/
49+
default <T> BodyReader<List<T>> listReader(ParameterizedType type) {
50+
throw new UnsupportedOperationException("Parameterized types not supported for this adapter");
51+
}
3352
}

client/src/main/java/io/avaje/http/client/DHttpClientContext.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.IOException;
44
import java.lang.reflect.Constructor;
5+
import java.lang.reflect.ParameterizedType;
56
import java.net.http.HttpClient;
67
import java.net.http.HttpHeaders;
78
import java.net.http.HttpRequest;
@@ -273,6 +274,10 @@ <T> BodyReader<T> beanReader(Class<T> cls) {
273274
return bodyAdapter.beanReader(cls);
274275
}
275276

277+
<T> BodyReader<T> beanReader(ParameterizedType cls) {
278+
return bodyAdapter.beanReader(cls);
279+
}
280+
276281
<T> T readBean(Class<T> cls, BodyContent content) {
277282
return bodyAdapter.beanReader(cls).read(content);
278283
}
@@ -281,6 +286,15 @@ <T> List<T> readList(Class<T> cls, BodyContent content) {
281286
return bodyAdapter.listReader(cls).read(content);
282287
}
283288

289+
@SuppressWarnings("unchecked")
290+
<T> T readBean(ParameterizedType cls, BodyContent content) {
291+
return (T) bodyAdapter.beanReader(cls).read(content);
292+
}
293+
294+
<T> List<T> readList(ParameterizedType cls, BodyContent content) {
295+
return (List<T>) bodyAdapter.listReader(cls).read(content);
296+
}
297+
284298
void afterResponse(DHttpClientRequest request) {
285299
metricResTotal.add(1);
286300
metricResMicros.add(request.responseTimeMicros());

client/src/main/java/io/avaje/http/client/DHttpClientRequest.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import javax.net.ssl.SSLSession;
44
import java.io.FileNotFoundException;
55
import java.io.InputStream;
6+
import java.lang.reflect.ParameterizedType;
67
import java.net.URI;
78
import java.net.URLEncoder;
89
import java.net.http.HttpClient;
@@ -433,7 +434,7 @@ public <T> List<T> list(Class<T> cls) {
433434
readResponseContent();
434435
return context.readList(cls, encodedResponseBody);
435436
}
436-
437+
437438
@Override
438439
public <T> Stream<T> stream(Class<T> cls) {
439440
final HttpResponse<Stream<String>> res = handler(HttpResponse.BodyHandlers.ofLines());
@@ -445,6 +446,31 @@ public <T> Stream<T> stream(Class<T> cls) {
445446
return res.body().map(bodyReader::readBody);
446447
}
447448

449+
450+
@Override
451+
public <T> T bean(ParameterizedType cls) {
452+
readResponseContent();
453+
return context.readBean(cls, encodedResponseBody);
454+
}
455+
456+
@Override
457+
public <T> List<T> list(ParameterizedType cls) {
458+
readResponseContent();
459+
return context.readList(cls, encodedResponseBody);
460+
}
461+
462+
463+
@Override
464+
public <T> Stream<T> stream(ParameterizedType cls) {
465+
final HttpResponse<Stream<String>> res = handler(HttpResponse.BodyHandlers.ofLines());
466+
this.httpResponse = res;
467+
if (res.statusCode() >= 300) {
468+
throw new HttpException(res, context);
469+
}
470+
final BodyReader<T> bodyReader = context.beanReader(cls);
471+
return res.body().map(bodyReader::readBody);
472+
}
473+
448474
@Override
449475
public <T> HttpResponse<T> handler(HttpResponse.BodyHandler<T> responseHandler) {
450476
final HttpResponse<T> response = sendWith(responseHandler);

client/src/main/java/io/avaje/http/client/HttpClientResponse.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.avaje.http.client;
22

33
import java.io.InputStream;
4+
import java.lang.reflect.ParameterizedType;
45
import java.net.http.HttpResponse;
56
import java.nio.file.Path;
67
import java.util.List;
@@ -85,6 +86,7 @@ public interface HttpClientResponse {
8586
*/
8687
<T> List<T> list(Class<T> type);
8788

89+
8890
/**
8991
* Return the response as a stream of beans.
9092
* <p>
@@ -106,6 +108,51 @@ public interface HttpClientResponse {
106108
*/
107109
<T> Stream<T> stream(Class<T> type);
108110

111+
/**
112+
* Return the response as a single bean.
113+
* <p>
114+
* If the HTTP statusCode is not in the 2XX range a HttpException is throw which contains
115+
* the HttpResponse. This is the cause in the CompletionException.
116+
*
117+
* @param type The parameterized type of the bean to convert the response content into.
118+
* @return The bean the response is converted into.
119+
* @throws HttpException when the response has error status codes
120+
*/
121+
<T> T bean(ParameterizedType type);
122+
123+
/**
124+
* Return the response as a list of beans.
125+
* <p>
126+
* If the HTTP statusCode is not in the 2XX range a HttpException is throw which contains
127+
* the HttpResponse. This is the cause in the CompletionException.
128+
*
129+
* @param type The parameterized type of the bean to convert the response content into.
130+
* @return The list of beans the response is converted into.
131+
* @throws HttpException when the response has error status codes
132+
*/
133+
<T> List<T> list(ParameterizedType type);
134+
135+
/**
136+
* Return the response as a stream of beans.
137+
* <p>
138+
* Typically the response is expected to be {@literal application/x-json-stream}
139+
* newline delimited json payload.
140+
* <p>
141+
* Note that for this stream request the response content is not deemed
142+
* 'loggable' by avaje-http-client. This is because the entire response
143+
* may not be available at the time of the callback. As such {@link RequestLogger}
144+
* will not include response content when logging stream request/response
145+
* <p>
146+
* If the HTTP statusCode is not in the 2XX range a HttpException is throw which contains
147+
* the HttpResponse. This is the cause in the CompletionException.
148+
*
149+
* @param type The parameterized type of the bean to convert the response content into.
150+
* @return The stream of beans from the response
151+
* @throws HttpException when the response has error status codes
152+
*/
153+
<T> Stream<T> stream(ParameterizedType type);
154+
155+
109156
/**
110157
* Return the response with check for 200 range status code.
111158
* <p>

client/src/main/java/io/avaje/http/client/JsonbBodyAdapter.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package io.avaje.http.client;
22

3-
import io.avaje.jsonb.JsonType;
4-
import io.avaje.jsonb.Jsonb;
5-
3+
import java.lang.reflect.ParameterizedType;
4+
import java.lang.reflect.Type;
65
import java.util.List;
76
import java.util.concurrent.ConcurrentHashMap;
87

8+
import io.avaje.jsonb.JsonType;
9+
import io.avaje.jsonb.Jsonb;
10+
911
/**
10-
* avaje jsonb BodyAdapter to read and write beans as JSON.
12+
* Avaje Jsonb BodyAdapter to read and write beans as JSON.
1113
*
1214
* <pre>{@code
1315
*
@@ -21,9 +23,9 @@
2123
public final class JsonbBodyAdapter implements BodyAdapter {
2224

2325
private final Jsonb jsonb;
24-
private final ConcurrentHashMap<Class<?>, BodyWriter<?>> beanWriterCache = new ConcurrentHashMap<>();
25-
private final ConcurrentHashMap<Class<?>, BodyReader<?>> beanReaderCache = new ConcurrentHashMap<>();
26-
private final ConcurrentHashMap<Class<?>, BodyReader<?>> listReaderCache = new ConcurrentHashMap<>();
26+
private final ConcurrentHashMap<Type, BodyWriter<?>> beanWriterCache = new ConcurrentHashMap<>();
27+
private final ConcurrentHashMap<Type, BodyReader<?>> beanReaderCache = new ConcurrentHashMap<>();
28+
private final ConcurrentHashMap<Type, BodyReader<?>> listReaderCache = new ConcurrentHashMap<>();
2729

2830
/**
2931
* Create passing the Jsonb to use.
@@ -36,7 +38,7 @@ public JsonbBodyAdapter(Jsonb jsonb) {
3638
* Create with a default Jsonb that allows unknown properties.
3739
*/
3840
public JsonbBodyAdapter() {
39-
this.jsonb = Jsonb.newBuilder().build();
41+
this.jsonb = Jsonb.builder().build();
4042
}
4143

4244
@SuppressWarnings("unchecked")
@@ -51,6 +53,18 @@ public <T> BodyReader<T> beanReader(Class<T> cls) {
5153
return (BodyReader<T>) beanReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(jsonb.type(cls)));
5254
}
5355

56+
@SuppressWarnings("unchecked")
57+
@Override
58+
public <T> BodyReader<T> beanReader(ParameterizedType cls) {
59+
return (BodyReader<T>) beanReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(jsonb.type(cls)));
60+
}
61+
62+
@SuppressWarnings("unchecked")
63+
@Override
64+
public <T> BodyReader<List<T>> listReader(ParameterizedType cls) {
65+
return (BodyReader<List<T>>) listReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(jsonb.type(cls).list()));
66+
}
67+
5468
@SuppressWarnings("unchecked")
5569
@Override
5670
public <T> BodyReader<List<T>> listReader(Class<T> cls) {

client/src/test/java/io/avaje/http/client/HelloControllerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ void get_notFound() {
394394
final HttpResponse<String> hres = request.GET().asString();
395395

396396
assertThat(hres.statusCode()).isEqualTo(404);
397-
assertThat(hres.body()).contains("Not found");
397+
assertThat(hres.body()).contains("Not Found");
398398
HttpClientContext.Metrics metrics = clientContext.metrics(true);
399399
assertThat(metrics.totalCount()).isEqualTo(1);
400400
assertThat(metrics.errorCount()).isEqualTo(1);

0 commit comments

Comments
 (0)