Skip to content

Commit

Permalink
Merge pull request #35590 from geoand/#35586
Browse files Browse the repository at this point in the history
Warn about missing JSON extension when using REST Client
  • Loading branch information
geoand authored Aug 28, 2023
2 parents 193e629 + c7767a2 commit 1347a17
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ public interface Capability {
String REST = QUARKUS_PREFIX + ".rest";
String REST_CLIENT = REST + ".client";
String REST_CLIENT_REACTIVE = REST_CLIENT + ".reactive";
String REST_CLIENT_REACTIVE_JACKSON = REST_CLIENT_REACTIVE + ".jackson";
String REST_CLIENT_REACTIVE_JSON = REST_CLIENT + ".reactive.json";
String REST_CLIENT_REACTIVE_JACKSON = REST_CLIENT_REACTIVE_JSON + ".jackson";
String REST_CLIENT_REACTIVE_JSONB = REST_CLIENT_REACTIVE_JSON + ".jsonb";
String REST_JACKSON = REST + ".jackson";
String REST_JSONB = REST + ".jsonb";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ void setupClientProxies(JaxrsClientReactiveRecorder recorder,
AdditionalWriters additionalWriters = new AdditionalWriters();

IndexView index = beanArchiveIndexBuildItem.getIndex();
ClientEndpointIndexer clientEndpointIndexer = new ClientEndpointIndexer.Builder()
ClientEndpointIndexer clientEndpointIndexer = new QuarkusClientEndpointIndexer.Builder(capabilities)
.setIndex(index)
.setApplicationIndex(applicationIndexBuildItem.getIndex())
.setExistingConverters(new HashMap<>())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.quarkus.jaxrs.client.reactive.deployment;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.client.processor.scanning.ClientEndpointIndexer;
import org.jboss.resteasy.reactive.common.ResteasyReactiveConfig;
import org.jboss.resteasy.reactive.common.model.ResourceMethod;
import org.jboss.resteasy.reactive.common.processor.DefaultProducesHandler;
import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore;

import io.quarkus.deployment.Capabilities;
import io.quarkus.resteasy.reactive.common.deployment.JsonDefaultProducersHandler;

public class QuarkusClientEndpointIndexer extends ClientEndpointIndexer {

private static final org.jboss.logging.Logger LOGGER = Logger.getLogger(QuarkusClientEndpointIndexer.class);

private final JsonDefaultProducersHandler jsonDefaultProducersHandler;
private final Capabilities capabilities;

QuarkusClientEndpointIndexer(Builder builder, String defaultProduces, boolean smartDefaultProduces) {
super(builder, defaultProduces, smartDefaultProduces);
capabilities = builder.capabilities;
jsonDefaultProducersHandler = new JsonDefaultProducersHandler();
}

private DefaultProducesHandler.Context currentDefaultProducesContext;

@Override
protected void setupApplyDefaults(Type nonAsyncReturnType, DotName httpMethod) {
currentDefaultProducesContext = new DefaultProducesHandler.Context() {
@Override
public Type nonAsyncReturnType() {
return nonAsyncReturnType;
}

@Override
public DotName httpMethod() {
return httpMethod;
}

@Override
public IndexView index() {
return applicationIndex;
}

@Override
public ResteasyReactiveConfig config() {
return config;
}
};
}

@Override
protected void handleAdditionalMethodProcessing(ResourceMethod method, ClassInfo currentClassInfo,
MethodInfo info, AnnotationStore annotationStore) {
super.handleAdditionalMethodProcessing(method, currentClassInfo, info, annotationStore);
if (!capabilities.isCapabilityWithPrefixMissing("io.quarkus.rest.client.reactive.json")) {
return;
}
warnAboutMissingJsonProviderIfNeeded(method, info, jsonDefaultProducersHandler, currentDefaultProducesContext);
}

@Override
protected void logMissingJsonWarning(MethodInfo info) {
LOGGER.warnf("Quarkus detected the use of JSON in REST Client method '" + info.declaringClass().name() + "#"
+ info.name()
+ "' but no JSON extension has been added. Consider adding 'quarkus-rest-client-reactive-jackson' (recommended) or 'quarkus-rest-client-reactive-jsonb'.");
}

public static final class Builder extends AbstractBuilder<Builder> {

private final Capabilities capabilities;

public Builder(Capabilities capabilities) {
this.capabilities = capabilities;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
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;
import org.jboss.resteasy.reactive.common.processor.scanning.SerializerScanningResult;
import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore;
import org.jboss.resteasy.reactive.server.model.ServerResourceMethod;
import org.jboss.resteasy.reactive.server.processor.ServerEndpointIndexer;
Expand All @@ -49,8 +47,6 @@ public class QuarkusServerEndpointIndexer
private final JsonDefaultProducersHandler jsonDefaultProducersHandler;
private final ResteasyReactiveRecorder resteasyReactiveRecorder;

private SerializerScanningResult serializerScanningResult;

QuarkusServerEndpointIndexer(Builder builder) {
super(builder);
this.capabilities = builder.capabilities;
Expand Down Expand Up @@ -152,7 +148,12 @@ public Builder setDefaultProducesHandler(DefaultProducesHandler defaultProducesH
protected void handleAdditionalMethodProcessing(ServerResourceMethod method, ClassInfo currentClassInfo,
MethodInfo info, AnnotationStore annotationStore) {
super.handleAdditionalMethodProcessing(method, currentClassInfo, info, annotationStore);
warnAboutMissingJsonProviderIfNeeded(method, info);

if (!capabilities.isCapabilityWithPrefixMissing("io.quarkus.resteasy.reactive.json")) {
return;
}

warnAboutMissingJsonProviderIfNeeded(method, info, jsonDefaultProducersHandler, currentDefaultProducesContext);
}

@Override
Expand Down Expand Up @@ -255,80 +256,11 @@ private boolean isServerMessageBodyWriter(ClassInfo classInfo) {
return index.getAllKnownImplementors(SERVER_MESSAGE_BODY_WRITER).contains(classInfo);
}

private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, MethodInfo info) {
if (!capabilities.isCapabilityWithPrefixMissing("io.quarkus.resteasy.reactive.json")) {
return;
}
if (hasJson(method) || (hasNoTypesDefined(method) && isDefaultJson())) {
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()
+ "' but no JSON extension has been added. Consider adding 'quarkus-resteasy-reactive-jackson' or 'quarkus-resteasy-reactive-jsonb'.");
}
}
}

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) {
for (String mt : provider.getMediaTypeStrings()) {
if (isJson(mt)) {
appProvidedJsonReaderExists = true;
break;
}
}
if (appProvidedJsonReaderExists) {
break;
}
}
return appProvidedJsonReaderExists;
}

private boolean isDefaultJson() {
List<MediaType> mediaTypes = jsonDefaultProducersHandler.handle(currentDefaultProducesContext);
for (MediaType mediaType : mediaTypes) {
if (isJson(mediaType.toString())) {
return true;
}
}
return false;
}

private boolean hasJson(ServerResourceMethod method) {
return hasJson(method.getProduces()) || hasJson(method.getConsumes()) || isJson(method.getStreamElementType());
}

private boolean hasNoTypesDefined(ServerResourceMethod method) {
return (method.getProduces() == null || method.getProduces().length == 0) &&
(method.getConsumes() == null || method.getConsumes().length == 0) &&
(method.getStreamElementType() == null);
}

private boolean hasJson(String[] types) {
if (types == null) {
return false;
}
for (String type : types) {
if (isJson(type)) {
return true;
}
}
return false;
@Override
protected void logMissingJsonWarning(MethodInfo info) {
LOGGER.warnf("Quarkus detected the use of JSON in JAX-RS method '" + info.declaringClass().name() + "#"
+ info.name()
+ "' but no JSON extension has been added. Consider adding 'quarkus-resteasy-reactive-jackson' (recommended) or 'quarkus-resteasy-reactive-jsonb'.");
}

private boolean isJson(String type) {
if (type == null) {
return false;
}
return type.startsWith(MediaType.APPLICATION_JSON);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<artifactId>quarkus-extension-maven-plugin</artifactId>
<configuration>
<capabilities>
<provides>io.quarkus.rest.client.reactive.jackson</provides>
<provides>io.quarkus.rest.client.reactive.json.jackson</provides>
</capabilities>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
<configuration>
<capabilities>
<provides>io.quarkus.rest.client.reactive.json.jsonb</provides>
</capabilities>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public class ClientEndpointIndexer
private final String[] defaultProducesNegotiated;
private final boolean smartDefaultProduces;

ClientEndpointIndexer(Builder builder, String defaultProduces, boolean smartDefaultProduces) {
public ClientEndpointIndexer(AbstractBuilder builder, String defaultProduces, boolean smartDefaultProduces) {
super(builder);
this.defaultProduces = new String[] { defaultProduces };
this.defaultProducesNegotiated = new String[] { defaultProduces, MediaType.WILDCARD };
Expand Down Expand Up @@ -230,23 +230,26 @@ public static class ClientIndexedParam extends IndexedParameter<ClientIndexedPar

}

public static final class Builder extends EndpointIndexer.Builder<ClientEndpointIndexer, Builder, ResourceMethod> {
public static abstract class AbstractBuilder<B extends EndpointIndexer.Builder<ClientEndpointIndexer, B, ResourceMethod>>
extends EndpointIndexer.Builder<ClientEndpointIndexer, B, ResourceMethod> {

private String defaultProduces = MediaType.TEXT_PLAIN;
private boolean smartDefaultProduces = true;

public Builder setDefaultProduces(String defaultProduces) {
public B setDefaultProduces(String defaultProduces) {
this.defaultProduces = defaultProduces;
return this;
return (B) this;
}

public Builder setSmartDefaultProduces(boolean smartDefaultProduces) {
public B setSmartDefaultProduces(boolean smartDefaultProduces) {
this.smartDefaultProduces = smartDefaultProduces;
return this;
return (B) this;
}

@Override
public ClientEndpointIndexer build() {
return new ClientEndpointIndexer(this, defaultProduces, smartDefaultProduces);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@
import org.jboss.resteasy.reactive.common.model.ResourceMethod;
import org.jboss.resteasy.reactive.common.processor.TargetJavaVersion.Status;
import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult;
import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner;
import org.jboss.resteasy.reactive.common.processor.scanning.ScannedSerializer;
import org.jboss.resteasy.reactive.common.processor.scanning.SerializerScanningResult;
import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore;
import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationsTransformer;
import org.jboss.resteasy.reactive.common.util.URLUtils;
Expand Down Expand Up @@ -231,6 +234,7 @@ public abstract class EndpointIndexer<T extends EndpointIndexer<T, PARAM, METHOD
private final Function<ClassInfo, Supplier<Boolean>> isDisabledCreator;

private final Predicate<Map<DotName, AnnotationInstance>> skipMethodParameter;
private SerializerScanningResult serializerScanningResult;

protected EndpointIndexer(Builder<T, ?, METHOD> builder) {
this.index = builder.index;
Expand Down Expand Up @@ -1568,6 +1572,85 @@ protected String getSeparator(Map<DotName, AnnotationInstance> annotations) {
return result;
}

protected boolean hasJson(String[] types) {
if (types == null) {
return false;
}
for (String type : types) {
if (isJson(type)) {
return true;
}
}
return false;
}

protected boolean isJson(String type) {
if (type == null) {
return false;
}
return type.startsWith(MediaType.APPLICATION_JSON);
}

protected boolean hasJson(ResourceMethod method) {
return hasJson(method.getProduces()) || hasJson(method.getConsumes()) || isJson(method.getStreamElementType());
}

protected boolean hasNoTypesDefined(ResourceMethod method) {
return (method.getProduces() == null || method.getProduces().length == 0) &&
(method.getConsumes() == null || method.getConsumes().length == 0) &&
(method.getStreamElementType() == null);
}

protected boolean isDefaultJson(DefaultProducesHandler jsonDefaultProducersHandler,
DefaultProducesHandler.Context context) {
List<MediaType> mediaTypes = jsonDefaultProducersHandler.handle(context);
for (MediaType mediaType : mediaTypes) {
if (isJson(mediaType.toString())) {
return true;
}
}
return false;
}

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

protected void warnAboutMissingJsonProviderIfNeeded(ResourceMethod method, MethodInfo info,
DefaultProducesHandler jsonDefaultProducersHandler,
DefaultProducesHandler.Context context) {
if (hasJson(method) || (hasNoTypesDefined(method) && isDefaultJson(jsonDefaultProducersHandler, context))) {
boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(getSerializerScanningResult().getReaders());
boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(getSerializerScanningResult().getWriters());
if (!appProvidedJsonReaderExists || !appProvidedJsonWriterExists) {
logMissingJsonWarning(info);
}
}
}

private boolean appProvidedJsonProviderExists(List<ScannedSerializer> providers) {
boolean appProvidedJsonReaderExists = false;
for (ScannedSerializer provider : providers) {
for (String mt : provider.getMediaTypeStrings()) {
if (isJson(mt)) {
appProvidedJsonReaderExists = true;
break;
}
}
if (appProvidedJsonReaderExists) {
break;
}
}
return appProvidedJsonReaderExists;
}

protected void logMissingJsonWarning(MethodInfo info) {

}

@SuppressWarnings({ "unchecked", "rawtypes" })
public static abstract class Builder<T extends EndpointIndexer<T, ?, METHOD>, B extends Builder<T, B, METHOD>, METHOD extends ResourceMethod> {
private Function<String, BeanFactory<Object>> factoryCreator;
Expand Down

0 comments on commit 1347a17

Please sign in to comment.