Skip to content

Commit e0afb63

Browse files
authored
Merge pull request #31312 from quarkusio/#31213
Add a way for RESTEasy Reactive to close arbitrary Closeable services
2 parents a0a60d4 + 9d435b7 commit e0afb63

File tree

5 files changed

+274
-1
lines changed

5 files changed

+274
-1
lines changed

extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveCDIProcessor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public class ResteasyReactiveCDIProcessor {
3535
AutoInjectAnnotationBuildItem contextInjection(
3636
BuildProducer<AdditionalBeanBuildItem> additionalBeanBuildItemBuildProducer) {
3737
additionalBeanBuildItemBuildProducer
38-
.produce(AdditionalBeanBuildItem.builder().addBeanClasses(ContextProducers.class, QuarkusContextProducers.class)
38+
.produce(AdditionalBeanBuildItem.builder()
39+
.addBeanClasses(ContextProducers.class, QuarkusContextProducers.class)
3940
.build());
4041
return new AutoInjectAnnotationBuildItem(ResteasyReactiveServerDotNames.CONTEXT,
4142
DotName.createSimple(BeanParam.class.getName()));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package io.quarkus.resteasy.reactive.server.test;
2+
3+
import static io.restassured.RestAssured.get;
4+
import static org.hamcrest.Matchers.equalTo;
5+
6+
import java.io.Closeable;
7+
import java.io.IOException;
8+
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.atomic.AtomicInteger;
10+
import java.util.function.Supplier;
11+
12+
import jakarta.enterprise.context.RequestScoped;
13+
import jakarta.inject.Inject;
14+
import jakarta.inject.Singleton;
15+
import jakarta.ws.rs.GET;
16+
import jakarta.ws.rs.Path;
17+
import jakarta.ws.rs.core.Context;
18+
19+
import org.jboss.shrinkwrap.api.ShrinkWrap;
20+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.RegisterExtension;
23+
24+
import io.quarkus.resteasy.reactive.server.Closer;
25+
import io.quarkus.test.QuarkusUnitTest;
26+
import io.smallrye.mutiny.Uni;
27+
28+
public class CloserTest {
29+
30+
@RegisterExtension
31+
static QuarkusUnitTest test = new QuarkusUnitTest()
32+
.setArchiveProducer(new Supplier<>() {
33+
@Override
34+
public JavaArchive get() {
35+
return ShrinkWrap.create(JavaArchive.class)
36+
.addClasses(PerRequestResource.class, SingletonResource.class, CounterResource.class,
37+
Counter.class);
38+
}
39+
});
40+
41+
@Test
42+
public void test() {
43+
get("/counter/singleton")
44+
.then()
45+
.body(equalTo("0"));
46+
get("/counter/uni-singleton")
47+
.then()
48+
.body(equalTo("0"));
49+
get("/counter/per-request")
50+
.then()
51+
.body(equalTo("0"));
52+
53+
get("/singleton")
54+
.then()
55+
.statusCode(200)
56+
.body(equalTo("0"));
57+
get("/singleton")
58+
.then()
59+
.statusCode(200)
60+
.body(equalTo("1"));
61+
get("/counter/singleton")
62+
.then()
63+
.body(equalTo("2"));
64+
get("/counter/uni-singleton")
65+
.then()
66+
.body(equalTo("0"));
67+
get("/counter/per-request")
68+
.then()
69+
.body(equalTo("0"));
70+
71+
get("/uni-singleton")
72+
.then()
73+
.statusCode(200)
74+
.body(equalTo("0"));
75+
get("/uni-singleton")
76+
.then()
77+
.statusCode(200)
78+
.body(equalTo("1"));
79+
get("/counter/singleton")
80+
.then()
81+
.body(equalTo("2"));
82+
get("/counter/uni-singleton")
83+
.then()
84+
.body(equalTo("2"));
85+
get("/counter/per-request")
86+
.then()
87+
.body(equalTo("0"));
88+
89+
get("/per-request")
90+
.then()
91+
.statusCode(200)
92+
.body(equalTo("0"));
93+
get("/per-request")
94+
.then()
95+
.statusCode(200)
96+
.body(equalTo("1"));
97+
get("/counter/singleton")
98+
.then()
99+
.body(equalTo("2"));
100+
get("/counter/uni-singleton")
101+
.then()
102+
.body(equalTo("2"));
103+
get("/counter/per-request")
104+
.then()
105+
.body(equalTo("2"));
106+
}
107+
108+
@Path("per-request")
109+
@RequestScoped
110+
public static class PerRequestResource implements Closeable {
111+
private final Closer closer;
112+
private final Counter counter;
113+
114+
public PerRequestResource(Closer closer, Counter counter) {
115+
this.closer = closer;
116+
this.counter = counter;
117+
}
118+
119+
@GET
120+
public int get() {
121+
closer.add(this);
122+
return counter.perRequest.get();
123+
}
124+
125+
public void close() throws IOException {
126+
counter.perRequest.incrementAndGet();
127+
}
128+
}
129+
130+
@Path("singleton")
131+
public static class SingletonResource implements Closeable {
132+
133+
private final Counter counter;
134+
135+
public SingletonResource(Counter counter) {
136+
this.counter = counter;
137+
}
138+
139+
@GET
140+
public int get(@Context Closer closer) {
141+
closer.add(this);
142+
return counter.singleton.get();
143+
}
144+
145+
@Override
146+
public void close() {
147+
counter.singleton.incrementAndGet();
148+
}
149+
}
150+
151+
@Path("uni-singleton")
152+
public static class UniSingletonResource implements Closeable {
153+
154+
@Inject
155+
Counter counter;
156+
157+
@Inject
158+
Closer closer;
159+
160+
public UniSingletonResource(Counter counter) {
161+
this.counter = counter;
162+
}
163+
164+
@GET
165+
public Uni<Integer> get() {
166+
return Uni.createFrom().completionStage(() -> CompletableFuture.completedStage(null))
167+
.invoke(() -> closer.add(UniSingletonResource.this))
168+
.map(v -> counter.uniSingleton.get());
169+
}
170+
171+
@Override
172+
public void close() {
173+
counter.uniSingleton.incrementAndGet();
174+
}
175+
}
176+
177+
@Path("counter")
178+
public static class CounterResource {
179+
180+
private final Counter counter;
181+
182+
public CounterResource(Counter counter) {
183+
this.counter = counter;
184+
}
185+
186+
@Path("singleton")
187+
@GET
188+
public int singletonCount() {
189+
return counter.singleton.get();
190+
}
191+
192+
@Path("uni-singleton")
193+
@GET
194+
public int uniSingleton() {
195+
return counter.uniSingleton.get();
196+
}
197+
198+
@Path("per-request")
199+
@GET
200+
public int perRequestCount() {
201+
return counter.perRequest.get();
202+
}
203+
}
204+
205+
@Singleton
206+
public static class Counter {
207+
public final AtomicInteger perRequest = new AtomicInteger(0);
208+
public final AtomicInteger singleton = new AtomicInteger(0);
209+
public final AtomicInteger uniSingleton = new AtomicInteger(0);
210+
211+
}
212+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.quarkus.resteasy.reactive.server;
2+
3+
import java.io.Closeable;
4+
5+
/**
6+
* A service that allows users to close any {@link Closeable} that
7+
* when the request completes.
8+
* <p>
9+
* Meant to be used a Resource Method parameter using {@link jakarta.ws.rs.core.Context}
10+
*/
11+
public interface Closer {
12+
13+
/**
14+
* Register a new {@link Closeable} that is to be closed when the request completes.
15+
*/
16+
void add(Closeable c);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.quarkus.resteasy.reactive.server.runtime;
2+
3+
import java.io.Closeable;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
7+
import org.jboss.logging.Logger;
8+
9+
import io.quarkus.resteasy.reactive.server.Closer;
10+
11+
public class CloserImpl implements Closer {
12+
13+
private static final Logger log = Logger.getLogger(CloserImpl.class);
14+
15+
private final List<Closeable> closables = new ArrayList<>();
16+
17+
@Override
18+
public void add(Closeable c) {
19+
closables.add(c);
20+
}
21+
22+
void close() {
23+
for (Closeable closable : closables) {
24+
try {
25+
closable.close();
26+
} catch (Exception e) {
27+
log.warn("Unable to perform close operation", e);
28+
}
29+
}
30+
}
31+
}

extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusContextProducers.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import jakarta.enterprise.context.ApplicationScoped;
44
import jakarta.enterprise.context.RequestScoped;
5+
import jakarta.enterprise.inject.Disposes;
56
import jakarta.enterprise.inject.Produces;
67
import jakarta.inject.Singleton;
78
import jakarta.ws.rs.ext.Providers;
@@ -30,4 +31,15 @@ HttpServerResponse httpServerResponse() {
3031
Providers providers() {
3132
return new ProvidersImpl(ResteasyReactiveRecorder.getCurrentDeployment());
3233
}
34+
35+
@RequestScoped
36+
@Produces
37+
CloserImpl closer() {
38+
return new CloserImpl();
39+
}
40+
41+
void closeCloser(@Disposes CloserImpl closer) {
42+
closer.close();
43+
}
44+
3345
}

0 commit comments

Comments
 (0)