Skip to content
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 @@ -2,15 +2,20 @@

import java.util.function.Predicate;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;

import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.execannotations.ExecutionModelAnnotationsAllowedBuildItem;

public class JaxrsMethodsProcessor {
@BuildStep
ExecutionModelAnnotationsAllowedBuildItem jaxrsMethods() {
ExecutionModelAnnotationsAllowedBuildItem jaxrsMethods(BeanArchiveIndexBuildItem beanArchiveIndex) {
IndexView index = beanArchiveIndex.getIndex();
return new ExecutionModelAnnotationsAllowedBuildItem(new Predicate<MethodInfo>() {
@Override
public boolean test(MethodInfo method) {
Expand All @@ -19,7 +24,29 @@ public boolean test(MethodInfo method) {
if (method.declaringClass().hasDeclaredAnnotation(ResteasyReactiveDotNames.PATH)) {
return true;
}
if (isJaxrsResourceMethod(method)) {
return true;
}

// also look at interfaces implemented by the method's declaringClass
for (Type interfaceType : method.declaringClass().interfaceTypes()) {
ClassInfo interfaceInfo = index.getClassByName(interfaceType.name());
if (interfaceInfo != null) {
if (interfaceInfo.hasDeclaredAnnotation(ResteasyReactiveDotNames.PATH)) {
return true;
}
MethodInfo overriddenMethodInfo = interfaceInfo.method(method.name(),
method.parameterTypes().toArray(new Type[0]));
if (overriddenMethodInfo != null && isJaxrsResourceMethod(overriddenMethodInfo)) {
return true;
}
}
}

return false;
}

private boolean isJaxrsResourceMethod(MethodInfo method) {
// we currently don't handle custom @HttpMethod annotations, should be fine most of the time
return method.hasDeclaredAnnotation(ResteasyReactiveDotNames.PATH)
|| method.hasDeclaredAnnotation(ResteasyReactiveDotNames.GET)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf
}
Set<String> nameBindingNames = nameBindingNames(currentMethodInfo, classNameBindings);
boolean blocking = isBlocking(currentMethodInfo, defaultBlocking);
boolean runOnVirtualThread = isRunOnVirtualThread(currentMethodInfo, defaultBlocking);
boolean runOnVirtualThread = isRunOnVirtualThread(currentMethodInfo, blocking, defaultBlocking);
// we want to allow "overriding" the blocking/non-blocking setting from an implementation class
// when the class defining the annotations is an interface
if (!actualEndpointInfo.equals(currentClassInfo) && Modifier.isInterface(currentClassInfo.flags())) {
Expand All @@ -739,7 +739,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf
//would be reached for a default
blocking = isBlocking(actualMethodInfo,
blocking ? BlockingDefault.BLOCKING : BlockingDefault.NON_BLOCKING);
runOnVirtualThread = isRunOnVirtualThread(actualMethodInfo,
runOnVirtualThread = isRunOnVirtualThread(actualMethodInfo, blocking,
blocking ? BlockingDefault.BLOCKING : BlockingDefault.NON_BLOCKING);
}
}
Expand Down Expand Up @@ -841,8 +841,7 @@ private String getAnnotationValueAsString(AnnotationTarget target, DotName annot
return value;
}

private boolean isRunOnVirtualThread(MethodInfo info, BlockingDefault defaultValue) {
boolean isRunOnVirtualThread = false;
private boolean isRunOnVirtualThread(MethodInfo info, boolean blocking, BlockingDefault defaultValue) {
Map.Entry<AnnotationTarget, AnnotationInstance> runOnVirtualThreadAnnotation = getInheritableAnnotation(info,
RUN_ON_VIRTUAL_THREAD);

Expand All @@ -856,28 +855,19 @@ private boolean isRunOnVirtualThread(MethodInfo info, BlockingDefault defaultVal
throw new DeploymentException("Method '" + info.name() + "' of class '" + info.declaringClass().name()
+ "' uses @RunOnVirtualThread but the target JDK version doesn't support virtual threads. Please configure your build tool to target Java 19 or above");
}
isRunOnVirtualThread = true;
}

//BlockingDefault.BLOCKING should mean "block a platform thread" ? here it does
if (defaultValue == BlockingDefault.BLOCKING) {
return false;
if (!blocking) {
throw new DeploymentException(
"Method '" + info.name() + "' of class '" + info.declaringClass().name()
+ "' is considered a non blocking method. @RunOnVirtualThread can only be used on " +
" methods considered blocking");
} else {
return true;
}
} else if (defaultValue == BlockingDefault.RUN_ON_VIRTUAL_THREAD) {
isRunOnVirtualThread = true;
} else if (defaultValue == BlockingDefault.NON_BLOCKING) {
return false;
}

if (isRunOnVirtualThread && !isBlocking(info, defaultValue)) {
throw new DeploymentException(
"Method '" + info.name() + "' of class '" + info.declaringClass().name()
+ "' is considered a non blocking method. @RunOnVirtualThread can only be used on " +
" methods considered blocking");
} else if (isRunOnVirtualThread) {
return true;
} else {
return false;
}

return false;
}

private boolean isBlocking(MethodInfo info, BlockingDefault defaultValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,23 @@ public static ApplicationScanningResult scanForApplicationClass(IndexView index,
| InvocationTargetException e) {
throw new RuntimeException("Unable to handle class: " + applicationClass, e);
}
if (applicationClassInfo.declaredAnnotation(ResteasyReactiveDotNames.BLOCKING) != null) {
if (applicationClassInfo.declaredAnnotation(ResteasyReactiveDotNames.NON_BLOCKING) != null) {
throw new DeploymentException("JAX-RS Application class '" + applicationClassInfo.name()
+ "' contains both @Blocking and @NonBlocking annotations.");
}
// collect default behaviour, making sure that we don't have multiple contradicting annotations
int numAnnotations = 0;
if (applicationClassInfo.hasDeclaredAnnotation(ResteasyReactiveDotNames.BLOCKING)) {
blocking = BlockingDefault.BLOCKING;
} else if (applicationClassInfo.declaredAnnotation(ResteasyReactiveDotNames.NON_BLOCKING) != null) {
numAnnotations++;
}
if (applicationClassInfo.hasDeclaredAnnotation(ResteasyReactiveDotNames.NON_BLOCKING)) {
blocking = BlockingDefault.NON_BLOCKING;
numAnnotations++;
}
if (applicationClassInfo.hasDeclaredAnnotation(ResteasyReactiveDotNames.RUN_ON_VIRTUAL_THREAD)) {
blocking = BlockingDefault.RUN_ON_VIRTUAL_THREAD;
numAnnotations++;
}
if (numAnnotations > 1) {
throw new DeploymentException("JAX-RS Application class '" + applicationClassInfo.name()
+ "' contains multiple conflicting @Blocking, @NonBlocking and @RunOnVirtualThread annotations.");
}
}
if (selectedAppClass != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.virtual.rr;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;

@Path("/itf")
public interface IResource {

@GET
String testGet();

@POST
String testPost(String body);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.virtual.rr;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;

@Path("/itfOnClass")
public interface IResourceOnClass {

@GET
String testGet();

@POST
String testPost(String body);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.quarkus.virtual.rr;

import jakarta.enterprise.context.RequestScoped;

import io.quarkus.test.vertx.VirtualThreadsAssertions;
import io.smallrye.common.annotation.RunOnVirtualThread;

@RequestScoped
public class ResourceImpl implements IResource {

private final Counter counter;

ResourceImpl(Counter counter) {
this.counter = counter;
}

@RunOnVirtualThread
public String testGet() {
VirtualThreadsAssertions.assertEverything();
return "hello-" + counter.increment();
}

@RunOnVirtualThread
public String testPost(String body) {
VirtualThreadsAssertions.assertEverything();
return body + "-" + counter.increment();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.virtual.rr;

import jakarta.enterprise.context.RequestScoped;

import io.quarkus.test.vertx.VirtualThreadsAssertions;
import io.smallrye.common.annotation.RunOnVirtualThread;

@RequestScoped
@RunOnVirtualThread
public class ResourceOnClassImpl implements IResourceOnClass {

private final Counter counter;

ResourceOnClassImpl(Counter counter) {
this.counter = counter;
}

public String testGet() {
VirtualThreadsAssertions.assertEverything();
return "hello-" + counter.increment();
}

public String testPost(String body) {
VirtualThreadsAssertions.assertEverything();
return body + "-" + counter.increment();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.hamcrest.Matchers.is;

import java.util.Arrays;
import java.util.UUID;

import org.junit.jupiter.api.Test;
Expand All @@ -18,30 +19,42 @@ class RunOnVirtualThreadTest {

@Test
void testGet() {
RestAssured.get().then()
.assertThat().statusCode(200)
.body(is("hello-1"));
RestAssured.get().then()
.assertThat().statusCode(200)
// Same value - request scoped bean
.body(is("hello-1"));
// test all variations:
// - MyResource ("/"): simple JAX-RS bean
// - ResourceImpl ("/itf"): bean implementing a JAX-RS interface with VT annotation on the method
// - ResourceOnClassImpl ("/itfOnClass"): bean implementing a JAX-RS interface with VT annotation on the class
for (String url : Arrays.asList("/", "itf", "itfOnClass")) {
RestAssured.get(url).then()
.assertThat().statusCode(200)
.body(is("hello-1"));
RestAssured.get(url).then()
.assertThat().statusCode(200)
// Same value - request scoped bean
.body(is("hello-1"));
}
}

@Test
void testPost() {
var body1 = UUID.randomUUID().toString();
var body2 = UUID.randomUUID().toString();
RestAssured
.given().body(body1)
.post().then()
.assertThat().statusCode(200)
.body(is(body1 + "-1"));
RestAssured
.given().body(body2)
.post().then()
.assertThat().statusCode(200)
// Same value - request scoped bean
.body(is(body2 + "-1"));
// test all variations:
// - MyResource ("/"): simple JAX-RS bean
// - ResourceImpl ("/itf"): bean implementing a JAX-RS interface with VT annotation on the method
// - ResourceOnClassImpl ("/itfOnClass"): bean implementing a JAX-RS interface with VT annotation on the class
for (String url : Arrays.asList("/", "itf", "itfOnClass")) {
var body1 = UUID.randomUUID().toString();
var body2 = UUID.randomUUID().toString();
RestAssured
.given().body(body1)
.post(url).then()
.assertThat().statusCode(200)
.body(is(body1 + "-1"));
RestAssured
.given().body(body2)
.post(url).then()
.assertThat().statusCode(200)
// Same value - request scoped bean
.body(is(body2 + "-1"));
}
}

@Test
Expand Down
Loading