Skip to content

Commit 17517c5

Browse files
mkoubagsmet
authored andcommitted
WebSockets Next: produce ExecutionModelAnnotationsAllowedBuildItem
- so that callback methods can be annotated with Blocking, NonBlocking and RunOnVirtualThread (cherry picked from commit 8df1abe)
1 parent 44026ec commit 17517c5

File tree

4 files changed

+142
-8
lines changed

4 files changed

+142
-8
lines changed

extensions/websockets-next/server/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketDotNames.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.quarkus.websockets.next.deployment;
22

3+
import java.util.List;
4+
35
import org.jboss.jandex.DotName;
46

57
import io.quarkus.websockets.next.OnBinaryMessage;
@@ -12,6 +14,7 @@
1214
import io.quarkus.websockets.next.WebSocket;
1315
import io.quarkus.websockets.next.WebSocketConnection;
1416
import io.smallrye.common.annotation.Blocking;
17+
import io.smallrye.common.annotation.NonBlocking;
1518
import io.smallrye.common.annotation.RunOnVirtualThread;
1619
import io.smallrye.mutiny.Multi;
1720
import io.smallrye.mutiny.Uni;
@@ -33,6 +36,7 @@ final class WebSocketDotNames {
3336
static final DotName MULTI = DotName.createSimple(Multi.class);
3437
static final DotName RUN_ON_VIRTUAL_THREAD = DotName.createSimple(RunOnVirtualThread.class);
3538
static final DotName BLOCKING = DotName.createSimple(Blocking.class);
39+
static final DotName NON_BLOCKING = DotName.createSimple(NonBlocking.class);
3640
static final DotName STRING = DotName.createSimple(String.class);
3741
static final DotName BUFFER = DotName.createSimple(Buffer.class);
3842
static final DotName JSON_OBJECT = DotName.createSimple(JsonObject.class);
@@ -41,4 +45,7 @@ final class WebSocketDotNames {
4145
static final DotName PATH_PARAM = DotName.createSimple(PathParam.class);
4246
static final DotName HANDSHAKE_REQUEST = DotName.createSimple(WebSocketConnection.HandshakeRequest.class);
4347
static final DotName THROWABLE = DotName.createSimple(Throwable.class);
48+
49+
static final List<DotName> CALLBACK_ANNOTATIONS = List.of(ON_OPEN, ON_CLOSE, ON_BINARY_MESSAGE, ON_TEXT_MESSAGE,
50+
ON_PONG_MESSAGE, ON_ERROR);
4451
}

extensions/websockets-next/server/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketServerProcessor.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.Map;
1010
import java.util.function.Consumer;
1111
import java.util.function.Function;
12+
import java.util.function.Predicate;
1213
import java.util.regex.Matcher;
1314
import java.util.regex.Pattern;
1415

@@ -36,6 +37,7 @@
3637
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
3738
import io.quarkus.arc.deployment.TransformedAnnotationsBuildItem;
3839
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
40+
import io.quarkus.arc.processor.Annotations;
3941
import io.quarkus.arc.processor.BeanInfo;
4042
import io.quarkus.arc.processor.BuiltinScope;
4143
import io.quarkus.arc.processor.DotNames;
@@ -47,6 +49,7 @@
4749
import io.quarkus.deployment.builditem.FeatureBuildItem;
4850
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
4951
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
52+
import io.quarkus.deployment.execannotations.ExecutionModelAnnotationsAllowedBuildItem;
5053
import io.quarkus.gizmo.BytecodeCreator;
5154
import io.quarkus.gizmo.CatchBlockCreator;
5255
import io.quarkus.gizmo.ClassCreator;
@@ -117,6 +120,18 @@ void unremovableBeans(BuildProducer<UnremovableBeanBuildItem> unremovableBeans)
117120
unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(TextMessageCodec.class));
118121
}
119122

