Skip to content

Commit 93724b4

Browse files
committed
#33 - Add support for using avaje-jsonb to serialise to/from json content
1 parent ee118c9 commit 93724b4

File tree

11 files changed

+240
-15
lines changed

11 files changed

+240
-15
lines changed

client/pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@
3131
<optional>true</optional>
3232
</dependency>
3333

34+
<dependency>
35+
<groupId>io.avaje</groupId>
36+
<artifactId>avaje-jsonb</artifactId>
37+
<version>0.5</version>
38+
<optional>true</optional>
39+
</dependency>
40+
3441
<!-- test dependencies -->
3542

3643
<dependency>
@@ -84,6 +91,13 @@
8491
<scope>test</scope>
8592
</dependency>
8693

94+
<!-- <dependency>-->
95+
<!-- <groupId>io.avaje</groupId>-->
96+
<!-- <artifactId>avaje-jsonb-generator</artifactId>-->
97+
<!-- <version>0.5</version>-->
98+
<!-- <scope>test</scope>-->
99+
<!-- </dependency>-->
100+
87101
</dependencies>
88102

89103
<build>
@@ -94,11 +108,13 @@
94108
<configuration>
95109
<argLine>
96110
--add-modules com.fasterxml.jackson.databind
111+
--add-modules io.avaje.jsonb
97112
--add-opens io.avaje.http.client/io.avaje.http.client=ALL-UNNAMED
98113
--add-opens io.avaje.http.client/org.example.webserver=ALL-UNNAMED
99114
--add-opens io.avaje.http.client/org.example.github=ALL-UNNAMED
100115
--add-opens io.avaje.http.client/org.example.webserver=com.fasterxml.jackson.databind
101116
--add-opens io.avaje.http.client/org.example.github=com.fasterxml.jackson.databind
117+
--add-opens io.avaje.http.client/org.example.github=io.avaje.jsonb
102118
</argLine>
103119
</configuration>
104120
</plugin>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public interface BodyAdapter {
1414
*
1515
* @param type The type of the bean this writer is for
1616
*/
17-
BodyWriter beanWriter(Class<?> type);
17+
<T> BodyWriter<T> beanWriter(Class<?> type);
1818

1919
/**
2020
* Return a BodyReader to read response content and convert to a bean.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33
/**
44
* Writes beans as content for a specific content type.
55
*/
6-
public interface BodyWriter {
6+
public interface BodyWriter<T> {
77

88
/**
99
* Write the bean as content using the default content type.
1010
* <p>
1111
* Used when all beans sent via POST, PUT, PATCH will be sent as
1212
* a single content type like <code>application/json; charset=utf8</code>.
1313
*/
14-
BodyContent write(Object bean);
14+
BodyContent write(T bean);
1515

1616
/**
1717
* Write the bean as content with the requested content type.
1818
* <p>
1919
* The writer is expected to use the given contentType to determine
2020
* how to write the bean as content.
2121
*/
22-
BodyContent write(Object bean, String contentType);
22+
BodyContent write(T bean, String contentType);
2323

2424
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest.Builder requestBuil
176176
return httpClient.sendAsync(requestBuilder.build(), bodyHandler);
177177
}
178178

179-
BodyContent write(Object bean, String contentType) {
179+
<T> BodyContent write(T bean, String contentType) {
180180
return bodyAdapter.beanWriter(bean.getClass()).write(bean, contentType);
181181
}
182182

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@ public class JacksonBodyAdapter implements BodyAdapter {
2525

2626
private final ObjectMapper mapper;
2727

28-
private final ConcurrentHashMap<Class<?>, BodyWriter> beanWriterCache = new ConcurrentHashMap<>();
29-
28+
private final ConcurrentHashMap<Class<?>, BodyWriter<?>> beanWriterCache = new ConcurrentHashMap<>();
3029
private final ConcurrentHashMap<Class<?>, BodyReader<?>> beanReaderCache = new ConcurrentHashMap<>();
31-
3230
private final ConcurrentHashMap<Class<?>, BodyReader<?>> listReaderCache = new ConcurrentHashMap<>();
3331

3432
/**
@@ -49,11 +47,12 @@ public JacksonBodyAdapter() {
4947
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
5048
}
5149

50+
@SuppressWarnings("unchecked")
5251
@Override
53-
public BodyWriter beanWriter(Class<?> cls) {
54-
return beanWriterCache.computeIfAbsent(cls, aClass -> {
52+
public <T> BodyWriter<T> beanWriter(Class<?> cls) {
53+
return (BodyWriter<T>)beanWriterCache.computeIfAbsent(cls, aClass -> {
5554
try {
56-
return new JWriter(mapper.writerFor(cls));
55+
return new JWriter<>(mapper.writerFor(cls));
5756
} catch (Exception e) {
5857
throw new RuntimeException(e);
5958
}
@@ -113,7 +112,7 @@ public T read(BodyContent bodyContent) {
113112
}
114113
}
115114

116-
private static class JWriter implements BodyWriter {
115+
private static class JWriter<T> implements BodyWriter<T> {
117116

118117
private final ObjectWriter writer;
119118

@@ -122,14 +121,14 @@ public JWriter(ObjectWriter writer) {
122121
}
123122

124123
@Override
125-
public BodyContent write(Object bean, String contentType) {
124+
public BodyContent write(T bean, String contentType) {
126125
// ignoring the requested contentType and always
127126
// writing the body as json content
128127
return write(bean);
129128
}
130129

131130
@Override
132-
public BodyContent write(Object bean) {
131+
public BodyContent write(T bean) {
133132
try {
134133
return BodyContent.asJson(writer.writeValueAsBytes(bean));
135134
} catch (JsonProcessingException e) {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package io.avaje.http.client;
2+
3+
import io.avaje.jsonb.JsonType;
4+
import io.avaje.jsonb.Jsonb;
5+
6+
import java.util.List;
7+
import java.util.concurrent.ConcurrentHashMap;
8+
9+
/**
10+
* avaje jsonb BodyAdapter to read and write beans as JSON.
11+
*
12+
* <pre>{@code
13+
*
14+
* HttpClientContext.newBuilder()
15+
* .baseUrl(baseUrl)
16+
* .bodyAdapter(new JsonbBodyAdapter())
17+
* .build();
18+
*
19+
* }</pre>
20+
*/
21+
public class JsonbBodyAdapter implements BodyAdapter {
22+
23+
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<>();
27+
28+
/**
29+
* Create passing the Jsonb to use.
30+
*/
31+
public JsonbBodyAdapter(Jsonb jsonb) {
32+
this.jsonb = jsonb;
33+
}
34+
35+
/**
36+
* Create with a default Jsonb that allows unknown properties.
37+
*/
38+
public JsonbBodyAdapter() {
39+
this.jsonb = Jsonb.newBuilder().build();
40+
}
41+
42+
@SuppressWarnings("unchecked")
43+
@Override
44+
public <T> BodyWriter<T> beanWriter(Class<?> cls) {
45+
return (BodyWriter<T>) beanWriterCache.computeIfAbsent(cls, aClass -> new JWriter<>(jsonb.type(cls)));
46+
}
47+
48+
@SuppressWarnings("unchecked")
49+
@Override
50+
public <T> BodyReader<T> beanReader(Class<T> cls) {
51+
return (BodyReader<T>) beanReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(jsonb.type(cls)));
52+
}
53+
54+
@SuppressWarnings("unchecked")
55+
@Override
56+
public <T> BodyReader<List<T>> listReader(Class<T> cls) {
57+
return (BodyReader<List<T>>) listReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(jsonb.type(cls).list()));
58+
}
59+
60+
private static class JReader<T> implements BodyReader<T> {
61+
62+
private final JsonType<T> reader;
63+
64+
JReader(JsonType<T> reader) {
65+
this.reader = reader;
66+
}
67+
68+
@Override
69+
public T readBody(String content) {
70+
return reader.fromJson(content);
71+
}
72+
73+
@Override
74+
public T read(BodyContent bodyContent) {
75+
return reader.fromJson(bodyContent.content());
76+
}
77+
}
78+
79+
private static class JWriter<T> implements BodyWriter<T> {
80+
81+
private final JsonType<T> writer;
82+
83+
public JWriter(JsonType<T> writer) {
84+
this.writer = writer;
85+
}
86+
87+
@Override
88+
public BodyContent write(T bean, String contentType) {
89+
// ignoring the requested contentType and always
90+
// writing the body as json content
91+
return write(bean);
92+
}
93+
94+
@Override
95+
public BodyContent write(T bean) {
96+
return BodyContent.asJson(writer.toJsonBytes(bean));
97+
}
98+
}
99+
100+
}

client/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
requires transitive java.net.http;
66
requires transitive org.slf4j;
77
requires static com.fasterxml.jackson.databind;
8+
requires static io.avaje.jsonb;
89

910
exports io.avaje.http.client;
1011
}

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

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

3+
import io.avaje.jsonb.Jsonb;
34
import org.example.github.Repo;
45
import org.example.github.Simple;
56
import org.example.github.httpclient.Simple$HttpClient;
7+
import org.example.github.RepoJsonAdapter;
8+
import org.junit.jupiter.api.Disabled;
69
import org.junit.jupiter.api.Test;
710

811
import java.util.List;
@@ -11,6 +14,7 @@
1114

1215
public class DHttpApiTest {
1316

17+
@Disabled
1418
@Test
1519
void test_github_listRepos() {
1620

@@ -27,4 +31,24 @@ void test_github_listRepos() {
2731
assertThat(repos).isNotEmpty();
2832
}
2933

34+
@Test
35+
void jsonb_github_listRepos() {
36+
37+
Jsonb jsonb = Jsonb.newBuilder()
38+
.add(Repo.class, RepoJsonAdapter::new)
39+
.build();
40+
41+
final HttpClientContext clientContext = HttpClientContext.newBuilder()
42+
.baseUrl("https://api.github.com")
43+
.bodyAdapter(new JsonbBodyAdapter(jsonb))
44+
.build();
45+
46+
DHttpApi httpApi = new DHttpApi();
47+
httpApi.addProvider(new Simple$HttpClient.Provider());
48+
final Simple simple = httpApi.provideFor(Simple.class, clientContext);
49+
50+
final List<Repo> repos = simple.listRepos("rbygrave", "junk");
51+
assertThat(repos).isNotEmpty();
52+
}
53+
3054
}

client/src/test/java/org/example/github/GithubTest.java

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

33
import io.avaje.http.client.HttpClientContext;
44
import io.avaje.http.client.JacksonBodyAdapter;
5-
import io.avaje.http.client.RequestLogger;
65
import org.junit.jupiter.api.Disabled;
76
import org.junit.jupiter.api.Test;
87

client/src/test/java/org/example/github/Repo.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.example.github;
22

3+
//import io.avaje.jsonb.Json;
4+
5+
//@Json
36
public class Repo {
47
public long id;
58
public String name;

0 commit comments

Comments
 (0)