1 package com.github.jonasrutishauser.transactional.event.quarkus.deployment.it;
2
3 import static java.util.concurrent.TimeUnit.SECONDS;
4 import static org.hamcrest.Matchers.is;
5 import static org.junit.jupiter.api.Assertions.assertFalse;
6
7 import java.util.concurrent.CountDownLatch;
8
9 import org.jboss.shrinkwrap.api.asset.EmptyAsset;
10 import org.junit.jupiter.api.Test;
11 import org.junit.jupiter.api.extension.RegisterExtension;
12
13 import com.github.jonasrutishauser.transactional.event.api.EventPublisher;
14 import com.github.jonasrutishauser.transactional.event.api.handler.EventHandler;
15
16 import io.quarkus.test.QuarkusUnitTest;
17 import io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle;
18 import io.restassured.RestAssured;
19 import jakarta.enterprise.context.ApplicationScoped;
20 import jakarta.enterprise.context.Dependent;
21 import jakarta.inject.Inject;
22 import jakarta.transaction.Transactional;
23 import jakarta.ws.rs.POST;
24 import jakarta.ws.rs.Path;
25 import jakarta.ws.rs.Produces;
26 import jakarta.ws.rs.core.MediaType;
27
28 class RequestContextIsolationIT {
29
30 @RegisterExtension
31 static final QuarkusUnitTest config = new QuarkusUnitTest()
32 .setFlatClassPath(true)
33 .withApplicationRoot(archive -> archive
34 .addClasses(RequestContextProbe.class, Rendezvous.class, PublishingService.class,
35 PublishingResource.class, RendezvousHandler.class)
36 .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
37 );
38
39 @Inject
40 Rendezvous rendezvous;
41
42 @Test
43 void testPublishingRequestKeepsItsRequestContext() {
44 RestAssured.when()
45 .post("/request-context")
46 .then()
47 .assertThat()
48 .statusCode(200)
49 .body(is("request context kept"));
50
51 assertFalse(rendezvous.isRequestContextSeenByHandler());
52 }
53
54 @Path("/request-context")
55 @Dependent
56 public static class PublishingResource {
57
58 @Inject
59 private RequestContextProbe requestContextProbe;
60
61 @Inject
62 private PublishingService service;
63
64 @Inject
65 private Rendezvous rendezvous;
66
67 @POST
68 @Produces(MediaType.TEXT_PLAIN)
69 public String publish() {
70 requestContextProbe.mark();
71 service.publish();
72 try {
73 rendezvous.awaitHandlerStarted();
74 return requestContextProbe.isMarked() ? "request context kept" : "request context lost";
75 } finally {
76 rendezvous.requestChecked();
77 }
78 }
79
80 }
81
82 @Dependent
83 public static class PublishingService {
84
85 private final EventPublisher publisher;
86
87 @Inject
88 PublishingService(EventPublisher publisher) {
89 this.publisher = publisher;
90 }
91
92 @Transactional
93 public void publish() {
94 publisher.publish("rendezvous");
95 }
96
97 }
98
99 @Dependent
100 public static class RendezvousHandler {
101
102 private final RequestContextProbe requestContextProbe;
103 private final Rendezvous rendezvous;
104
105 @Inject
106 RendezvousHandler(RequestContextProbe requestContextProbe, Rendezvous rendezvous) {
107 this.requestContextProbe = requestContextProbe;
108 this.rendezvous = rendezvous;
109 }
110
111 @EventHandler
112 void handle(String event) {
113 VertxContextSafetyToggle.validateContextIfExists("processing context is marked unsafe",
114 "processing context is not marked safe");
115 rendezvous.handlerStarted(requestContextProbe.isMarked());
116 }
117
118 }
119
120 @ApplicationScoped
121 public static class Rendezvous {
122
123 private final CountDownLatch handlerStarted = new CountDownLatch(1);
124 private final CountDownLatch requestChecked = new CountDownLatch(1);
125 private volatile boolean requestContextSeenByHandler;
126
127 void handlerStarted(boolean requestContextSeen) {
128 requestContextSeenByHandler = requestContextSeen;
129 handlerStarted.countDown();
130 await(requestChecked);
131 }
132
133 void awaitHandlerStarted() {
134 await(handlerStarted);
135 }
136
137 void requestChecked() {
138 requestChecked.countDown();
139 }
140
141 boolean isRequestContextSeenByHandler() {
142 return requestContextSeenByHandler;
143 }
144
145 private static void await(CountDownLatch latch) {
146 try {
147 if (!latch.await(10, SECONDS)) {
148 throw new IllegalStateException("rendezvous timed out");
149 }
150 } catch (InterruptedException e) {
151 Thread.currentThread().interrupt();
152 throw new IllegalStateException("rendezvous interrupted", e);
153 }
154 }
155
156 }
157
158 }