Skip to content

Commit 7a0a0f9

Browse files
authored
Merge pull request #45223 from geoand/#32969
Lazily access `ObjectMapper` in Quarkus REST Jackson module
2 parents d4e46af + 07ba5e6 commit 7a0a0f9

File tree

5 files changed

+138
-18
lines changed

5 files changed

+138
-18
lines changed

extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
import java.io.IOException;
88
import java.io.InputStream;
99
import java.io.OutputStream;
10+
import java.lang.annotation.Annotation;
1011
import java.nio.charset.StandardCharsets;
1112
import java.util.ArrayList;
1213
import java.util.Collections;
14+
import java.util.Iterator;
1315
import java.util.List;
1416
import java.util.Objects;
1517

18+
import jakarta.enterprise.inject.Instance;
19+
import jakarta.enterprise.util.TypeLiteral;
1620
import jakarta.ws.rs.WebApplicationException;
1721
import jakarta.ws.rs.container.CompletionCallback;
1822
import jakarta.ws.rs.container.ConnectionCallback;
@@ -119,7 +123,8 @@ void shouldThrowInvalidDefinitionException() {
119123
@Nested
120124
@DisplayName("ServerJacksonMessageBodyReader")
121125
class ServerJacksonMessageBodyReaderTests {
122-
private final CommonReaderTests tests = new CommonReaderTests(new ServerJacksonMessageBodyReader(new ObjectMapper()));
126+
private final CommonReaderTests tests = new CommonReaderTests(
127+
new ServerJacksonMessageBodyReader(new NewObjectMapperInstance()));
123128

124129
@Test
125130
void shouldThrowWebExceptionWithStreamReadExceptionCause() {
@@ -146,7 +151,7 @@ void shouldThrowInvalidDefinitionException() {
146151

147152
@Test
148153
void shouldThrowWebExceptionWithValueInstantiationExceptionCauseUsingServerRequestContext() throws IOException {
149-
var reader = new ServerJacksonMessageBodyReader(new ObjectMapper());
154+
var reader = new ServerJacksonMessageBodyReader(new NewObjectMapperInstance());
150155
// missing non-nullable property
151156
var stream = new ByteArrayInputStream("{\"cost\": 2}".getBytes(StandardCharsets.UTF_8));
152157
var context = new MockServerRequestContext(stream);
@@ -279,4 +284,56 @@ public void abortWith(Response response) {
279284

280285
}
281286
}
287+
288+
private static class NewObjectMapperInstance implements Instance<ObjectMapper> {
289+
@Override
290+
public Instance<ObjectMapper> select(Annotation... qualifiers) {
291+
throw new IllegalStateException("Should never be called");
292+
}
293+
294+
@Override
295+
public <U extends ObjectMapper> Instance<U> select(Class<U> subtype, Annotation... qualifiers) {
296+
throw new IllegalStateException("Should never be called");
297+
}
298+
299+
@Override
300+
public <U extends ObjectMapper> Instance<U> select(TypeLiteral<U> subtype, Annotation... qualifiers) {
301+
throw new IllegalStateException("Should never be called");
302+
}
303+
304+
@Override
305+
public boolean isUnsatisfied() {
306+
throw new IllegalStateException("Should never be called");
307+
}
308+
309+
@Override
310+
public boolean isAmbiguous() {
311+
throw new IllegalStateException("Should never be called");
312+
}
313+
314+
@Override
315+
public void destroy(ObjectMapper instance) {
316+
throw new IllegalStateException("Should never be called");
317+
}
318+
319+
@Override
320+
public Handle<ObjectMapper> getHandle() {
321+
throw new IllegalStateException("Should never be called");
322+
}
323+
324+
@Override
325+
public Iterable<? extends Handle<ObjectMapper>> handles() {
326+
throw new IllegalStateException("Should never be called");
327+
}
328+
329+
@Override
330+
public ObjectMapper get() {
331+
return new ObjectMapper();
332+
}
333+
334+
@Override
335+
public Iterator<ObjectMapper> iterator() {
336+
throw new IllegalStateException("Should never be called");
337+
}
338+
}
282339
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.quarkus.resteasy.reactive.jackson.runtime.serialisers;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.lang.annotation.Annotation;
6+
import java.lang.reflect.Type;
7+
import java.util.function.Supplier;
8+
9+
import jakarta.enterprise.inject.Instance;
10+
import jakarta.ws.rs.WebApplicationException;
11+
import jakarta.ws.rs.core.MediaType;
12+
import jakarta.ws.rs.core.MultivaluedMap;
13+
14+
import org.jboss.resteasy.reactive.common.providers.serialisers.AbstractJsonMessageBodyReader;
15+
import org.jboss.resteasy.reactive.common.util.EmptyInputStream;
16+
17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
import com.fasterxml.jackson.databind.ObjectReader;
19+
20+
import io.quarkus.arc.impl.LazyValue;
21+
22+
public abstract class AbstractServerJacksonMessageBodyReader extends AbstractJsonMessageBodyReader {
23+
24+
protected final LazyValue<ObjectReader> defaultReader;
25+
26+
public AbstractServerJacksonMessageBodyReader(Instance<ObjectMapper> mapper) {
27+
this.defaultReader = new LazyValue<>(new Supplier<>() {
28+
@Override
29+
public ObjectReader get() {
30+
return mapper.get().reader();
31+
}
32+
});
33+
}
34+
35+
@Override
36+
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
37+
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException,
38+
WebApplicationException {
39+
return doReadFrom(type, genericType, entityStream);
40+
}
41+
42+
protected ObjectReader getEffectiveReader() {
43+
return defaultReader.get();
44+
}
45+
46+
private Object doReadFrom(Class<Object> type, Type genericType, InputStream entityStream) throws IOException {
47+
if (entityStream instanceof EmptyInputStream) {
48+
return null;
49+
}
50+
ObjectReader reader = getEffectiveReader();
51+
return reader.forType(reader.getTypeFactory().constructType(genericType != null ? genericType : type))
52+
.readValue(entityStream);
53+
}
54+
}

extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/BasicServerJacksonMessageBodyWriter.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import java.util.Map;
1212
import java.util.concurrent.ConcurrentHashMap;
1313
import java.util.function.Function;
14+
import java.util.function.Supplier;
1415

16+
import jakarta.enterprise.inject.Instance;
1517
import jakarta.inject.Inject;
1618
import jakarta.ws.rs.WebApplicationException;
1719
import jakarta.ws.rs.core.MediaType;
@@ -24,22 +26,28 @@
2426
import com.fasterxml.jackson.databind.ObjectMapper;
2527
import com.fasterxml.jackson.databind.ObjectWriter;
2628

29+
import io.quarkus.arc.impl.LazyValue;
2730
import io.quarkus.resteasy.reactive.jackson.runtime.mappers.JacksonMapperUtil;
2831

2932
public class BasicServerJacksonMessageBodyWriter extends ServerMessageBodyWriter.AllWriteableMessageBodyWriter {
3033

31-
private final ObjectWriter defaultWriter;
34+
private final LazyValue<ObjectWriter> defaultWriter;
3235
private final Map<JavaType, ObjectWriter> genericWriters = new ConcurrentHashMap<>();
3336

3437
@Inject
35-
public BasicServerJacksonMessageBodyWriter(ObjectMapper mapper) {
36-
this.defaultWriter = createDefaultWriter(mapper);
38+
public BasicServerJacksonMessageBodyWriter(Instance<ObjectMapper> mapper) {
39+
this.defaultWriter = new LazyValue<>(new Supplier<>() {
40+
@Override
41+
public ObjectWriter get() {
42+
return createDefaultWriter(mapper.get());
43+
}
44+
});
3745
}
3846

3947
private ObjectWriter getWriter(Type genericType, Object value) {
4048
// make sure we properly handle polymorphism in generic collections
4149
if (value != null && genericType != null) {
42-
JavaType rootType = JacksonMapperUtil.getGenericRootType(genericType, defaultWriter);
50+
JavaType rootType = JacksonMapperUtil.getGenericRootType(genericType, defaultWriter.get());
4351
// Check that the determined root type is really assignable from the given entity.
4452
// A mismatch can happen, if a ServerResponseFilter replaces the response entity with another object
4553
// that does not match the original signature of the method (see HalServerResponseFilter for an example)
@@ -50,7 +58,7 @@ private ObjectWriter getWriter(Type genericType, Object value) {
5058
writer = genericWriters.computeIfAbsent(rootType, new Function<>() {
5159
@Override
5260
public ObjectWriter apply(JavaType type) {
53-
return defaultWriter.forType(type);
61+
return defaultWriter.get().forType(type);
5462
}
5563
});
5664
}
@@ -59,7 +67,7 @@ public ObjectWriter apply(JavaType type) {
5967
}
6068

6169
// no generic type given, or the generic type is just a class. Use the default writer.
62-
return this.defaultWriter;
70+
return this.defaultWriter.get();
6371
}
6472

6573
@Override

extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyReader.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.function.BiFunction;
1313
import java.util.function.Function;
1414

15+
import jakarta.enterprise.inject.Instance;
1516
import jakarta.inject.Inject;
1617
import jakarta.ws.rs.WebApplicationException;
1718
import jakarta.ws.rs.core.MediaType;
@@ -22,7 +23,6 @@
2223

2324
import org.jboss.resteasy.reactive.common.util.StreamUtil;
2425
import org.jboss.resteasy.reactive.server.core.CurrentRequestManager;
25-
import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader;
2626
import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
2727
import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader;
2828
import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
@@ -36,18 +36,18 @@
3636

3737
import io.quarkus.resteasy.reactive.jackson.runtime.ResteasyReactiveServerJacksonRecorder;
3838

39-
public class FullyFeaturedServerJacksonMessageBodyReader extends JacksonBasicMessageBodyReader
39+
public class FullyFeaturedServerJacksonMessageBodyReader extends AbstractServerJacksonMessageBodyReader
4040
implements ServerMessageBodyReader<Object> {
4141

42-
private final ObjectMapper originalMapper;
42+
private final Instance<ObjectMapper> originalMapper;
4343
private final Providers providers;
4444
private final ConcurrentMap<String, ObjectReader> perMethodReader = new ConcurrentHashMap<>();
4545
private final ConcurrentMap<String, ObjectReader> perTypeReader = new ConcurrentHashMap<>();
4646
private final ConcurrentMap<Class<?>, ObjectMapper> contextResolverMap = new ConcurrentHashMap<>();
4747
private final ConcurrentMap<ObjectMapper, ObjectReader> objectReaderMap = new ConcurrentHashMap<>();
4848

4949
@Inject
50-
public FullyFeaturedServerJacksonMessageBodyReader(ObjectMapper mapper, Providers providers) {
50+
public FullyFeaturedServerJacksonMessageBodyReader(Instance<ObjectMapper> mapper, Providers providers) {
5151
super(mapper);
5252
this.originalMapper = mapper;
5353
this.providers = providers;
@@ -152,7 +152,7 @@ private ObjectReader getObjectReaderFromAnnotations(ResteasyReactiveResourceInfo
152152

153153
private ObjectReader getEffectiveReader(Class<Object> type, Type genericType, MediaType responseMediaType) {
154154
ObjectMapper effectiveMapper = getEffectiveMapper(type, responseMediaType);
155-
ObjectReader effectiveReader = defaultReader;
155+
ObjectReader effectiveReader = defaultReader.get();
156156
if (effectiveMapper != originalMapper) {
157157
// Effective reader based on the context
158158
effectiveReader = objectReaderMap.computeIfAbsent(effectiveMapper, new Function<>() {
@@ -192,7 +192,7 @@ public ObjectReader apply(ObjectMapper objectMapper) {
192192

193193
private ObjectMapper getEffectiveMapper(Class<Object> type, MediaType responseMediaType) {
194194
if (providers == null) {
195-
return originalMapper;
195+
return originalMapper.get();
196196
}
197197

198198
ContextResolver<ObjectMapper> contextResolver = providers.getContextResolver(ObjectMapper.class,
@@ -214,7 +214,7 @@ public ObjectMapper apply(Class<?> aClass) {
214214
}
215215
}
216216

217-
return originalMapper;
217+
return originalMapper.get();
218218
}
219219

220220
private static class MethodObjectReaderFunction implements Function<String, ObjectReader> {

extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/ServerJacksonMessageBodyReader.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
import java.lang.annotation.Annotation;
66
import java.lang.reflect.Type;
77

8+
import jakarta.enterprise.inject.Instance;
89
import jakarta.inject.Inject;
910
import jakarta.ws.rs.WebApplicationException;
1011
import jakarta.ws.rs.core.MediaType;
1112
import jakarta.ws.rs.core.MultivaluedMap;
1213
import jakarta.ws.rs.core.Response;
1314

1415
import org.jboss.resteasy.reactive.common.util.StreamUtil;
15-
import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader;
1616
import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
1717
import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader;
1818
import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
@@ -24,10 +24,11 @@
2424
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
2525
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
2626

27-
public class ServerJacksonMessageBodyReader extends JacksonBasicMessageBodyReader implements ServerMessageBodyReader<Object> {
27+
public class ServerJacksonMessageBodyReader extends AbstractServerJacksonMessageBodyReader
28+
implements ServerMessageBodyReader<Object> {
2829

2930
@Inject
30-
public ServerJacksonMessageBodyReader(ObjectMapper mapper) {
31+
public ServerJacksonMessageBodyReader(Instance<ObjectMapper> mapper) {
3132
super(mapper);
3233
}
3334

0 commit comments

Comments
 (0)