Skip to content

Commit ba6d6d6

Browse files
committed
jackson 3
1 parent 937d0b1 commit ba6d6d6

File tree

5 files changed

+214
-1
lines changed

5 files changed

+214
-1
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
22
* Provides annotations to support Controllers for web frameworks that
3-
* are route based (like Sparkjava, Javlin etc).
3+
* are route based (like Avaje Jex, Javalin, Helidon, etc).
44
*/
55
package io.avaje.http.api;

http-client/src/main/java/module-info.java renamed to http-client/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
requires static com.fasterxml.jackson.databind;
2929
requires static com.fasterxml.jackson.annotation;
3030
requires static com.fasterxml.jackson.core;
31+
requires static tools.jackson.databind;
3132
requires static io.avaje.jsonb;
3233
requires static io.avaje.inject;
3334
requires static jdk.httpserver;
3435

3536
exports io.avaje.http.client;
37+
3638
}

http-client/pom.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@
2929
<version>1.2</version>
3030
</dependency>
3131

32+
<dependency>
33+
<groupId>tools.jackson.core</groupId>
34+
<artifactId>jackson-databind</artifactId>
35+
<version>3.0.3</version>
36+
<optional>true</optional>
37+
</dependency>
38+
3239
<dependency>
3340
<groupId>com.fasterxml.jackson.core</groupId>
3441
<artifactId>jackson-databind</artifactId>
@@ -155,6 +162,25 @@
155162
</execution>
156163
</executions>
157164
</plugin>
165+
<plugin>
166+
<groupId>org.moditect</groupId>
167+
<artifactId>moditect-maven-plugin</artifactId>
168+
<executions>
169+
<execution>
170+
<id>add-module-infos</id>
171+
<phase>package</phase>
172+
<goals>
173+
<goal>add-module-info</goal>
174+
</goals>
175+
<configuration>
176+
<overwriteExistingFiles>true</overwriteExistingFiles>
177+
<module>
178+
<moduleInfoFile>module-info.java</moduleInfoFile>
179+
</module>
180+
</configuration>
181+
</execution>
182+
</executions>
183+
</plugin>
158184
</plugins>
159185
</build>
160186
</project>

