Skip to content

Commit 61be57e

Browse files
authored
Add JsonB stream support (#621)
* Add JsonB stream support This is initially using JSON Array content rather than application/x-json-stream * JsonB stream support - use streamAsLines() and application/stream+json * [Jex] JsonB stream support for Jex using application/stream+json * Extract defaultMediaType() helper method * Bump to jsonb 3.6-RC2
1 parent d6ba156 commit 61be57e

File tree

20 files changed

+231
-40
lines changed

20 files changed

+231
-40
lines changed

aws-cognito/http-client-authtoken/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@
2323
<dependency>
2424
<groupId>io.avaje</groupId>
2525
<artifactId>avaje-json-core</artifactId>
26-
<version>3.5</version>
26+
<version>3.6-RC2</version>
2727
</dependency>
2828

2929
<!-- test dependencies -->
3030
<dependency>
3131
<groupId>io.avaje</groupId>
3232
<artifactId>avaje-json-node</artifactId>
33-
<version>3.5</version>
33+
<version>3.6-RC2</version>
3434
<scope>test</scope>
3535
</dependency>
3636

http-client/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@
3737
<dependency>
3838
<groupId>io.avaje</groupId>
3939
<artifactId>avaje-jsonb</artifactId>
40-
<version>3.5</version>
40+
<version>3.6-RC2</version>
4141
<optional>true</optional>
4242
</dependency>
4343

4444
<dependency>
4545
<groupId>io.avaje</groupId>
4646
<artifactId>avaje-json-node</artifactId>
47-
<version>3.5</version>
47+
<version>3.6-RC2</version>
4848
<scope>test</scope>
4949
</dependency>
5050

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,13 @@ public static void writeJsonbType(UType type, Append writer) {
109109
writeType(type.paramRaw(), writer);
110110
writer.append(".map()");
111111
break;
112+
case "java.util.stream.Stream":
113+
writeType(type.paramRaw(), writer);
114+
writer.append(".streamAsLines()");
115+
break;
112116
default: {
113117
if (type.mainType().contains("java.util")) {
114-
throw new UnsupportedOperationException("Only java.util Map, Set and List are supported JsonB Controller Collection Types");
118+
throw new UnsupportedOperationException("Only java.util Map, Set, List and Stream are supported JsonB Controller Collection Types");
115119
}
116120
writeType(type, writer);
117121
}

http-generator-core/src/main/java/io/avaje/http/generator/core/openapi/MediaType.java

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

33
public enum MediaType {
44
APPLICATION_JSON("application/json"),
5+
APPLICATION_STREAM_JSON("application/stream+json"),
56
TEXT_PLAIN("text/plain"),
67
TEXT_HTML("text/html"),
78
HTML_UTF8("text/html;charset=UTF8"),

http-generator-helidon/src/main/java/io/avaje/http/generator/helidon/nima/ControllerMethodWriter.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -258,15 +258,14 @@ void writeHandler(boolean requestScoped) {
258258
writer.append(indent).append("res.send(content);").eol();
259259

260260
} else {
261-
writeContextReturn(indent);
261+
final var uType = UType.parse(method.returnType());
262+
writeContextReturn(indent, streamingResponse(uType));
262263
if (responseMode == ResponseMode.InputStream) {
263-
final var uType = UType.parse(method.returnType());
264264
writer.append(indent).append("result.transferTo(res.outputStream());", uType.shortName()).eol();
265265
} else if (responseMode == ResponseMode.Json) {
266266
if (returnTypeString()) {
267267
writer.append(indent).append("res.send(result); // send raw JSON").eol();
268268
} else {
269-
final var uType = UType.parse(method.returnType());
270269
writer.append(indent).append("%sJsonType.toJson(result, JsonOutput.of(res));", uType.shortName()).eol();
271270
}
272271
} else {
@@ -280,6 +279,10 @@ void writeHandler(boolean requestScoped) {
280279
writer.append(" }").eol().eol();
281280
}
282281

282+
private static boolean streamingResponse(UType uType) {
283+
return uType.mainType().equals("java.util.stream.Stream");
284+
}
285+
283286
enum ResponseMode {
284287
Void,
285288
Json,
@@ -381,25 +384,35 @@ private boolean usesQueryParams() {
381384
}
382385

383386
private void writeContextReturn(String indent) {
387+
writeContextReturn(indent, false);
388+
}
389+
390+
private void writeContextReturn(String indent, boolean streaming) {
384391
final var producesOp = Optional.ofNullable(method.produces());
385392
if (producesOp.isEmpty() && !useJsonB && !useJstachio) {
386393
return;
387394
}
388-
final var produces =
389-
producesOp
390-
.map(MediaType::parse)
391-
.orElse(useJstachio ? MediaType.HTML_UTF8 : MediaType.APPLICATION_JSON);
395+
final var produces = producesOp
396+
.map(MediaType::parse)
397+
.orElse(defaultMediaType(streaming));
392398
final var contentTypeString = "res.headers().contentType(MediaTypes.";
393399
writer.append(indent);
394400
switch (produces) {
395401
case HTML_UTF8 -> writer.append("res.headers().contentType(HTML_UTF8);").eol();
396402
case APPLICATION_JSON -> writer.append(contentTypeString).append("APPLICATION_JSON);").eol();
403+
case APPLICATION_STREAM_JSON -> writer.append(contentTypeString).append("APPLICATION_STREAM_JSON);").eol();
397404
case TEXT_HTML -> writer.append(contentTypeString).append("TEXT_HTML);").eol();
398405
case TEXT_PLAIN -> writer.append(contentTypeString).append("TEXT_PLAIN);").eol();
399406
case UNKNOWN -> writer.append(contentTypeString + "create(\"%s\"));", producesOp.orElse("UNKNOWN")).eol();
400407
}
401408
}
402409

410+
private MediaType defaultMediaType(boolean streaming) {
411+
return useJstachio
412+
? MediaType.HTML_UTF8
413+
: streaming ? MediaType.APPLICATION_STREAM_JSON : MediaType.APPLICATION_JSON;
414+
}
415+
403416
private String lookupStatusCode(int statusCode) {
404417
return statusMap.getOrDefault(statusCode, String.valueOf(statusCode));
405418
}

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

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ private void write(boolean requestScoped) {
208208
writer.append(");").eol();
209209
writer.append(" var cacheContent = contentCache.content(key);").eol();
210210
writer.append(" if (cacheContent != null) {").eol();
211-
writeContextReturn(responseMode, "cacheContent");
211+
writeContextReturn(responseMode, "cacheContent", "");
212212
writer.append(" return;").eol();
213213
writer.append(" }").eol();
214214
}
@@ -258,18 +258,15 @@ private void write(boolean requestScoped) {
258258
if (withContentCache) {
259259
writer.append(indent).append("contentCache.contentPut(key, content);").eol();
260260
}
261-
writer.append(indent);
262-
writeContextReturn(responseMode, "content");
261+
writeContextReturn(responseMode, "content", indent);
263262
}
264263
case Jstachio -> {
265264
var renderer = ProcessingContext.jstacheRenderer(method.returnType());
266265
writer.append(indent).append("var content = %s(result);", renderer).eol();
267-
writer.append(indent);
268-
writeContextReturn(responseMode, "content");
266+
writeContextReturn(responseMode, "content", indent);
269267
}
270268
default -> {
271-
writer.append(indent);
272-
writeContextReturn(responseMode, "result");
269+
writeContextReturn(responseMode, "result", indent);
273270
}
274271
}
275272
if (includeNoContent) {
@@ -278,7 +275,8 @@ private void write(boolean requestScoped) {
278275
}
279276
}
280277

281-
private void writeContextReturn(ResponseMode responseMode, String resultVariable) {
278+
private void writeContextReturn(ResponseMode responseMode, String resultVariable, String indent) {
279+
writer.append(indent);
282280
final UType type = UType.parse(method.returnType());
283281
if ("java.util.concurrent.CompletableFuture".equals(type.mainType())) {
284282
logError(method.element(), "CompletableFuture is not a supported return type.");
@@ -293,30 +291,42 @@ private void writeContextReturn(ResponseMode responseMode, String resultVariable
293291
}
294292
switch (responseMode) {
295293
case Void -> {}
296-
case Json -> writeJsonReturn(produces);
294+
case Json -> writeJsonReturn(produces, indent);
297295
case Text -> writer.append("ctx.text(%s);", resultVariable);
298296
case Templating -> writer.append("ctx.html(%s);", resultVariable);
299297
default -> writer.append("ctx.contentType(\"%s\").write(%s);", produces, resultVariable);
300298
}
301299
writer.eol();
302300
}
303301

304-
private void writeJsonReturn(String produces) {
302+
private void writeJsonReturn(String produces, String indent) {
303+
var uType = UType.parse(method.returnType());
304+
boolean streaming = useJsonB && streamingContent(uType);
305305
if (produces == null) {
306-
produces = MediaType.APPLICATION_JSON.getValue();
306+
produces = streaming
307+
? MediaType.APPLICATION_STREAM_JSON.getValue()
308+
: MediaType.APPLICATION_JSON.getValue();
307309
}
308-
var uType = UType.parse(method.returnType());
309310
if ("java.lang.String".equals(method.returnType().toString())) {
310311
writer.append("ctx.contentType(\"%s\").write(result); // raw json", produces);
311312
return;
312313
}
313314
if (useJsonB) {
314-
writer.append("ctx.jsonb(%sJsonType, result);", uType.shortName());
315+
if (streaming) {
316+
writer.append("ctx.contentType(\"%s\");", produces).eol();
317+
writer.append(indent).append("%sJsonType.toJson(result, io.avaje.jex.core.json.JsonbOutput.of(ctx));", uType.shortName());
318+
} else {
319+
writer.append("ctx.jsonb(%sJsonType, result);", uType.shortName());
320+
}
315321
} else {
316322
writer.append("ctx.json(result);");
317323
}
318324
}
319325

326+
private static boolean streamingContent(UType uType) {
327+
return uType.mainType().equals("java.util.stream.Stream");
328+
}
329+
320330
private static boolean isExceptionOrFilterChain(MethodParam param) {
321331
return isAssignable2Interface(param.utype().mainType(), "java.lang.Exception")
322332
|| "HttpFilter.FilterChain".equals(param.shortType());

http-generator-jex/src/main/java/io/avaje/http/generator/jex/JexAdapter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ public String bodyAsClass(UType type) {
4949

5050
public static String writeJsonbType(UType type) {
5151
var writer = new StringBuilder();
52-
5352
switch (type.mainType()) {
5453
case "java.util.List":
5554
writeType(type.paramRaw(), writer);
@@ -63,10 +62,14 @@ public static String writeJsonbType(UType type) {
6362
writeType(type.paramRaw(), writer);
6463
writer.append(".map()");
6564
break;
65+
case "java.util.stream.Stream":
66+
writeType(type.paramRaw(), writer);
67+
writer.append(".stream()");
68+
break;
6669
default: {
6770
if (type.mainType().contains("java.util")) {
6871
throw new UnsupportedOperationException(
69-
"Only java.util Map, Set and List are supported JsonB Controller Collection Types");
72+
"Only java.util Map, Set, List and Stream are supported JsonB Controller Collection Types");
7073
}
7174
writeType(type, writer);
7275
}

tests/test-javalin-jsonb/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@
7474
<dependency>
7575
<groupId>io.avaje</groupId>
7676
<artifactId>avaje-jsonb</artifactId>
77-
<version>3.5</version>
77+
<version>3.6-RC2</version>
7878
</dependency>
7979

8080
<dependency>
8181
<groupId>io.avaje</groupId>
8282
<artifactId>avaje-jsonb-generator</artifactId>
83-
<version>3.5</version>
83+
<version>3.6-RC2</version>
8484
<scope>provided</scope>
8585
</dependency>
8686

tests/test-jex/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
<dependency>
7575
<groupId>io.avaje</groupId>
7676
<artifactId>avaje-jsonb</artifactId>
77-
<version>3.5</version>
77+
<version>3.6-RC2</version>
7878
</dependency>
7979

8080
<!-- java annotation processors -->
@@ -140,7 +140,7 @@
140140
<path>
141141
<groupId>io.avaje</groupId>
142142
<artifactId>avaje-jsonb-generator</artifactId>
143-
<version>3.5</version>
143+
<version>3.6-RC2</version>
144144
</path>
145145
<path>
146146
<groupId>io.avaje</groupId>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.example.web;
22

33
import java.math.BigInteger;
4+
import java.util.stream.Stream;
45

56
import io.avaje.http.api.Controller;
67
import io.avaje.http.api.Default;
@@ -16,6 +17,11 @@
1617
@Path("/")
1718
public class HelloController {
1819

20+
@Get("stream")
21+
Stream<HelloDto> stream() {
22+
return Stream.of(new HelloDto(1,"a"), new HelloDto(2, "b"));
23+
}
24+
1925
@Get
2026
HelloDto getHello() {
2127
HelloDto dto = new HelloDto();

0 commit comments

Comments
 (0)