Skip to content

Commit a388cea

Browse files
committed
Merge branch 'LoonyRules-master'
2 parents 7c5277f + e4e2c13 commit a388cea

File tree

7 files changed

+130
-7
lines changed

7 files changed

+130
-7
lines changed

http-generator-core/src/main/java/io/avaje/http/generator/core/JsonBUtil.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ private static void addJsonBodyType(MethodReader methodReader, Consumer<UType> a
5050
}
5151

5252
public static void writeJsonbType(UType type, Append writer) {
53+
// Support for CompletableFuture's.
54+
if (type.mainType().equals("java.util.concurrent.CompletableFuture")) {
55+
writeJsonbType(type.paramRaw(), writer);
56+
return;
57+
}
58+
5359
writer.append(" this.%sJsonType = jsonB.type(", type.shortName());
5460
if (!type.isGeneric()) {
5561
writer.append("%s.class)", Util.shortName(PrimitiveUtil.wrap(type.full())));

http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/ControllerMethodWriter.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,47 @@ void write(boolean requestScoped) {
9292
}
9393

9494
private void writeContextReturn() {
95+
// Support for CompletableFuture's.
96+
final UType type = UType.parse(method.returnType());
97+
if (type.mainType().equals("java.util.concurrent.CompletableFuture")) {
98+
if (!type.isGeneric()) {
99+
throw new IllegalStateException("CompletableFuture must be generic type (e.g. CompletableFuture<String>, CompletableFuture<Void>).");
100+
}
101+
102+
final String futureResultVariableName = "futureResult";
103+
104+
writer.append(" ctx.future(() -> {").eol();
105+
writer.append(" return result.thenAccept(%s -> {", futureResultVariableName).eol();
106+
writer.append(" ");
107+
this.writeContextReturn(futureResultVariableName);
108+
writer.eol().append(" });").eol();
109+
writer.append(" });").eol();
110+
return;
111+
}
112+
113+
// Everything else
114+
this.writeContextReturn("result");
115+
}
116+
117+
private void writeContextReturn(final String resultVariableName) {
95118
final var produces = method.produces();
96119
if (produces == null || MediaType.APPLICATION_JSON.getValue().equalsIgnoreCase(produces)) {
97120
if (useJsonB) {
98-
final var uType = UType.parse(method.returnType());
99-
writer.append(" %sJsonType.toJson(result, ctx.contentType(\"application/json\").outputStream());", uType.shortName());
121+
var uType = UType.parse(method.returnType());
122+
if (uType.mainType().equals("java.util.concurrent.CompletableFuture")) {
123+
uType = uType.paramRaw();
124+
}
125+
126+
writer.append(" %sJsonType.toJson(%s, ctx.contentType(\"application/json\").outputStream());", uType.shortName(), resultVariableName);
100127
} else {
101-
writer.append(" ctx.json(result);");
128+
writer.append(" ctx.json(%s);", resultVariableName);
102129
}
103130
} else if (MediaType.TEXT_HTML.getValue().equalsIgnoreCase(produces)) {
104-
writer.append(" ctx.html(result);");
131+
writer.append(" ctx.html(%s);", resultVariableName);
105132
} else if (MediaType.TEXT_PLAIN.getValue().equalsIgnoreCase(produces)) {
106-
writer.append(" ctx.contentType(\"text/plain\").result(result);");
133+
writer.append(" ctx.contentType(\"text/plain\").result(%s);", resultVariableName);
107134
} else {
108-
writer.append(" ctx.contentType(\"%s\").result(result);", produces);
135+
writer.append(" ctx.contentType(\"%s\").result(%s);", produces, resultVariableName);
109136
}
110137
}
111138
}

http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/ControllerWriter.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,18 @@ private void writeClassStart() {
8787
writer.append(" private final Validator validator;").eol();
8888
}
8989

90-
for (final UType type : jsonTypes.values()) {
90+
for (UType type : jsonTypes.values()) {
91+
// Support for CompletableFuture's.
92+
if (type.mainType().equals("java.util.concurrent.CompletableFuture")) {
93+
type = type.paramRaw();
94+
95+
if (this.jsonTypes.containsKey(type.full())) {
96+
// Already written before -- we can skip.
97+
continue;
98+
}
99+
}
100+
101+
// Everything else
91102
final var typeString = PrimitiveUtil.wrap(type.shortType()).replace(",", ", ");
92103
writer.append(" private final JsonType<%s> %sJsonType;", typeString, type.shortName()).eol();
93104
}
@@ -107,6 +118,12 @@ private void writeClassStart() {
107118
}
108119
if (useJsonB) {
109120
for (final UType type : jsonTypes.values()) {
121+
// Skip trying to assign a global variable value for any UType that is a Completable Future. Because the paramRaw() should
122+
// already be in this jsonTypes map anyway and write the assignment all by itself.
123+
if (type.mainType().equals("java.util.concurrent.CompletableFuture")) {
124+
continue;
125+
}
126+
110127
JsonBUtil.writeJsonbType(type, writer);
111128
}
112129
}

tests/test-javalin-jsonb/src/main/java/org/example/myapp/web/HelloController.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import java.time.LocalDate;
66
import java.util.ArrayList;
77
import java.util.List;
8+
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.Executors;
810

911
import javax.validation.Valid;
1012

@@ -142,6 +144,21 @@ List<HelloDto> getAll() {
142144
return myService.findAll();
143145
}
144146

147+
@Get("/async")
148+
CompletableFuture<List<HelloDto>> getAllAsync() {
149+
return CompletableFuture.supplyAsync(() -> {
150+
// Simulate a delay as if an actual IO operation is being executed.
151+
// This also helps ensure that we aren't just getting lucky with timings.
152+
try {
153+
Thread.sleep(10L);
154+
} catch (InterruptedException e) {
155+
throw new RuntimeException(e);
156+
}
157+
158+
return myService.findAll();
159+
}, Executors.newSingleThreadExecutor()); // Example of how to use a custom executor.
160+
}
161+
145162
// @Hidden
146163
@Delete(":id")
147164
void deleteById(int id) {

tests/test-javalin-jsonb/src/test/java/org/example/myapp/HelloControllerTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ void hello2() {
6262
assertThat(helloDtos).hasSize(2);
6363
}
6464

65+
@Test
66+
void helloAsyncRequestHandling() {
67+
TypeRef<List<HelloDto>> listDto = new TypeRef<List<HelloDto>>() { };
68+
final List<HelloDto> beans = given()
69+
.get(baseUrl + "/hello/async")
70+
.then()
71+
.statusCode(200)
72+
.extract()
73+
.as(listDto);
74+
75+
assertThat(beans).hasSize(2);
76+
77+
final List<HelloDto> helloDtos = client.request()
78+
.path("hello/async")
79+
.GET().list(HelloDto.class);
80+
81+
assertThat(helloDtos).hasSize(2);
82+
}
83+
6584
@Test
6685
void getWithPathParamAndQueryParam() {
6786

tests/test-javalin/src/main/java/org/example/myapp/web/HelloController.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import java.time.LocalDate;
66
import java.util.ArrayList;
77
import java.util.List;
8+
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.Executors;
810

911
import javax.validation.Valid;
1012

@@ -142,6 +144,21 @@ List<HelloDto> getAll() {
142144
return myService.findAll();
143145
}
144146

147+
@Get("/async")
148+
CompletableFuture<List<HelloDto>> getAllAsync() {
149+
return CompletableFuture.supplyAsync(() -> {
150+
// Simulate a delay as if an actual IO operation is being executed.
151+
// This also helps ensure that we aren't just getting lucky with timings.
152+
try {
153+
Thread.sleep(10L);
154+
} catch (InterruptedException e) {
155+
throw new RuntimeException(e);
156+
}
157+
158+
return myService.findAll();
159+
}, Executors.newSingleThreadExecutor()); // Example of how to use a custom executor.
160+
}
161+
145162
// @Hidden
146163
@Delete("{id}")
147164
void deleteById(int id) {
@@ -159,4 +176,5 @@ String getWithMatrixParam(int year, String author, String country, String other,
159176
String slashAccepting(String name, String nam0, String nam1) {
160177
return "got name:" + name + " splat0:" + nam0 + " splat1:" + nam1;
161178
}
179+
162180
}

tests/test-javalin/src/test/java/org/example/myapp/HelloControllerTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ void hello2() {
6262
assertThat(helloDtos).hasSize(2);
6363
}
6464

65+
@Test
66+
void helloAsyncRequestHandling() {
67+
TypeRef<List<HelloDto>> listDto = new TypeRef<List<HelloDto>>() { };
68+
final List<HelloDto> beans = given()
69+
.get(baseUrl + "/hello/async")
70+
.then()
71+
.statusCode(200)
72+
.extract()
73+
.as(listDto);
74+
75+
assertThat(beans).hasSize(2);
76+
77+
final List<HelloDto> helloDtos = client.request()
78+
.path("hello/async")
79+
.GET().list(HelloDto.class);
80+
81+
assertThat(helloDtos).hasSize(2);
82+
}
83+
6584
@Test
6685
void getWithPathParamAndQueryParam() {
6786

0 commit comments

Comments
 (0)