Skip to content

Commit

Permalink
Warn when Resource method returns JSON but no JSON provider exists
Browse files Browse the repository at this point in the history
Resolves: quarkusio#22970
  • Loading branch information
geoand committed Jan 19, 2022
1 parent 362e58e commit 30ccd76
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,60 @@
import javax.ws.rs.core.MediaType;

import org.jboss.jandex.AnnotationInstance;
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.common.ResteasyReactiveConfig;
import org.jboss.resteasy.reactive.common.processor.DefaultProducesHandler;
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;
import org.jboss.resteasy.reactive.server.processor.ServerIndexedParameter;
import org.jboss.resteasy.reactive.server.spi.EndpointInvokerFactory;

import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.JsonDefaultProducersHandler;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder;

public class QuarkusServerEndpointIndexer
extends ServerEndpointIndexer {

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

private final Capabilities capabilities;
private final BuildProducer<GeneratedClassBuildItem> generatedClassBuildItemBuildProducer;
private final BuildProducer<BytecodeTransformerBuildItem> bytecodeTransformerBuildProducer;
private final BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer;
private final DefaultProducesHandler defaultProducesHandler;
private final JsonDefaultProducersHandler jsonDefaultProducersHandler;
private final ResteasyReactiveRecorder resteasyReactiveRecorder;

private final Predicate<String> applicationClassPredicate;

QuarkusServerEndpointIndexer(Builder builder) {
super(builder);
this.capabilities = builder.capabilities;
this.generatedClassBuildItemBuildProducer = builder.generatedClassBuildItemBuildProducer;
this.bytecodeTransformerBuildProducer = builder.bytecodeTransformerBuildProducer;
this.reflectiveClassProducer = builder.reflectiveClassProducer;
this.defaultProducesHandler = builder.defaultProducesHandler;
this.applicationClassPredicate = builder.applicationClassPredicate;
this.resteasyReactiveRecorder = builder.resteasyReactiveRecorder;
this.jsonDefaultProducersHandler = new JsonDefaultProducersHandler();
}

private DefaultProducesHandler.Context currentDefaultProducesContext;

@Override
protected String[] applyAdditionalDefaults(Type nonAsyncReturnType) {
List<MediaType> defaultMediaTypes = defaultProducesHandler.handle(new DefaultProducesHandler.Context() {
protected void setupApplyDefaults(Type nonAsyncReturnType) {
currentDefaultProducesContext = new DefaultProducesHandler.Context() {
@Override
public Type nonAsyncReturnType() {
return nonAsyncReturnType;
Expand All @@ -59,7 +75,12 @@ public IndexView index() {
public ResteasyReactiveConfig config() {
return config;
}
});
};
}

@Override
protected String[] applyAdditionalDefaults(Type nonAsyncReturnType) {
List<MediaType> defaultMediaTypes = defaultProducesHandler.handle(currentDefaultProducesContext);
if ((defaultMediaTypes != null) && !defaultMediaTypes.isEmpty()) {
String[] result = new String[defaultMediaTypes.size()];
for (int i = 0; i < defaultMediaTypes.size(); i++) {
Expand All @@ -80,13 +101,19 @@ protected boolean handleCustomParameter(Map<DotName, AnnotationInstance> anns, S

public static final class Builder extends AbstractBuilder<Builder> {

private final Capabilities capabilities;

private BuildProducer<GeneratedClassBuildItem> generatedClassBuildItemBuildProducer;
private BuildProducer<BytecodeTransformerBuildItem> bytecodeTransformerBuildProducer;
private BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer;
private ResteasyReactiveRecorder resteasyReactiveRecorder;
private DefaultProducesHandler defaultProducesHandler = DefaultProducesHandler.Noop.INSTANCE;
public Predicate<String> applicationClassPredicate;

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

@Override
public QuarkusServerEndpointIndexer build() {
return new QuarkusServerEndpointIndexer(this);
Expand Down Expand Up @@ -125,4 +152,55 @@ public Builder setDefaultProducesHandler(DefaultProducesHandler defaultProducesH
return this;
}
}

@Override
protected void handleAdditionalMethodProcessing(ServerResourceMethod method, ClassInfo currentClassInfo,
MethodInfo info, AnnotationStore annotationStore) {
super.handleAdditionalMethodProcessing(method, currentClassInfo, info, annotationStore);
warnAboutMissingJsonProviderIfNeeded(method, info);
}

private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, MethodInfo info) {
if (!capabilities.isCapabilityWithPrefixMissing("io.quarkus.resteasy.reactive.json")) {
return;
}
if (hasJson(method) || isDefaultJson()) {
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 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.getSseElementType());
}

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

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

0 comments on commit 30ccd76

Please sign in to comment.