Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rest Client Reactive: Provide custom ObjectMapper in request scope #27203

Merged
merged 1 commit into from
Aug 26, 2022
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
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