123+
@BuildStep
124+
ExecutionModelAnnotationsAllowedBuildItem executionModelAnnotations(
125+
TransformedAnnotationsBuildItem transformedAnnotations) {
126+
return new ExecutionModelAnnotationsAllowedBuildItem(new Predicate<MethodInfo>() {
127+
@Override
128+
public boolean test(MethodInfo method) {
129+
return Annotations.containsAny(transformedAnnotations.getAnnotations(method),
130+
WebSocketDotNames.CALLBACK_ANNOTATIONS);
131+
}
132+
});
133+
}
134+
120135
@BuildStep
121136
public void collectEndpoints(BeanArchiveIndexBuildItem beanArchiveIndex,
122137
BeanDiscoveryFinishedBuildItem beanDiscoveryFinished,
@@ -1006,7 +1021,8 @@ private List<Callback> findErrorHandlers(IndexView index, ClassInfo beanClass, C
10061021
List<Callback> errorHandlers = new ArrayList<>();
10071022
for (AnnotationInstance annotation : annotations) {
10081023
MethodInfo method = annotation.target().asMethod();
1009-
Callback callback = new Callback(annotation, method, executionModel(method), callbackArguments,
1024+
Callback callback = new Callback(annotation, method, executionModel(method, transformedAnnotations),
1025+
callbackArguments,
10101026
transformedAnnotations, endpointPath, index);
10111027
long errorArguments = callback.arguments.stream().filter(ca -> ca instanceof ErrorCallbackArgument).count();
10121028
if (errorArguments != 1) {
@@ -1052,7 +1068,8 @@ private Callback findCallback(IndexView index, ClassInfo beanClass, DotName anno
10521068
} else if (annotations.size() == 1) {
10531069
AnnotationInstance annotation = annotations.get(0);
10541070
MethodInfo method = annotation.target().asMethod();
1055-
Callback callback = new Callback(annotation, method, executionModel(method), callbackArguments,
1071+
Callback callback = new Callback(annotation, method, executionModel(method, transformedAnnotations),
1072+
callbackArguments,
10561073
transformedAnnotations, endpointPath, index);
10571074
long messageArguments = callback.arguments.stream().filter(ca -> ca instanceof MessageCallbackArgument).count();
10581075
if (callback.acceptsMessage()) {
@@ -1081,13 +1098,16 @@ private Callback findCallback(IndexView index, ClassInfo beanClass, DotName anno
10811098
String.format("There can be only one callback annotated with %s declared on %s", annotationName, beanClass));
10821099
}
10831100

1084-
ExecutionModel executionModel(MethodInfo method) {
1085-
if (hasBlockingSignature(method)) {
1086-
return method.hasDeclaredAnnotation(WebSocketDotNames.RUN_ON_VIRTUAL_THREAD) ? ExecutionModel.VIRTUAL_THREAD
1087-
: ExecutionModel.WORKER_THREAD;
1101+
ExecutionModel executionModel(MethodInfo method, TransformedAnnotationsBuildItem transformedAnnotations) {
1102+
if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)) {
1103+
return ExecutionModel.VIRTUAL_THREAD;
1104+
} else if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.BLOCKING)) {
1105+
return ExecutionModel.WORKER_THREAD;
1106+
} else if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.NON_BLOCKING)) {
1107+
return ExecutionModel.EVENT_LOOP;
1108+
} else {
1109+
return hasBlockingSignature(method) ? ExecutionModel.WORKER_THREAD : ExecutionModel.EVENT_LOOP;
10881110
}
1089-
return method.hasDeclaredAnnotation(WebSocketDotNames.BLOCKING) ? ExecutionModel.WORKER_THREAD
1090-
: ExecutionModel.EVENT_LOOP;
10911111
}
10921112

10931113
boolean hasBlockingSignature(MethodInfo method) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.quarkus.websockets.next.test.executionmodel;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.net.URI;
6+
7+
import jakarta.inject.Inject;
8+
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.extension.RegisterExtension;
11+
12+
import io.quarkus.test.QuarkusUnitTest;
13+
import io.quarkus.test.common.http.TestHTTPResource;
14+
import io.quarkus.websockets.next.OnTextMessage;
15+
import io.quarkus.websockets.next.WebSocket;
16+
import io.quarkus.websockets.next.test.utils.WSClient;
17+
import io.smallrye.common.annotation.Blocking;
18+
import io.smallrye.mutiny.Uni;
19+
import io.vertx.core.Context;
20+
import io.vertx.core.Vertx;
21+
22+
public class BlockingAnnotationTest {
23+
24+
@RegisterExtension
25+
public static final QuarkusUnitTest test = new QuarkusUnitTest()
26+
.withApplicationRoot(root -> {
27+
root.addClasses(Endpoint.class, WSClient.class);
28+
});
29+
30+
@Inject
31+
Vertx vertx;
32+
33+
@TestHTTPResource("endpoint")
34+
URI endUri;
35+
36+
@Test
37+
void testEndoint() {
38+
try (WSClient client = new WSClient(vertx).connect(endUri)) {
39+
assertEquals("evenloop:false,worker:true", client.sendAndAwaitReply("foo").toString());
40+
}
41+
}
42+
43+
@WebSocket(path = "/endpoint")
44+
public static class Endpoint {
45+
46+
@Blocking
47+
@OnTextMessage
48+
Uni<String> message(String ignored) {
49+
return Uni.createFrom().item("evenloop:" + Context.isOnEventLoopThread() + ",worker:" + Context.isOnWorkerThread());
50+
}
51+
52+
}
53+
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.quarkus.websockets.next.test.executionmodel;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.net.URI;
6+
7+
import jakarta.inject.Inject;
8+
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.extension.RegisterExtension;
11+
12+
import io.quarkus.test.QuarkusUnitTest;
13+
import io.quarkus.test.common.http.TestHTTPResource;
14+
import io.quarkus.websockets.next.OnTextMessage;
15+
import io.quarkus.websockets.next.WebSocket;
16+
import io.quarkus.websockets.next.test.utils.WSClient;
17+
import io.smallrye.common.annotation.NonBlocking;
18+
import io.vertx.core.Context;
19+
import io.vertx.core.Vertx;
20+
21+
public class NonBlockingAnnotationTest {
22+
23+
@RegisterExtension
24+
public static final QuarkusUnitTest test = new QuarkusUnitTest()
25+
.withApplicationRoot(root -> {
26+
root.addClasses(Endpoint.class, WSClient.class);
27+
});
28+
29+
@Inject
30+
Vertx vertx;
31+
32+
@TestHTTPResource("endpoint")
33+
URI endUri;
34+
35+
@Test
36+
void testEndoint() {
37+
try (WSClient client = new WSClient(vertx).connect(endUri)) {
38+
assertEquals("evenloop:true,worker:false", client.sendAndAwaitReply("foo").toString());
39+
}
40+
}
41+
42+
@WebSocket(path = "/endpoint")
43+
public static class Endpoint {
44+
45+
@NonBlocking
46+
@OnTextMessage
47+
String message(String ignored) {
48+
return "evenloop:" + Context.isOnEventLoopThread() + ",worker:" + Context.isOnWorkerThread();
49+
}
50+
51+
}
52+
53+
}

0 commit comments

Comments
 (0)