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

Register Resource Class for reflection when custom Reader or Writer is used #32798

Merged
merged 1 commit into from
Apr 21, 2023
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 @@ -313,7 +313,7 @@ public boolean test(Map<DotName, AnnotationInstance> anns) {
})
.setResourceMethodCallback(new Consumer<>() {
@Override
public void accept(EndpointIndexer.ResourceMethodCallbackData entry) {
public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) {
MethodInfo method = entry.getMethodInfo();
String source = JaxrsClientReactiveProcessor.class.getSimpleName() + " > " + method.declaringClass()
+ "[" + method + "]";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.quarkus.resteasy.reactive.server.deployment;

import static org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames.SERVER_MESSAGE_BODY_READER;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
Expand All @@ -14,6 +17,8 @@
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.common.ResteasyReactiveConfig;
import org.jboss.resteasy.reactive.common.model.MethodParameter;
import org.jboss.resteasy.reactive.common.model.ParameterType;
import org.jboss.resteasy.reactive.common.processor.DefaultProducesHandler;
import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner;
import org.jboss.resteasy.reactive.common.processor.scanning.ScannedSerializer;
Expand All @@ -24,9 +29,11 @@
import org.jboss.resteasy.reactive.server.processor.ServerIndexedParameter;
import org.jboss.resteasy.reactive.server.spi.EndpointInvokerFactory;

import io.quarkus.builder.BuildException;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.util.JandexUtil;
import io.quarkus.resteasy.reactive.common.deployment.JsonDefaultProducersHandler;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder;

Expand Down Expand Up @@ -147,16 +154,73 @@ protected void handleAdditionalMethodProcessing(ServerResourceMethod method, Cla
warnAboutMissingJsonProviderIfNeeded(method, info);
}

@Override
public boolean additionalRegisterClassForReflectionCheck(ResourceMethodCallbackEntry entry) {
return checkBodyParameterMessageBodyReader(entry);
}

/**
* Check whether the Resource Method has a body parameter for which there exists a matching
* {@link jakarta.ws.rs.ext.MessageBodyReader}
* that is not a {@link org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader}.
* In this case the Resource Class needs to be registered for reflection because the
* {@link jakarta.ws.rs.ext.MessageBodyReader#isReadable(Class, java.lang.reflect.Type, Annotation[], MediaType)}
* method expects to be passed the method annotations.
*/
private boolean checkBodyParameterMessageBodyReader(ResourceMethodCallbackEntry entry) {
MethodParameter[] parameters = entry.getResourceMethod().getParameters();
if (parameters.length == 0) {
return false;
}
MethodParameter bodyParameter = null;
for (MethodParameter parameter : parameters) {
if (parameter.parameterType == ParameterType.BODY) {
bodyParameter = parameter;
break;
}
}
if (bodyParameter == null) {
return false;
}
String parameterClassName = bodyParameter.getDeclaredType();
List<ScannedSerializer> readers = getSerializerScanningResult().getReaders();

for (ScannedSerializer reader : readers) {
if (isSubclassOf(parameterClassName, reader.getHandledClassName()) && !isServerMessageBodyReader(
reader.getClassInfo())) {
return true;
}
}
return false;
}

private boolean isSubclassOf(String className, String parentName) {
if (className.equals(parentName)) {
return true;
}
ClassInfo classByName = index.getClassByName(className);
if (classByName == null) {
return false;
}
try {
return JandexUtil.isSubclassOf(index, classByName,
DotName.createSimple(parentName));
} catch (BuildException e) {
return false;
}
}

private boolean isServerMessageBodyReader(ClassInfo readerClassInfo) {
return index.getAllKnownImplementors(SERVER_MESSAGE_BODY_READER).contains(readerClassInfo);
}

private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, MethodInfo info) {
if (!capabilities.isCapabilityWithPrefixMissing("io.quarkus.resteasy.reactive.json")) {
return;
}
if (hasJson(method) || (hasNoTypesDefined(method) && isDefaultJson())) {
if (serializerScanningResult == null) {
serializerScanningResult = ResteasyReactiveScanner.scanForSerializers(index, applicationScanningResult);
}
boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(serializerScanningResult.getReaders());
boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(serializerScanningResult.getWriters());
boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(getSerializerScanningResult().getReaders());
boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(getSerializerScanningResult().getWriters());
if (!appProvidedJsonReaderExists || !appProvidedJsonWriterExists) {
LOGGER.warnf("Quarkus detected the use of JSON in JAX-RS method '" + info.declaringClass().name() + "#"
+ info.name()
Expand All @@ -165,6 +229,13 @@ private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, M
}
}

private SerializerScanningResult getSerializerScanningResult() {
if (serializerScanningResult == null) {
serializerScanningResult = ResteasyReactiveScanner.scanForSerializers(index, applicationScanningResult);
}
return serializerScanningResult;
}

private boolean appProvidedJsonProviderExists(List<ScannedSerializer> providers) {
boolean appProvidedJsonReaderExists = false;
for (ScannedSerializer provider : providers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ public void setupEndpoints(ApplicationIndexBuildItem applicationIndexBuildItem,
: Collections.emptyMap())
.setResourceMethodCallback(new Consumer<>() {
@Override
public void accept(EndpointIndexer.ResourceMethodCallbackData entry) {
public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) {
MethodInfo method = entry.getMethodInfo();

resourceMethodEntries.add(new ResteasyReactiveResourceMethodEntriesBuildItem.Entry(
Expand Down Expand Up @@ -545,18 +545,27 @@ public void accept(EndpointIndexer.ResourceMethodCallbackData entry) {
.build());
}
if (parameterType.name().equals(FILE)) {
reflectiveClassBuildItemBuildProducer
.produce(ReflectiveClassBuildItem
.builder(entry.getActualEndpointInfo().name().toString())
.constructors(false).methods().build());
minimallyRegisterResourceClassForReflection(entry,
reflectiveClassBuildItemBuildProducer);
}
}

if (filtersAccessResourceMethod) {
reflectiveClassBuildItemBuildProducer.produce(
ReflectiveClassBuildItem.builder(entry.getActualEndpointInfo().name().toString())
.constructors(false).methods().build());
minimallyRegisterResourceClassForReflection(entry, reflectiveClassBuildItemBuildProducer);
}

if (entry.additionalRegisterClassForReflectionCheck()) {
minimallyRegisterResourceClassForReflection(entry, reflectiveClassBuildItemBuildProducer);
}
}

private void minimallyRegisterResourceClassForReflection(
EndpointIndexer.ResourceMethodCallbackEntry entry,
BuildProducer<ReflectiveClassBuildItem> reflectiveClassBuildItemBuildProducer) {
reflectiveClassBuildItemBuildProducer
.produce(ReflectiveClassBuildItem
.builder(entry.getActualEndpointInfo().name().toString())
.constructors(false).methods().build());
}

private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName annotation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ public abstract class EndpointIndexer<T extends EndpointIndexer<T, PARAM, METHOD
private final BlockingDefault defaultBlocking;
private final Map<DotName, Map<String, String>> classLevelExceptionMappers;
private final Function<String, BeanFactory<Object>> factoryCreator;
private final Consumer<ResourceMethodCallbackData> resourceMethodCallback;
private final Consumer<ResourceMethodCallbackEntry> resourceMethodCallback;
private final AnnotationStore annotationStore;
protected final ApplicationScanningResult applicationScanningResult;
private final Set<DotName> contextTypes;
Expand Down Expand Up @@ -773,7 +773,9 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf
handleAdditionalMethodProcessing((METHOD) method, currentClassInfo, currentMethodInfo, getAnnotationStore());
if (resourceMethodCallback != null) {
resourceMethodCallback.accept(
new ResourceMethodCallbackData(basicResourceClassInfo, actualEndpointInfo, currentMethodInfo, method));
new ResourceMethodCallbackEntry(this, index, basicResourceClassInfo, actualEndpointInfo,
currentMethodInfo,
method));
}
return method;
} catch (Exception e) {
Expand Down Expand Up @@ -923,6 +925,10 @@ protected void handleAdditionalMethodProcessing(METHOD method, ClassInfo current

}

public boolean additionalRegisterClassForReflectionCheck(ResourceMethodCallbackEntry entry) {
return false;
}

protected abstract InjectableBean scanInjectableBean(ClassInfo currentClassInfo,
ClassInfo actualEndpointInfo,
Map<String, String> existingConverters,
Expand Down Expand Up @@ -1581,7 +1587,7 @@ public static abstract class Builder<T extends EndpointIndexer<T, ?, METHOD>, B
private AdditionalWriters additionalWriters;
private boolean hasRuntimeConverters;
private Map<DotName, Map<String, String>> classLevelExceptionMappers;
private Consumer<ResourceMethodCallbackData> resourceMethodCallback;
private Consumer<ResourceMethodCallbackEntry> resourceMethodCallback;
private Collection<AnnotationsTransformer> annotationsTransformers;
private ApplicationScanningResult applicationScanningResult;
private final Set<DotName> contextTypes = new HashSet<>(DEFAULT_CONTEXT_TYPES);
Expand Down Expand Up @@ -1689,7 +1695,7 @@ public B setClassLevelExceptionMappers(Map<DotName, Map<String, String>> classLe
return (B) this;
}

public B setResourceMethodCallback(Consumer<ResourceMethodCallbackData> resourceMethodCallback) {
public B setResourceMethodCallback(Consumer<ResourceMethodCallbackEntry> resourceMethodCallback) {
this.resourceMethodCallback = resourceMethodCallback;
return (B) this;
}
Expand Down Expand Up @@ -1761,14 +1767,22 @@ public String getStreamElementType() {
}
}

public static class ResourceMethodCallbackData {
@SuppressWarnings("rawtypes")
public static class ResourceMethodCallbackEntry {

private final EndpointIndexer indexer;
private final IndexView index;
private final BasicResourceClassInfo basicResourceClassInfo;
private final ClassInfo actualEndpointInfo;
private final MethodInfo methodInfo;
private final ResourceMethod resourceMethod;

public ResourceMethodCallbackData(BasicResourceClassInfo basicResourceClassInfo, ClassInfo actualEndpointInfo,
public ResourceMethodCallbackEntry(EndpointIndexer indexer, IndexView index,
BasicResourceClassInfo basicResourceClassInfo,
ClassInfo actualEndpointInfo,
MethodInfo methodInfo, ResourceMethod resourceMethod) {
this.indexer = indexer;
this.index = index;
this.basicResourceClassInfo = basicResourceClassInfo;
this.methodInfo = methodInfo;
this.actualEndpointInfo = actualEndpointInfo;
Expand All @@ -1790,6 +1804,10 @@ public ClassInfo getActualEndpointInfo() {
public ResourceMethod getResourceMethod() {
return resourceMethod;
}

public boolean additionalRegisterClassForReflectionCheck() {
return indexer.additionalRegisterClassForReflectionCheck(this);
}
}

public static class DeclaredTypes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ public static SerializerScanningResult scanForSerializers(IndexView index,
runtimeType = RuntimeType.SERVER;
}
List<String> mediaTypeStrings = Collections.emptyList();
String readerClassName = readerClass.name().toString();
AnnotationInstance consumesAnnotation = readerClass.classAnnotation(ResteasyReactiveDotNames.CONSUMES);
if (consumesAnnotation != null) {
mediaTypeStrings = Arrays.asList(consumesAnnotation.value().asStringArray());
Expand All @@ -167,7 +166,7 @@ public static SerializerScanningResult scanForSerializers(IndexView index,
if (priorityInstance != null) {
priority = priorityInstance.value().asInt();
}
readerList.add(new ScannedSerializer(readerClassName,
readerList.add(new ScannedSerializer(readerClass,
typeParameters.get(0).name().toString(), mediaTypeStrings, runtimeType, false, priority));
}
}
Expand All @@ -192,7 +191,6 @@ public static SerializerScanningResult scanForSerializers(IndexView index,
List<Type> typeParameters = JandexUtil.resolveTypeParameters(writerClass.name(),
ResteasyReactiveDotNames.MESSAGE_BODY_WRITER,
index);
String writerClassName = writerClass.name().toString();
AnnotationInstance constrainedToInstance = writerClass.classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO);
if (constrainedToInstance != null) {
runtimeType = RuntimeType.valueOf(constrainedToInstance.value().asEnum());
Expand All @@ -202,7 +200,7 @@ public static SerializerScanningResult scanForSerializers(IndexView index,
if (priorityInstance != null) {
priority = priorityInstance.value().asInt();
}
writerList.add(new ScannedSerializer(writerClassName,
writerList.add(new ScannedSerializer(writerClass,
typeParameters.get(0).name().toString(), mediaTypeStrings, runtimeType, false, priority));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,35 @@
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.RuntimeType;

import org.jboss.jandex.ClassInfo;

public class ScannedSerializer {

private final ClassInfo classInfo;
private final String className;
private final String handledClassName;
private final List<String> mediaTypeStrings;
private final RuntimeType runtimeType;
private final boolean builtin;
private final Integer priority;

public ScannedSerializer(ClassInfo classInfo, String handledClassName, List<String> mediaTypeStrings) {
this(classInfo, handledClassName, mediaTypeStrings, null, true, Priorities.USER);
}

// used only for testing
public ScannedSerializer(String className, String handledClassName, List<String> mediaTypeStrings) {
this(className, handledClassName, mediaTypeStrings, null, true, Priorities.USER);
this(null, className, handledClassName, mediaTypeStrings, null, true, Priorities.USER);
}

public ScannedSerializer(String className, String handledClassName, List<String> mediaTypeStrings,
public ScannedSerializer(ClassInfo classInfo, String handledClassName, List<String> mediaTypeStrings,
RuntimeType runtimeType, boolean builtin, Integer priority) {
this(classInfo, classInfo.name().toString(), handledClassName, mediaTypeStrings, runtimeType, builtin, priority);
}

private ScannedSerializer(ClassInfo classInfo, String className, String handledClassName, List<String> mediaTypeStrings,
RuntimeType runtimeType, boolean builtin, Integer priority) {
this.classInfo = classInfo;
this.className = className;
this.handledClassName = handledClassName;
this.mediaTypeStrings = mediaTypeStrings;
Expand All @@ -28,6 +42,12 @@ public ScannedSerializer(String className, String handledClassName, List<String>
this.priority = priority;
}

// used only for tests

public ClassInfo getClassInfo() {
return classInfo;
}

public String getClassName() {
return className;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.it.envers;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("input")
public class InputResource {

@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
public String in(Message message) {
return message.getData();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.it.envers;

public class Message {

private String data;

public Message() {
}

public Message(String data) {
this.data = data;
}

public String getData() {
return data;
}

public void setData(String data) {
this.data = data;
}
}
Loading