Skip to content

Commit

Permalink
Rest Client Reactive: Provide custom ObjectMapper in request scope
Browse files Browse the repository at this point in the history
With these changes, we can now register custom object mappers when creating a client programmatically:

```
clientAllowsUnknown = RestClientBuilder.newBuilder()
                .baseUrl(new URL(wireMockServer.baseUrl()))
                .register(ClientObjectMapperUnknown.class)
                .build(MyClient.class);
```

where ClientObjectMapperUnknown is:

```
public static class ClientObjectMapperUnknown implements ContextResolver<ObjectMapper> {
        @OverRide
        public ObjectMapper getContext(Class<?> type) {
            return new ObjectMapper()
                    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                    .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        }
    }
```

I implemented this feature by injecting the rest client context via the interface ClientRestHandler. Then, the rest client context has registered all the context resolvers, so the jackson message reader/writer can get the custom object mappers.

Fix quarkusio#26152
Relates quarkusio#26988
  • Loading branch information
Sgitario authored and fercomunello committed Aug 31, 2022
1 parent a3c2e03 commit 9b9673b
Show file tree
Hide file tree
Showing 16 changed files with 327 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;

public class ServerJacksonMessageBodyReader extends JacksonBasicMessageBodyReader implements ServerMessageBodyReader<Object> {
Expand Down Expand Up @@ -58,6 +59,7 @@ private Object doReadFrom(Class<Object> type, Type genericType, InputStream enti
return null;
}
try {
ObjectReader reader = getEffectiveReader();
return reader.forType(reader.getTypeFactory().constructType(genericType != null ? genericType : type))
.readValue(entityStream);
} catch (MismatchedInputException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@

import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.rest.client.reactive.jackson.runtime.serialisers.ClientJacksonMessageBodyReader;
import io.quarkus.rest.client.reactive.jackson.runtime.serialisers.ClientJacksonMessageBodyWriter;
import io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProviderDefinedBuildItem;
import io.quarkus.resteasy.reactive.jackson.runtime.serialisers.vertx.VertxJsonArrayBasicMessageBodyReader;
Expand Down Expand Up @@ -48,13 +47,13 @@ void additionalProviders(
}
// make these beans to they can get instantiated with the Quarkus CDI configured Jsonb object
additionalBean.produce(AdditionalBeanBuildItem.builder()
.addBeanClass(JacksonBasicMessageBodyReader.class.getName())
.addBeanClass(ClientJacksonMessageBodyReader.class.getName())
.addBeanClass(ClientJacksonMessageBodyWriter.class.getName())
.setUnremovable().build());

additionalReaders
.produce(
new MessageBodyReaderBuildItem.Builder(JacksonBasicMessageBodyReader.class.getName(),
new MessageBodyReaderBuildItem.Builder(ClientJacksonMessageBodyReader.class.getName(),
Object.class.getName())
.setMediaTypeStrings(HANDLED_READ_MEDIA_TYPES)
.setBuiltin(true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.quarkus.rest.client.reactive.jackson.runtime.serialisers;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

import javax.inject.Inject;

import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ClientRestHandler;
import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;

public class ClientJacksonMessageBodyReader extends JacksonBasicMessageBodyReader implements ClientRestHandler {

private final ConcurrentMap<ObjectMapper, ObjectReader> contextResolverMap = new ConcurrentHashMap<>();
private RestClientRequestContext context;

@Inject
public ClientJacksonMessageBodyReader(ObjectMapper mapper) {
super(mapper);
}

@Override
public void handle(RestClientRequestContext requestContext) {
this.context = requestContext;
}

@Override
protected ObjectReader getEffectiveReader() {
if (context == null) {
// no context injected when reader is not running within a rest client context
return super.getEffectiveReader();
}

ObjectMapper objectMapper = context.getConfiguration().getFromContext(ObjectMapper.class);
if (objectMapper == null) {
return super.getEffectiveReader();
}

return contextResolverMap.computeIfAbsent(objectMapper, new Function<>() {
@Override
public ObjectReader apply(ObjectMapper objectMapper) {
return objectMapper.reader();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,28 @@
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;

import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ClientRestHandler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

public class ClientJacksonMessageBodyWriter implements MessageBodyWriter<Object> {
public class ClientJacksonMessageBodyWriter implements MessageBodyWriter<Object>, ClientRestHandler {

protected final ObjectMapper originalMapper;
protected final ObjectWriter defaultWriter;
private final ConcurrentMap<ObjectMapper, ObjectWriter> contextResolverMap = new ConcurrentHashMap<>();
private RestClientRequestContext context;

@Inject
public ClientJacksonMessageBodyWriter(ObjectMapper mapper) {
Expand All @@ -36,6 +44,30 @@ public boolean isWriteable(Class type, Type genericType, Annotation[] annotation
@Override
public void writeTo(Object o, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
doLegacyWrite(o, annotations, httpHeaders, entityStream, defaultWriter);
doLegacyWrite(o, annotations, httpHeaders, entityStream, getEffectiveWriter());
}

@Override
public void handle(RestClientRequestContext requestContext) throws Exception {
this.context = context;
}

protected ObjectWriter getEffectiveWriter() {
if (context == null) {
// no context injected when writer is not running within a rest client context
return defaultWriter;
}

ObjectMapper objectMapper = context.getConfiguration().getFromContext(ObjectMapper.class);
if (objectMapper == null) {
return defaultWriter;
}

return contextResolverMap.computeIfAbsent(objectMapper, new Function<>() {
@Override
public ObjectWriter apply(ObjectMapper objectMapper) {
return createDefaultWriter(objectMapper);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
import org.jboss.resteasy.reactive.client.spi.ClientRestHandler;
import org.jboss.resteasy.reactive.common.core.Serialisers;
import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl;
import org.jboss.resteasy.reactive.common.util.CaseInsensitiveMap;

public class ClientReaderInterceptorContextImpl extends AbstractClientInterceptorContextImpl
implements ReaderInterceptorContext {

final RestClientRequestContext clientRequestContext;
final ConfigurationImpl configuration;
final Serialisers serialisers;
InputStream inputStream;
Expand All @@ -30,11 +32,13 @@ public class ClientReaderInterceptorContextImpl extends AbstractClientIntercepto
private final MultivaluedMap<String, String> headers = new CaseInsensitiveMap<>();

public ClientReaderInterceptorContextImpl(Annotation[] annotations, Class<?> entityClass, Type entityType,
MediaType mediaType,
Map<String, Object> properties, MultivaluedMap<String, String> headers,
MediaType mediaType, Map<String, Object> properties,
RestClientRequestContext clientRequestContext,
MultivaluedMap<String, String> headers,
ConfigurationImpl configuration, Serialisers serialisers, InputStream inputStream,
ReaderInterceptor[] interceptors) {
super(annotations, entityClass, entityType, mediaType, properties);
this.clientRequestContext = clientRequestContext;
this.configuration = configuration;
this.serialisers = serialisers;
this.inputStream = inputStream;
Expand All @@ -51,6 +55,13 @@ public Object proceed() throws IOException, WebApplicationException {
for (MessageBodyReader<?> reader : readers) {
if (reader.isReadable(entityClass, entityType, annotations, mediaType)) {
try {
if (reader instanceof ClientRestHandler) {
try {
((ClientRestHandler) reader).handle(clientRequestContext);
} catch (Exception e) {
throw new WebApplicationException("Can't inject the client request context", e);
}
}
return ((MessageBodyReader) reader).readFrom(entityClass, entityType, annotations, mediaType, headers,
inputStream);
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected <T> T readEntity(Class<T> entityType, Type genericType, Annotation[] a
MediaType mediaType = getMediaType();
try {
entity = ClientSerialisers.invokeClientReader(annotations, entityType, genericType, mediaType,
restClientRequestContext.properties, getStringHeaders(),
restClientRequestContext.properties, restClientRequestContext, getStringHeaders(),
restClientRequestContext.getRestClient().getClientContext().getSerialisers(),
entityStream, restClientRequestContext.getReaderInterceptors(), restClientRequestContext.configuration);
consumed = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected <OtherT> OtherT readEntity(Class<OtherT> entityType, Type genericType,
MediaType mediaType = getMediaType();
try {
entity = (T) ClientSerialisers.invokeClientReader(annotations, entityType, genericType, mediaType,
restClientRequestContext.properties, getStringHeaders(),
restClientRequestContext.properties, restClientRequestContext, getStringHeaders(),
restClientRequestContext.getRestClient().getClientContext().getSerialisers(),
entityStream, restClientRequestContext.getReaderInterceptors(), restClientRequestContext.configuration);
consumed = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import javax.ws.rs.ext.WriterInterceptor;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.client.providers.serialisers.ClientDefaultTextPlainBodyHandler;
import org.jboss.resteasy.reactive.client.spi.ClientRestHandler;
import org.jboss.resteasy.reactive.common.core.Serialisers;
import org.jboss.resteasy.reactive.common.core.UnmanagedBeanFactory;
import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl;
Expand Down Expand Up @@ -104,45 +105,55 @@ public class ClientSerialisers extends Serialisers {
// FIXME: pass InvocationState to wrap args?
public static Buffer invokeClientWriter(Entity<?> entity, Object entityObject, Class<?> entityClass, Type entityType,
MultivaluedMap<String, String> headerMap, MessageBodyWriter writer, WriterInterceptor[] writerInterceptors,
Map<String, Object> properties, Serialisers serialisers, ConfigurationImpl configuration)
Map<String, Object> properties, RestClientRequestContext clientRequestContext, Serialisers serialisers,
ConfigurationImpl configuration)
throws IOException {

if (writer.isWriteable(entityClass, entityType, entity.getAnnotations(), entity.getMediaType())) {
if ((writerInterceptors == null) || writerInterceptors.length == 0) {
VertxBufferOutputStream out = new VertxBufferOutputStream();
if (writer instanceof ClientRestHandler) {
try {
((ClientRestHandler) writer).handle(clientRequestContext);
} catch (Exception e) {
throw new WebApplicationException("Can't inject the client request context", e);
}
}

writer.writeTo(entityObject, entityClass, entityType, entity.getAnnotations(),
entity.getMediaType(), headerMap, out);
return out.getBuffer();
} else {
return runClientWriterInterceptors(entityObject, entityClass, entityType, entity.getAnnotations(),
entity.getMediaType(), headerMap, writer, writerInterceptors, properties, serialisers,
configuration);
entity.getMediaType(), headerMap, writer, writerInterceptors, properties, clientRequestContext,
serialisers, configuration);
}
}

return null;
}

public static Buffer runClientWriterInterceptors(Object entity, Class<?> entityClass, Type entityType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> headers, MessageBodyWriter writer,
WriterInterceptor[] writerInterceptors, Map<String, Object> properties, Serialisers serialisers,
Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> headers, MessageBodyWriter writer,
WriterInterceptor[] writerInterceptors, Map<String, Object> properties,
RestClientRequestContext clientRequestContext, Serialisers serialisers,
ConfigurationImpl configuration) throws IOException {
ClientWriterInterceptorContextImpl wc = new ClientWriterInterceptorContextImpl(writerInterceptors, writer,
annotations, entityClass, entityType, entity, mediaType, headers, properties, serialisers, configuration);
annotations, entityClass, entityType, entity, mediaType, headers, properties, clientRequestContext, serialisers,
configuration);
wc.proceed();
return wc.getResult();
}

public static Object invokeClientReader(Annotation[] annotations, Class<?> entityClass, Type entityType,
MediaType mediaType, Map<String, Object> properties,
MediaType mediaType, Map<String, Object> properties, RestClientRequestContext clientRequestContext,
MultivaluedMap metadata, Serialisers serialisers, InputStream in, ReaderInterceptor[] interceptors,
ConfigurationImpl configuration)
throws WebApplicationException, IOException {
// FIXME: perhaps optimise for when we have no interceptor?
ClientReaderInterceptorContextImpl context = new ClientReaderInterceptorContextImpl(annotations,
entityClass, entityType, mediaType,
properties, metadata, configuration, serialisers, in, interceptors);
entityClass, entityType, mediaType, properties, clientRequestContext,
metadata, configuration, serialisers, in, interceptors);
return context.proceed();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
import org.jboss.resteasy.reactive.client.spi.ClientRestHandler;
import org.jboss.resteasy.reactive.common.core.Serialisers;
import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl;

Expand All @@ -25,6 +26,7 @@ public class ClientWriterInterceptorContextImpl extends AbstractClientIntercepto
boolean done = false;
private int index = 0;
private OutputStream outputStream = baos;
private final RestClientRequestContext clientRequestContext;
private final Serialisers serialisers;
private final ConfigurationImpl configuration;
// as the interceptors can change the type or mediaType, when that happens we need to find a new reader/writer
Expand All @@ -40,8 +42,9 @@ public class ClientWriterInterceptorContextImpl extends AbstractClientIntercepto
public ClientWriterInterceptorContextImpl(WriterInterceptor[] writerInterceptors, MessageBodyWriter writer,
Annotation[] annotations, Class<?> entityClass, Type entityType, Object entity,
MediaType mediaType, MultivaluedMap<String, String> headers, Map<String, Object> properties,
Serialisers serialisers, ConfigurationImpl configuration) {
RestClientRequestContext clientRequestContext, Serialisers serialisers, ConfigurationImpl configuration) {
super(annotations, entityClass, entityType, mediaType, properties);
this.clientRequestContext = clientRequestContext;
this.interceptors = writerInterceptors;
this.writer = writer;
this.entity = entity;
Expand All @@ -63,6 +66,15 @@ public void proceed() throws IOException, WebApplicationException {
}
effectiveWriter = newWriters.get(0);
}

if (effectiveWriter instanceof ClientRestHandler) {
try {
((ClientRestHandler) effectiveWriter).handle(clientRequestContext);
} catch (Exception e) {
throw new WebApplicationException("Can't inject the client request context", e);
}
}

effectiveWriter.writeTo(entity, entityClass, entityType,
annotations, mediaType, headers, outputStream);
outputStream.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public <T> T readData(GenericType<T> type, MediaType mediaType) {
InputStream in = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
try {
return (T) ClientSerialisers.invokeClientReader(null, type.getRawType(), type.getType(),
mediaType, null, Serialisers.EMPTY_MULTI_MAP,
mediaType, null, null, Serialisers.EMPTY_MULTI_MAP,
serialisers, in, Serialisers.NO_READER_INTERCEPTOR, configuration);
} catch (IOException e) {
throw new UncheckedIOException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ public <T> T readEntity(InputStream in,
if (in == null)
return null;
return (T) ClientSerialisers.invokeClientReader(null, responseType.getRawType(), responseType.getType(),
mediaType, properties, metadata, restClient.getClientContext().getSerialisers(), in, getReaderInterceptors(),
configuration);
mediaType, properties, this, metadata, restClient.getClientContext().getSerialisers(), in,
getReaderInterceptors(), configuration);
}

public ReaderInterceptor[] getReaderInterceptors() {
Expand Down Expand Up @@ -244,7 +244,7 @@ public Buffer writeEntity(Entity<?> entity, MultivaluedMap<String, String> heade
RuntimeType.CLIENT);
for (MessageBodyWriter<?> w : writers) {
Buffer ret = ClientSerialisers.invokeClientWriter(entity, entityObject, entityClass, entityType, headerMap, w,
interceptors, properties, restClient.getClientContext().getSerialisers(), configuration);
interceptors, properties, this, restClient.getClientContext().getSerialisers(), configuration);
if (ret != null) {
return ret;
}
Expand Down
Loading

0 comments on commit 9b9673b

Please sign in to comment.