http-client/src/main/java/io/avaje/http/client/DHttpClientBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ private BodyAdapter defaultBodyAdapter() {
138138
if (bootLayer.findModule("com.fasterxml.jackson.databind").isPresent()) {
139139
return new JacksonBodyAdapter();
140140
}
141+
if (bootLayer.findModule("tools.jackson.databind").isPresent()) {
142+
return new Jackson3BodyAdapter();
143+
}
141144
return bodyAdapter;
142145
})
143146
.orElseGet(() -> {
@@ -148,6 +151,11 @@ private BodyAdapter defaultBodyAdapter() {
148151
}
149152
try {
150153
return new JacksonBodyAdapter();
154+
} catch (Throwable e) {
155+
// I guess it don't exist
156+
}
157+
try {
158+
return new Jackson3BodyAdapter();
151159
} catch (Throwable e) {
152160
return bodyAdapter;
153161
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package io.avaje.http.client;
2+
3+
import java.lang.reflect.Type;
4+
import java.util.List;
5+
import java.util.concurrent.ConcurrentHashMap;
6+
7+
import tools.jackson.databind.ObjectMapper;
8+
import tools.jackson.databind.ObjectReader;
9+
import tools.jackson.databind.ObjectWriter;
10+
import tools.jackson.databind.type.CollectionType;
11+
12+
/**
13+
* Jackson 3.x BodyAdapter to read and write beans as JSON.
14+
*
15+
* <pre>{@code
16+
* HttpClient.builder()
17+
* .baseUrl(baseUrl)
18+
* .bodyAdapter(new Jackson3BodyAdapter())
19+
* .build();
20+
*
21+
* }</pre>
22+
*/
23+
public final class Jackson3BodyAdapter implements BodyAdapter {
24+
25+
private final ObjectMapper mapper;
26+
27+
private final ConcurrentHashMap<Type, BodyWriter<?>> beanWriterCache = new ConcurrentHashMap<>();
28+
private final ConcurrentHashMap<Type, BodyReader<?>> beanReaderCache = new ConcurrentHashMap<>();
29+
private final ConcurrentHashMap<Type, BodyReader<?>> listReaderCache = new ConcurrentHashMap<>();
30+
31+
/** Create passing the ObjectMapper to use. */
32+
public Jackson3BodyAdapter(ObjectMapper mapper) {
33+
this.mapper = mapper;
34+
}
35+
36+
/** Create with a ObjectMapper that allows unknown properties and inclusion non empty. */
37+
public Jackson3BodyAdapter() {
38+
this.mapper = new ObjectMapper();
39+
}
40+
41+
@SuppressWarnings("unchecked")
42+
@Override
43+
public <T> BodyWriter<T> beanWriter(Class<?> cls) {
44+
return (BodyWriter<T>)
45+
beanWriterCache.computeIfAbsent(
46+
cls,
47+
aClass -> {
48+
try {
49+
return new JWriter<>(mapper.writerFor(cls));
50+
} catch (Exception e) {
51+
throw new RuntimeException(e);
52+
}
53+
});
54+
}
55+
56+
@SuppressWarnings("unchecked")
57+
@Override
58+
public <T> BodyReader<T> beanReader(Class<T> cls) {
59+
return (BodyReader<T>)
60+
beanReaderCache.computeIfAbsent(
61+
cls,
62+
aClass -> {
63+
try {
64+
return new JReader<>(mapper.readerFor(cls));
65+
} catch (Exception e) {
66+
throw new RuntimeException(e);
67+
}
68+
});
69+
}
70+
71+
@SuppressWarnings("unchecked")
72+
@Override
73+
public <T> BodyWriter<T> beanWriter(Type cls) {
74+
return (BodyWriter<T>)
75+
beanWriterCache.computeIfAbsent(
76+
cls,
77+
aClass -> {
78+
try {
79+
return new JWriter<>(mapper.writerFor(mapper.getTypeFactory().constructType(cls)));
80+
} catch (Exception e) {
81+
throw new RuntimeException(e);
82+
}
83+
});
84+
}
85+
86+
@SuppressWarnings("unchecked")
87+
@Override
88+
public <T> BodyReader<T> beanReader(Type cls) {
89+
return (BodyReader<T>)
90+
beanReaderCache.computeIfAbsent(
91+
cls,
92+
aClass -> {
93+
try {
94+
return new JReader<>(mapper.readerFor(mapper.getTypeFactory().constructType(cls)));
95+
} catch (Exception e) {
96+
throw new RuntimeException(e);
97+
}
98+
});
99+
}
100+
101+
@SuppressWarnings("unchecked")
102+
@Override
103+
public <T> BodyReader<List<T>> listReader(Class<T> cls) {
104+
return (BodyReader<List<T>>)
105+
listReaderCache.computeIfAbsent(
106+
cls,
107+
aClass -> {
108+
try {
109+
final CollectionType collectionType =
110+
mapper.getTypeFactory().constructCollectionType(List.class, cls);
111+
final ObjectReader reader = mapper.readerFor(collectionType);
112+
return new JReader<>(reader);
113+
} catch (Exception e) {
114+
throw new RuntimeException(e);
115+
}
116+
});
117+
}
118+
119+
@SuppressWarnings("unchecked")
120+
@Override
121+
public <T> BodyReader<List<T>> listReader(Type type) {
122+
return (BodyReader<List<T>>)
123+
listReaderCache.computeIfAbsent(
124+
type,
125+
aType -> {
126+
try {
127+
var javaType = mapper.getTypeFactory().constructType(aType);
128+
final CollectionType collectionType =
129+
mapper.getTypeFactory().constructCollectionType(List.class, javaType);
130+
final ObjectReader reader = mapper.readerFor(collectionType);
131+
return new JReader<>(reader);
132+
} catch (Exception e) {
133+
throw new RuntimeException(e);
134+
}
135+
});
136+
}
137+
138+
private static final class JReader<T> implements BodyReader<T> {
139+
140+
private final ObjectReader reader;
141+
142+
JReader(ObjectReader reader) {
143+
this.reader = reader;
144+
}
145+
146+
@Override
147+
public T readBody(String content) {
148+
return reader.readValue(content);
149+
}
150+
151+
@Override
152+
public T read(BodyContent bodyContent) {
153+
return reader.readValue(bodyContent.content());
154+
}
155+
}
156+
157+
private static final class JWriter<T> implements BodyWriter<T> {
158+
159+
private final ObjectWriter writer;
160+
161+
public JWriter(ObjectWriter writer) {
162+
this.writer = writer;
163+
}
164+
165+
@Override
166+
public BodyContent write(T bean, String contentType) {
167+
// ignoring the requested contentType and always
168+
// writing the body as json content
169+
return write(bean);
170+
}
171+
172+
@Override
173+
public BodyContent write(T bean) {
174+
return BodyContent.asJson(writer.writeValueAsBytes(bean));
175+
}
176+
}
177+
}

0 commit comments

Comments
 (0)