Skip to content

Commit 508da12

Browse files
tbredzinThomas Bredzinski
andauthored
Add support for asynchronous API for JavaJaxRsSpec (#10919)
* Add support for asynchronous API for JavaJaxRsSpec #4832 * Ran the requested command for a PR Run the following to build the project and update samples: ./mvnw clean package ./bin/generate-samples.sh ./bin/utils/export_docs_generators.sh * Set java 8 mode when using supportAsync=true `CompletionStage` are only introduced since 1.8, enabling async should for use java8 Co-authored-by: Thomas Bredzinski <thomas.bredzinski@gemalto.com>
1 parent e71ee1b commit 508da12

File tree

10 files changed

+230
-3
lines changed

10 files changed

+230
-3
lines changed

docs/generators/jaxrs-cxf-cdi.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
5858
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
5959
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
6060
|sourceFolder|source folder for generated code| |src/gen/java|
61+
|supportAsync|Wrap responses in CompletionStage type, allowing asynchronous computation (requires JAX-RS 2.1).| |false|
6162
|title|a title describing the application| |OpenAPI Server|
6263
|useBeanValidation|Use BeanValidation API annotations| |true|
6364
|useSwaggerAnnotations|Whether to generate Swagger annotations.| |true|

docs/generators/jaxrs-spec.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
5858
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
5959
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
6060
|sourceFolder|source folder for generated code| |src/main/java|
61+
|supportAsync|Wrap responses in CompletionStage type, allowing asynchronous computation (requires JAX-RS 2.1).| |false|
6162
|title|a title describing the application| |OpenAPI Server|
6263
|useBeanValidation|Use BeanValidation API annotations| |true|
6364
|useSwaggerAnnotations|Whether to generate Swagger annotations.| |true|

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ public JavaJAXRSSpecServerCodegen() {
102102
cliOptions.add(CliOption.newBoolean(RETURN_RESPONSE, "Whether generate API interface should return javax.ws.rs.core.Response instead of a deserialized entity. Only useful if interfaceOnly is true.").defaultValue(String.valueOf(returnResponse)));
103103
cliOptions.add(CliOption.newBoolean(USE_SWAGGER_ANNOTATIONS, "Whether to generate Swagger annotations.", useSwaggerAnnotations));
104104
cliOptions.add(CliOption.newString(OPEN_API_SPEC_FILE_LOCATION, "Location where the file containing the spec will be generated in the output folder. No file generated when set to null or empty string."));
105+
cliOptions.add(CliOption.newBoolean(SUPPORT_ASYNC, "Wrap responses in CompletionStage type, allowing asynchronous computation (requires JAX-RS 2.1).", supportAsync));
105106
}
106107

107108
@Override
@@ -121,6 +122,14 @@ public void processOpts() {
121122
additionalProperties.remove(RETURN_RESPONSE);
122123
}
123124
}
125+
if (additionalProperties.containsKey(SUPPORT_ASYNC)) {
126+
supportAsync = Boolean.parseBoolean(additionalProperties.get(SUPPORT_ASYNC).toString());
127+
if (!supportAsync) {
128+
additionalProperties.remove(SUPPORT_ASYNC);
129+
} else {
130+
setJava8ModeAndAdditionalProperties(true);
131+
}
132+
}
124133
if (QUARKUS_LIBRARY.equals(library) || THORNTAIL_LIBRARY.equals(library) || HELIDON_LIBRARY.equals(library) || OPEN_LIBERTY_LIBRARY.equals(library) || KUMULUZEE_LIBRARY.equals(library)) {
125134
useSwaggerAnnotations = false;
126135
} else {

modules/openapi-generator/src/main/resources/JavaJaxRS/spec/api.mustache

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import javax.ws.rs.core.Response;
99
{{#useSwaggerAnnotations}}
1010
import io.swagger.annotations.*;
1111
{{/useSwaggerAnnotations}}
12+
{{#supportAsync}}
13+
import java.util.concurrent.CompletionStage;
14+
import java.util.concurrent.CompletableFuture;
15+
{{/supportAsync}}
1216

1317
import java.io.InputStream;
1418
import java.util.Map;

modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiInterface.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
{{/isOAuth}}{{/authMethods}} }{{/hasAuthMethods}}, tags={ {{#vendorExtensions.x-tags}}"{{tag}}"{{^-last}}, {{/-last}}{{/vendorExtensions.x-tags}} })
1111
@ApiResponses(value = { {{#responses}}
1212
@ApiResponse(code = {{{code}}}, message = "{{{message}}}", response = {{{baseType}}}.class{{#returnContainer}}, responseContainer = "{{{.}}}"{{/returnContainer}}){{^-last}},{{/-last}}{{/responses}} }){{/useSwaggerAnnotations}}
13-
{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{>returnTypeInterface}}{{/returnResponse}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}});
13+
{{#supportAsync}}{{>returnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{>returnTypeInterface}}{{/returnResponse}}{{/supportAsync}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}});

modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiMethod.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
@ApiResponses(value = { {{#responses}}
1212
@ApiResponse(code = {{{code}}}, message = "{{{message}}}", response = {{{baseType}}}.class{{#containerType}}, responseContainer = "{{{.}}}"{{/containerType}}){{^-last}},{{/-last}}{{/responses}}
1313
}){{/useSwaggerAnnotations}}
14-
public Response {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}) {
15-
return Response.ok().entity("magic!").build();
14+
public {{#supportAsync}}CompletionStage<{{/supportAsync}}Response{{#supportAsync}}>{{/supportAsync}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}) {
15+
return {{#supportAsync}}CompletableFuture.supplyAsync(() -> {{/supportAsync}}Response.ok().entity("magic!").build(){{#supportAsync}}){{/supportAsync}};
1616
}

modules/openapi-generator/src/main/resources/JavaJaxRS/spec/pom.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@
140140
{{/useBeanValidation}}
141141
</dependencies>
142142
<properties>
143+
{{#java8}}
144+
<java.version>1.8</java.version>
145+
<maven.compiler.source>${java.version}</maven.compiler.source>
146+
<maven.compiler.target>${java.version}</maven.compiler.target>
147+
{{/java8}}
143148
<jackson-version>2.9.9</jackson-version>
144149
<junit-version>4.13.1</junit-version>
145150
{{#useBeanValidation}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CompletionStage<{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{#returnContainer}}{{#isMap}}Map<String, {{{returnBaseType}}}>{{/isMap}}{{#isArray}}{{{returnContainer}}}<{{{returnBaseType}}}>{{/isArray}}{{/returnContainer}}{{^returnContainer}}{{{returnBaseType}}}{{/returnContainer}}{{/returnResponse}}>

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828

2929
import static org.openapitools.codegen.TestUtils.assertFileContains;
3030
import static org.openapitools.codegen.TestUtils.validateJavaSourceFiles;
31+
import static org.openapitools.codegen.languages.AbstractJavaCodegen.JAVA8_MODE;
32+
import static org.openapitools.codegen.languages.AbstractJavaJAXRSServerCodegen.USE_TAGS;
33+
import static org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen.INTERFACE_ONLY;
34+
import static org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen.SUPPORT_ASYNC;
35+
import static org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen.RETURN_RESPONSE;
3136
import static org.testng.Assert.assertTrue;
3237

3338
/**
@@ -95,6 +100,8 @@ public void testAdditionalPropertiesPutForConfigValues() throws Exception {
95100
codegen.additionalProperties().put(CodegenConstants.INVOKER_PACKAGE, "xyz.yyyyy.iiii.invoker");
96101
codegen.additionalProperties().put("serverPort", "8088");
97102
codegen.additionalProperties().put(JavaJAXRSSpecServerCodegen.OPEN_API_SPEC_FILE_LOCATION, "openapi.yml");
103+
codegen.additionalProperties().put(SUPPORT_ASYNC, true);
104+
codegen.additionalProperties().put(JAVA8_MODE, false);
98105
codegen.processOpts();
99106

100107
OpenAPI openAPI = new OpenAPI();
@@ -112,6 +119,8 @@ public void testAdditionalPropertiesPutForConfigValues() throws Exception {
112119
Assert.assertEquals(codegen.additionalProperties().get(AbstractJavaJAXRSServerCodegen.SERVER_PORT), "8088");
113120
Assert.assertEquals(codegen.getOpenApiSpecFileLocation(), "openapi.yml");
114121
Assert.assertEquals(codegen.additionalProperties().get(JavaJAXRSSpecServerCodegen.OPEN_API_SPEC_FILE_LOCATION), "openapi.yml");
122+
Assert.assertEquals(codegen.additionalProperties().get(SUPPORT_ASYNC), "true");
123+
Assert.assertEquals(codegen.additionalProperties().get(JAVA8_MODE), true); //overridden by supportAsync=true
115124
}
116125

117126
/**
@@ -419,4 +428,170 @@ public void addsImportForSetResponse() throws IOException {
419428

420429
assertFileContains(path, "\nimport java.util.Set;\n");
421430
}
431+
432+
@Test
433+
public void generateApiWithAsyncSupport() throws Exception {
434+
final File output = Files.createTempDirectory("test").toFile();
435+
output.deleteOnExit();
436+
437+
final OpenAPI openAPI = new OpenAPIParser()
438+
.readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();
439+
440+
codegen.setOutputDir(output.getAbsolutePath());
441+
codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled
442+
443+
final ClientOptInput input = new ClientOptInput()
444+
.openAPI(openAPI)
445+
.config(codegen); //Using JavaJAXRSSpecServerCodegen
446+
447+
final DefaultGenerator generator = new DefaultGenerator();
448+
final List<File> files = generator.opts(input).generate(); //When generating files
449+
450+
//Then the java files are compilable
451+
validateJavaSourceFiles(files);
452+
453+
//And the generated class contains CompletionStage<Response>
454+
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java");
455+
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"),
456+
"\nimport java.util.concurrent.CompletionStage;\n",
457+
"\nimport java.util.concurrent.CompletableFuture;\n",
458+
"\npublic CompletionStage<Response> pingGet() {\n",
459+
"\nCompletableFuture.supplyAsync(() -> Response.ok().entity(\"magic!\").build())\n"
460+
);
461+
}
462+
463+
@Test
464+
public void generateApiWithAsyncSupportAndInterfaceOnly() throws Exception {
465+
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
466+
output.deleteOnExit();
467+
468+
final OpenAPI openAPI = new OpenAPIParser()
469+
.readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();
470+
471+
codegen.setOutputDir(output.getAbsolutePath());
472+
codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled
473+
codegen.additionalProperties().put(INTERFACE_ONLY, true); //And only interfaces are generated
474+
475+
final ClientOptInput input = new ClientOptInput()
476+
.openAPI(openAPI)
477+
.config(codegen); //Using JavaJAXRSSpecServerCodegen
478+
479+
final DefaultGenerator generator = new DefaultGenerator();
480+
final List<File> files = generator.opts(input).generate(); //When generating files
481+
482+
//Then the java files are compilable
483+
validateJavaSourceFiles(files);
484+
485+
//And the generated interface contains CompletionStage<Void>
486+
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java");
487+
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"),
488+
"\nimport java.util.concurrent.CompletionStage;\n",
489+
"\nCompletionStage<Void> pingGet();\n");
490+
}
491+
492+
@Test
493+
public void generateApiWithAsyncSupportAndInterfaceOnlyAndResponse() throws Exception {
494+
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
495+
output.deleteOnExit();
496+
497+
final OpenAPI openAPI = new OpenAPIParser()
498+
.readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();
499+
500+
codegen.setOutputDir(output.getAbsolutePath());
501+
codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled
502+
codegen.additionalProperties().put(INTERFACE_ONLY, true); //And only interfaces are generated
503+
codegen.additionalProperties().put(RETURN_RESPONSE, true); //And return type is Response
504+
505+
final ClientOptInput input = new ClientOptInput()
506+
.openAPI(openAPI)
507+
.config(codegen); //Using JavaJAXRSSpecServerCodegen
508+
509+
final DefaultGenerator generator = new DefaultGenerator();
510+
final List<File> files = generator.opts(input).generate(); //When generating files
511+
512+
//Then the java files are compilable
513+
validateJavaSourceFiles(files);
514+
515+
//And the generated interface contains CompletionStage<Response>
516+
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java");
517+
assertFileContains(output.toPath().resolve( "src/gen/java/org/openapitools/api/PingApi.java"),
518+
"\nimport java.util.concurrent.CompletionStage;\n",
519+
"\nCompletionStage<Response> pingGet();\n");
520+
}
521+
522+
523+
@Test
524+
public void generatePetstoreAPIWithAsyncSupport() throws Exception {
525+
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
526+
output.deleteOnExit();
527+
528+
final OpenAPI openAPI = new OpenAPIParser()
529+
.readLocation("src/test/resources/3_0/petstore.yaml", null, new ParseOptions()).getOpenAPI();
530+
531+
codegen.setOutputDir(output.getAbsolutePath());
532+
codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled
533+
codegen.additionalProperties().put(INTERFACE_ONLY, true); //And only interfaces are generated
534+
535+
final ClientOptInput input = new ClientOptInput()
536+
.openAPI(openAPI)
537+
.config(codegen); //using JavaJAXRSSpecServerCodegen
538+
539+
final DefaultGenerator generator = new DefaultGenerator();
540+
final List<File> files = generator.opts(input).generate(); //When generating files
541+
542+
//Then the java files are compilable
543+
validateJavaSourceFiles(files);
544+
545+
//And the generated interfaces contains CompletionStage
546+
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PetApi.java");
547+
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PetApi.java"),
548+
"\nimport java.util.concurrent.CompletionStage;\n",
549+
"CompletionStage<Void> deletePet", //Support empty response
550+
"CompletionStage<List<Pet>> findPetsByStatus", //Support type of arrays response
551+
"CompletionStage<Pet> getPetById" //Support single type response
552+
);
553+
554+
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/StoreApi.java");
555+
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/StoreApi.java"),
556+
"\nimport java.util.concurrent.CompletionStage;\n",
557+
"CompletionStage<Map<String, Integer>>" //Support map response
558+
);
559+
560+
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/UserApi.java");
561+
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/UserApi.java"),
562+
"\nimport java.util.concurrent.CompletionStage;\n",
563+
"CompletionStage<String>" //Support simple types
564+
);
565+
}
566+
567+
@Test
568+
public void generatePingWithAsyncSupportPrimitiveType() throws Exception {
569+
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
570+
output.deleteOnExit();
571+
572+
final OpenAPI openAPI = new OpenAPIParser()
573+
.readLocation("src/test/resources/3_0/issue_4832.yaml", null, new ParseOptions()).getOpenAPI();
574+
575+
codegen.setOutputDir(output.getAbsolutePath());
576+
codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled
577+
codegen.additionalProperties().put(INTERFACE_ONLY, true); //And only interfaces are generated
578+
codegen.additionalProperties().put(USE_TAGS, true); //And use tags to generate everything in PingApi.java
579+
580+
final ClientOptInput input = new ClientOptInput()
581+
.openAPI(openAPI)
582+
.config(codegen); //using JavaJAXRSSpecServerCodegen
583+
584+
final DefaultGenerator generator = new DefaultGenerator();
585+
final List<File> files = generator.opts(input).generate(); //When generating files
586+
587+
//Then the java files are compilable
588+
validateJavaSourceFiles(files);
589+
590+
//And the generated interfaces contains CompletionStage with proper classes instead of primitive types
591+
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java");
592+
TestUtils.assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"),
593+
"CompletionStage<Boolean> pingGetBoolean", //Support primitive types response
594+
"CompletionStage<Integer> pingGetInteger" //Support primitive types response
595+
);
596+
}
422597
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
openapi: 3.0.1
2+
info:
3+
title: ping that return primitive types
4+
version: '1.0'
5+
servers:
6+
- url: 'http://localhost:8082/'
7+
paths:
8+
/pingBoolean:
9+
get:
10+
operationId: pingGetBoolean
11+
tags: [Ping]
12+
responses:
13+
'200':
14+
description: OK
15+
content:
16+
'application/json':
17+
schema:
18+
type: boolean
19+
/pingInteger:
20+
get:
21+
operationId: pingGetInteger
22+
tags: [Ping]
23+
responses:
24+
'200':
25+
description: OK
26+
content:
27+
'application/json':
28+
schema:
29+
type: integer
30+
format: int32
31+

0 commit comments

Comments
 (0)