Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Fixed @path annotation-lookup

Include interfaces in lookup to resolve @path annotation in order to produce correct path-template used for metrics.
  • Loading branch information
lostiniceland committed Feb 8, 2023
1 parent aec9d78 commit 307f25d
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package io.quarkus.resteasy.deployment;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
Expand All @@ -17,6 +23,7 @@
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem;
import io.quarkus.resteasy.common.spi.ResteasyDotNames;
import io.quarkus.resteasy.runtime.QuarkusRestPathTemplate;
Expand Down Expand Up @@ -49,6 +56,7 @@ AdditionalBeanBuildItem registerBeanClasses(Capabilities capabilities,

@BuildStep
void findRestPaths(
CombinedIndexBuildItem index,
Capabilities capabilities, Optional<MetricsCapabilityBuildItem> metricsCapability,
BuildProducer<AnnotationsTransformerBuildItem> transformers,
Optional<ResteasyJaxrsConfigBuildItem> restApplicationPathBuildItem) {
Expand Down Expand Up @@ -80,7 +88,7 @@ public void transform(TransformationContext ctx) {
MethodInfo methodInfo = target.asMethod();
ClassInfo classInfo = methodInfo.declaringClass();

if (!isRestEndpointMethod(methodInfo)) {
if (!isRestEndpointMethod(index, methodInfo)) {
return;
}
// Don't create annotations for rest clients
Expand All @@ -93,13 +101,23 @@ public void transform(TransformationContext ctx) {
if (annotation != null) {
stringBuilder = new StringBuilder(slashify(annotation.value().asString()));
} else {
stringBuilder = new StringBuilder();
// Fallback: look for @Path on interface-method with same name
stringBuilder = searchPathAnnotationOnInterfaces(index, methodInfo)
.map(annotationInstance -> new StringBuilder(slashify(annotationInstance.value().asString())))
.orElse(new StringBuilder());
}

// Look for @Path annotation on the class
annotation = classInfo.classAnnotation(REST_PATH);
if (annotation != null) {
stringBuilder.insert(0, slashify(annotation.value().asString()));
} else {
// Fallback: look for @Path on interfaces
getAllClassInterfaces(index, List.of(classInfo), new ArrayList<>()).stream()
.filter(interfaceClassInfo -> interfaceClassInfo.hasAnnotation(REST_PATH))
.findFirst()
.map(interfaceClassInfo -> interfaceClassInfo.annotation(REST_PATH).value())
.ifPresent(annotationValue -> stringBuilder.insert(0, slashify(annotationValue.asString())));
}

if (restPathPrefix != null) {
Expand Down Expand Up @@ -134,7 +152,56 @@ String slashify(String path) {
return '/' + path;
}

static boolean isRestEndpointMethod(MethodInfo methodInfo) {
/**
* Searches for the same method as passed in methodInfo parameter in all implemented interfaces and yields an
* Optional containing the JAX-RS Path annotation.
*
* @param index Jandex-Index for additional lookup
* @param methodInfo the method to find
* @return Optional with the annotation if found. Never null.
*/
static Optional<AnnotationInstance> searchPathAnnotationOnInterfaces(CombinedIndexBuildItem index, MethodInfo methodInfo) {

Collection<ClassInfo> allClassInterfaces = getAllClassInterfaces(index, List.of(methodInfo.declaringClass()),
new ArrayList<>());

return allClassInterfaces.stream()
.map(interfaceClassInfo -> interfaceClassInfo.method(
methodInfo.name(),
methodInfo.parameterTypes().toArray(new Type[] {})))
.filter(Objects::nonNull)
.findFirst()
.map(resolvedMethodInfo -> resolvedMethodInfo.annotation(REST_PATH));
}

/**
* Recursively get all interfaces given as classInfo collection.
*
* @param index Jandex-Index for additional lookup
* @param classInfos the class(es) to search. Ends the recursion when empty.
* @param resultAcc accumulator for tail-recursion
* @return Collection of all interfaces und their parents. Never null.
*/
private static Collection<ClassInfo> getAllClassInterfaces(
CombinedIndexBuildItem index,
Collection<ClassInfo> classInfos,
List<ClassInfo> resultAcc) {
Objects.requireNonNull(index);
Objects.requireNonNull(classInfos);
Objects.requireNonNull(resultAcc);
if (classInfos.isEmpty()) {
return resultAcc;
}
List<ClassInfo> interfaces = classInfos.stream()
.flatMap(classInfo -> classInfo.interfaceNames().stream())
.map(dotName -> index.getIndex().getClassByName(dotName))
.filter(Objects::nonNull)
.collect(Collectors.toUnmodifiableList());
resultAcc.addAll(interfaces);
return getAllClassInterfaces(index, interfaces, resultAcc);
}

static boolean isRestEndpointMethod(CombinedIndexBuildItem index, MethodInfo methodInfo) {

if (!methodInfo.hasAnnotation(REST_PATH)) {
// Check for @Path on class and not method
Expand All @@ -143,7 +210,8 @@ static boolean isRestEndpointMethod(MethodInfo methodInfo) {
return true;
}
}
return false;
// Search for interface
return searchPathAnnotationOnInterfaces(index, methodInfo).isPresent();
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ void setUpDenyAllJaxRs(CombinedIndexBuildItem index,
ClassInfo classInfo = index.getIndex().getClassByName(DotName.createSimple(className));
if (!hasSecurityAnnotation(classInfo)) {
for (MethodInfo methodInfo : classInfo.methods()) {
if (isRestEndpointMethod(methodInfo) && !hasSecurityAnnotation(methodInfo)) {
if (isRestEndpointMethod(index, methodInfo) && !hasSecurityAnnotation(methodInfo)) {
methods.add(methodInfo);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import io.micrometer.core.instrument.MeterRegistry;

@Path("/message")
public class MessageResource {
public class MessageResource implements MessageResourceApi {

private final MeterRegistry registry;

Expand All @@ -32,15 +32,13 @@ public String item(@PathParam("id") String id) {
return "return message with id " + id;
}

@GET
@Path("match/{id}/{sub}")
public String match(@PathParam("id") String id, @PathParam("sub") String sub) {
@Override
public String match(String id, String sub) {
return "return message with id " + id + ", and sub " + sub;
}

@GET
@Path("match/{text}")
public String optional(@PathParam("text") String text) {
@Override
public String optional(String text) {
return "return message with text " + text;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.it.micrometer.prometheus;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

// Splitting JAX-RS annotations to interfaces tests the behaviour of
// io.quarkus.resteasy.deployment.RestPathAnnotationProcessor
public interface MessageResourceApi extends MessageResourceNestedApi {

@GET
@Path("match/{id}/{sub}")
String match(@PathParam("id") String id, @PathParam("sub") String sub);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.quarkus.it.micrometer.prometheus;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

// Testing deep lookup of Path-annotation
public interface MessageResourceNestedApi {

@GET
@Path("match/{text}")
String optional(@PathParam("text") String text);
}

0 comments on commit 307f25d

Please sign in to comment.