Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion http-api/src/main/java/io/avaje/http/api/package-info.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Provides annotations to support Controllers for web frameworks that
* are route based (like Sparkjava, Javlin etc).
* are route based (like Avaje Jex, Javalin, Helidon, etc).
*/
package io.avaje.http.api;
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
requires static com.fasterxml.jackson.databind;
requires static com.fasterxml.jackson.annotation;
requires static com.fasterxml.jackson.core;
requires static tools.jackson.databind;
requires static io.avaje.jsonb;
requires static io.avaje.inject;
requires static jdk.httpserver;

exports io.avaje.http.client;

}
26 changes: 26 additions & 0 deletions http-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
<version>1.2</version>
</dependency>

<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.0.3</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
Expand Down Expand Up @@ -155,6 +162,25 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<executions>
<execution>
<id>add-module-infos</id>
<phase>package</phase>
<goals>
<goal>add-module-info</goal>
</goals>
<configuration>
<overwriteExistingFiles>true</overwriteExistingFiles>
<module>
<moduleInfoFile>module-info.java</moduleInfoFile>
</module>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ private BodyAdapter defaultBodyAdapter() {
if (bootLayer.findModule("com.fasterxml.jackson.databind").isPresent()) {
return new JacksonBodyAdapter();
}
if (bootLayer.findModule("tools.jackson.databind").isPresent()) {
return new Jackson3BodyAdapter();
}
return bodyAdapter;
})
.orElseGet(() -> {
Expand All @@ -148,6 +151,11 @@ private BodyAdapter defaultBodyAdapter() {
}
try {
return new JacksonBodyAdapter();
} catch (Throwable e) {
// I guess it don't exist
}
try {
return new Jackson3BodyAdapter();
} catch (Throwable e) {
return bodyAdapter;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package io.avaje.http.client;

import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectReader;
import tools.jackson.databind.ObjectWriter;
import tools.jackson.databind.type.CollectionType;

/**
* Jackson 3.x BodyAdapter to read and write beans as JSON.
*
* <pre>{@code
* HttpClient.builder()
* .baseUrl(baseUrl)
* .bodyAdapter(new Jackson3BodyAdapter())
* .build();
*
* }</pre>
*/
public final class Jackson3BodyAdapter implements BodyAdapter {

private final ObjectMapper mapper;

private final ConcurrentHashMap<Type, BodyWriter<?>> beanWriterCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Type, BodyReader<?>> beanReaderCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Type, BodyReader<?>> listReaderCache = new ConcurrentHashMap<>();

/** Create passing the ObjectMapper to use. */
public Jackson3BodyAdapter(ObjectMapper mapper) {
this.mapper = mapper;
}

/** Create with a ObjectMapper that allows unknown properties and inclusion non empty. */
public Jackson3BodyAdapter() {
this.mapper = new ObjectMapper();
}

@SuppressWarnings("unchecked")
@Override
public <T> BodyWriter<T> beanWriter(Class<?> cls) {
return (BodyWriter<T>) beanWriterCache.computeIfAbsent(cls, aClass -> {
try {
return new JWriter<>(mapper.writerFor(cls));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}

@SuppressWarnings("unchecked")
@Override
public <T> BodyReader<T> beanReader(Class<T> cls) {
return (BodyReader<T>) beanReaderCache.computeIfAbsent(cls, aClass -> {
try {
return new JReader<>(mapper.readerFor(cls));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}

@SuppressWarnings("unchecked")
@Override
public <T> BodyWriter<T> beanWriter(Type cls) {
return (BodyWriter<T>) beanWriterCache.computeIfAbsent(cls, aClass -> {
try {
return new JWriter<>(mapper.writerFor(mapper.getTypeFactory().constructType(cls)));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}

@SuppressWarnings("unchecked")
@Override
public <T> BodyReader<T> beanReader(Type cls) {
return (BodyReader<T>) beanReaderCache.computeIfAbsent(cls, aClass -> {
try {
return new JReader<>(mapper.readerFor(mapper.getTypeFactory().constructType(cls)));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}

@SuppressWarnings("unchecked")
@Override
public <T> BodyReader<List<T>> listReader(Class<T> cls) {
return (BodyReader<List<T>>) listReaderCache.computeIfAbsent(cls, aClass -> {
try {
final CollectionType collectionType =
mapper.getTypeFactory().constructCollectionType(List.class, cls);
final ObjectReader reader = mapper.readerFor(collectionType);
return new JReader<>(reader);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}

@SuppressWarnings("unchecked")
@Override
public <T> BodyReader<List<T>> listReader(Type type) {
return (BodyReader<List<T>>) listReaderCache.computeIfAbsent(type, aType -> {
try {
var javaType = mapper.getTypeFactory().constructType(aType);
final CollectionType collectionType =
mapper.getTypeFactory().constructCollectionType(List.class, javaType);
final ObjectReader reader = mapper.readerFor(collectionType);
return new JReader<>(reader);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}

private static final class JReader<T> implements BodyReader<T> {

private final ObjectReader reader;

JReader(ObjectReader reader) {
this.reader = reader;
}

@Override
public T readBody(String content) {
return reader.readValue(content);
}

@Override
public T read(BodyContent bodyContent) {
return reader.readValue(bodyContent.content());
}
}

private static final class JWriter<T> implements BodyWriter<T> {

private final ObjectWriter writer;

JWriter(ObjectWriter writer) {
this.writer = writer;
}

@Override
public BodyContent write(T bean, String contentType) {
// ignoring the requested contentType and always
// writing the body as json content
return write(bean);
}

@Override
public BodyContent write(T bean) {
return BodyContent.asJson(writer.writeValueAsBytes(bean));
}
}
}