Skip to content

Commit 6c51dfe

Browse files
authored
jackson 3 (#695)
1 parent 937d0b1 commit 6c51dfe

File tree

5 files changed

+196
-1
lines changed

5 files changed

+196
-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: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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>) beanWriterCache.computeIfAbsent(cls, aClass -> {
45+
try {
46+
return new JWriter<>(mapper.writerFor(cls));
47+
} catch (Exception e) {
48+
throw new RuntimeException(e);
49+
}
50+
});
51+
}
52+
53+
@SuppressWarnings("unchecked")
54+
@Override
55+
public <T> BodyReader<T> beanReader(Class<T> cls) {
56+
return (BodyReader<T>) beanReaderCache.computeIfAbsent(cls, aClass -> {
57+
try {
58+
return new JReader<>(mapper.readerFor(cls));
59+
} catch (Exception e) {
60+
throw new RuntimeException(e);
61+
}
62+
});
63+
}
64+
65+
@SuppressWarnings("unchecked")
66+
@Override
67+
public <T> BodyWriter<T> beanWriter(Type cls) {
68+
return (BodyWriter<T>) beanWriterCache.computeIfAbsent(cls, aClass -> {
69+
try {
70+
return new JWriter<>(mapper.writerFor(mapper.getTypeFactory().constructType(cls)));
71+
} catch (Exception e) {
72+
throw new RuntimeException(e);
73+
}
74+
});
75+
}
76+
77+
@SuppressWarnings("unchecked")
78+
@Override
79+
public <T> BodyReader<T> beanReader(Type cls) {
80+
return (BodyReader<T>) beanReaderCache.computeIfAbsent(cls, aClass -> {
81+
try {
82+
return new JReader<>(mapper.readerFor(mapper.getTypeFactory().constructType(cls)));
83+
} catch (Exception e) {
84+
throw new RuntimeException(e);
85+
}
86+
});
87+
}
88+
89+
@SuppressWarnings("unchecked")
90+
@Override
91+
public <T> BodyReader<List<T>> listReader(Class<T> cls) {
92+
return (BodyReader<List<T>>) listReaderCache.computeIfAbsent(cls, aClass -> {
93+
try {
94+
final CollectionType collectionType =
95+
mapper.getTypeFactory().constructCollectionType(List.class, cls);
96+
final ObjectReader reader = mapper.readerFor(collectionType);
97+
return new JReader<>(reader);
98+
} catch (Exception e) {
99+
throw new RuntimeException(e);
100+
}
101+
});
102+
}
103+
104+
@SuppressWarnings("unchecked")
105+
@Override
106+
public <T> BodyReader<List<T>> listReader(Type type) {
107+
return (BodyReader<List<T>>) listReaderCache.computeIfAbsent(type, aType -> {
108+
try {
109+
var javaType = mapper.getTypeFactory().constructType(aType);
110+
final CollectionType collectionType =
111+
mapper.getTypeFactory().constructCollectionType(List.class, javaType);
112+
final ObjectReader reader = mapper.readerFor(collectionType);
113+
return new JReader<>(reader);
114+
} catch (Exception e) {
115+
throw new RuntimeException(e);
116+
}
117+
});
118+
}
119+
120+
private static final class JReader<T> implements BodyReader<T> {
121+
122+
private final ObjectReader reader;
123+
124+
JReader(ObjectReader reader) {
125+
this.reader = reader;
126+
}
127+
128+
@Override
129+
public T readBody(String content) {
130+
return reader.readValue(content);
131+
}
132+
133+
@Override
134+
public T read(BodyContent bodyContent) {
135+
return reader.readValue(bodyContent.content());
136+
}
137+
}
138+
139+
private static final class JWriter<T> implements BodyWriter<T> {
140+
141+
private final ObjectWriter writer;
142+
143+
JWriter(ObjectWriter writer) {
144+
this.writer = writer;
145+
}
146+
147+
@Override
148+
public BodyContent write(T bean, String contentType) {
149+
// ignoring the requested contentType and always
150+
// writing the body as json content
151+
return write(bean);
152+
}
153+
154+
@Override
155+
public BodyContent write(T bean) {
156+
return BodyContent.asJson(writer.writeValueAsBytes(bean));
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)