diff --git a/docs/generators/jaxrs-cxf-cdi.md b/docs/generators/jaxrs-cxf-cdi.md index 621f0dc3a73..c46ec4c9631 100644 --- a/docs/generators/jaxrs-cxf-cdi.md +++ b/docs/generators/jaxrs-cxf-cdi.md @@ -58,6 +58,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |sourceFolder|source folder for generated code| |src/gen/java| +|supportAsync|Wrap responses in CompletionStage type, allowing asynchronous computation (requires JAX-RS 2.1).| |false| |title|a title describing the application| |OpenAPI Server| |useBeanValidation|Use BeanValidation API annotations| |true| |useSwaggerAnnotations|Whether to generate Swagger annotations.| |true| diff --git a/docs/generators/jaxrs-spec.md b/docs/generators/jaxrs-spec.md index 621122f6eaa..a29e8a93178 100644 --- a/docs/generators/jaxrs-spec.md +++ b/docs/generators/jaxrs-spec.md @@ -58,6 +58,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |sourceFolder|source folder for generated code| |src/main/java| +|supportAsync|Wrap responses in CompletionStage type, allowing asynchronous computation (requires JAX-RS 2.1).| |false| |title|a title describing the application| |OpenAPI Server| |useBeanValidation|Use BeanValidation API annotations| |true| |useSwaggerAnnotations|Whether to generate Swagger annotations.| |true| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java index a0313a6bc8e..8b50933247a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java @@ -102,6 +102,7 @@ public class JavaJAXRSSpecServerCodegen extends AbstractJavaJAXRSServerCodegen { 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))); cliOptions.add(CliOption.newBoolean(USE_SWAGGER_ANNOTATIONS, "Whether to generate Swagger annotations.", useSwaggerAnnotations)); 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.")); + cliOptions.add(CliOption.newBoolean(SUPPORT_ASYNC, "Wrap responses in CompletionStage type, allowing asynchronous computation (requires JAX-RS 2.1).", supportAsync)); } @Override @@ -121,6 +122,14 @@ public class JavaJAXRSSpecServerCodegen extends AbstractJavaJAXRSServerCodegen { additionalProperties.remove(RETURN_RESPONSE); } } + if (additionalProperties.containsKey(SUPPORT_ASYNC)) { + supportAsync = Boolean.parseBoolean(additionalProperties.get(SUPPORT_ASYNC).toString()); + if (!supportAsync) { + additionalProperties.remove(SUPPORT_ASYNC); + } else { + setJava8ModeAndAdditionalProperties(true); + } + } if (QUARKUS_LIBRARY.equals(library) || THORNTAIL_LIBRARY.equals(library) || HELIDON_LIBRARY.equals(library) || OPEN_LIBERTY_LIBRARY.equals(library) || KUMULUZEE_LIBRARY.equals(library)) { useSwaggerAnnotations = false; } else { diff --git a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/api.mustache b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/api.mustache index b19e2510d71..37f5ba04013 100644 --- a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/api.mustache +++ b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/api.mustache @@ -9,6 +9,10 @@ import javax.ws.rs.core.Response; {{#useSwaggerAnnotations}} import io.swagger.annotations.*; {{/useSwaggerAnnotations}} +{{#supportAsync}} +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CompletableFuture; +{{/supportAsync}} import java.io.InputStream; import java.util.Map; diff --git a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiInterface.mustache b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiInterface.mustache index 0fcf5420c28..77e7bd3e3da 100644 --- a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiInterface.mustache +++ b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiInterface.mustache @@ -10,4 +10,4 @@ {{/isOAuth}}{{/authMethods}} }{{/hasAuthMethods}}, tags={ {{#vendorExtensions.x-tags}}"{{tag}}"{{^-last}}, {{/-last}}{{/vendorExtensions.x-tags}} }) @ApiResponses(value = { {{#responses}} @ApiResponse(code = {{{code}}}, message = "{{{message}}}", response = {{{baseType}}}.class{{#returnContainer}}, responseContainer = "{{{.}}}"{{/returnContainer}}){{^-last}},{{/-last}}{{/responses}} }){{/useSwaggerAnnotations}} - {{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{>returnTypeInterface}}{{/returnResponse}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}); \ No newline at end of file + {{#supportAsync}}{{>returnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{>returnTypeInterface}}{{/returnResponse}}{{/supportAsync}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}); \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiMethod.mustache b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiMethod.mustache index d6c4ae7c82c..edecd390f9c 100644 --- a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiMethod.mustache +++ b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/apiMethod.mustache @@ -11,6 +11,6 @@ @ApiResponses(value = { {{#responses}} @ApiResponse(code = {{{code}}}, message = "{{{message}}}", response = {{{baseType}}}.class{{#containerType}}, responseContainer = "{{{.}}}"{{/containerType}}){{^-last}},{{/-last}}{{/responses}} }){{/useSwaggerAnnotations}} - public Response {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}) { - return Response.ok().entity("magic!").build(); + public {{#supportAsync}}CompletionStage<{{/supportAsync}}Response{{#supportAsync}}>{{/supportAsync}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}) { + return {{#supportAsync}}CompletableFuture.supplyAsync(() -> {{/supportAsync}}Response.ok().entity("magic!").build(){{#supportAsync}}){{/supportAsync}}; } \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/pom.mustache b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/pom.mustache index 48ac267133b..3aa05e79fbd 100644 --- a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/pom.mustache +++ b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/pom.mustache @@ -140,6 +140,11 @@ {{/useBeanValidation}} +{{#java8}} + 1.8 + ${java.version} + ${java.version} +{{/java8}} 2.9.9 4.13.1 {{#useBeanValidation}} diff --git a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/returnAsyncTypeInterface.mustache b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/returnAsyncTypeInterface.mustache new file mode 100644 index 00000000000..0da348c7a22 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/returnAsyncTypeInterface.mustache @@ -0,0 +1 @@ +CompletionStage<{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{#returnContainer}}{{#isMap}}Map{{/isMap}}{{#isArray}}{{{returnContainer}}}<{{{returnBaseType}}}>{{/isArray}}{{/returnContainer}}{{^returnContainer}}{{{returnBaseType}}}{{/returnContainer}}{{/returnResponse}}> \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java index 3fe1133d755..c04197d9091 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java @@ -28,6 +28,11 @@ import java.util.Map; import static org.openapitools.codegen.TestUtils.assertFileContains; import static org.openapitools.codegen.TestUtils.validateJavaSourceFiles; +import static org.openapitools.codegen.languages.AbstractJavaCodegen.JAVA8_MODE; +import static org.openapitools.codegen.languages.AbstractJavaJAXRSServerCodegen.USE_TAGS; +import static org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen.INTERFACE_ONLY; +import static org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen.SUPPORT_ASYNC; +import static org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen.RETURN_RESPONSE; import static org.testng.Assert.assertTrue; /** @@ -95,6 +100,8 @@ public class JavaJAXRSSpecServerCodegenTest extends JavaJaxrsBaseTest { codegen.additionalProperties().put(CodegenConstants.INVOKER_PACKAGE, "xyz.yyyyy.iiii.invoker"); codegen.additionalProperties().put("serverPort", "8088"); codegen.additionalProperties().put(JavaJAXRSSpecServerCodegen.OPEN_API_SPEC_FILE_LOCATION, "openapi.yml"); + codegen.additionalProperties().put(SUPPORT_ASYNC, true); + codegen.additionalProperties().put(JAVA8_MODE, false); codegen.processOpts(); OpenAPI openAPI = new OpenAPI(); @@ -112,6 +119,8 @@ public class JavaJAXRSSpecServerCodegenTest extends JavaJaxrsBaseTest { Assert.assertEquals(codegen.additionalProperties().get(AbstractJavaJAXRSServerCodegen.SERVER_PORT), "8088"); Assert.assertEquals(codegen.getOpenApiSpecFileLocation(), "openapi.yml"); Assert.assertEquals(codegen.additionalProperties().get(JavaJAXRSSpecServerCodegen.OPEN_API_SPEC_FILE_LOCATION), "openapi.yml"); + Assert.assertEquals(codegen.additionalProperties().get(SUPPORT_ASYNC), "true"); + Assert.assertEquals(codegen.additionalProperties().get(JAVA8_MODE), true); //overridden by supportAsync=true } /** @@ -419,4 +428,170 @@ public class JavaJAXRSSpecServerCodegenTest extends JavaJaxrsBaseTest { assertFileContains(path, "\nimport java.util.Set;\n"); } + + @Test + public void generateApiWithAsyncSupport() throws Exception { + final File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI(); + + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled + + final ClientOptInput input = new ClientOptInput() + .openAPI(openAPI) + .config(codegen); //Using JavaJAXRSSpecServerCodegen + + final DefaultGenerator generator = new DefaultGenerator(); + final List files = generator.opts(input).generate(); //When generating files + + //Then the java files are compilable + validateJavaSourceFiles(files); + + //And the generated class contains CompletionStage + TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java"); + assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"), + "\nimport java.util.concurrent.CompletionStage;\n", + "\nimport java.util.concurrent.CompletableFuture;\n", + "\npublic CompletionStage pingGet() {\n", + "\nCompletableFuture.supplyAsync(() -> Response.ok().entity(\"magic!\").build())\n" + ); + } + + @Test + public void generateApiWithAsyncSupportAndInterfaceOnly() throws Exception { + final File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + final OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI(); + + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled + codegen.additionalProperties().put(INTERFACE_ONLY, true); //And only interfaces are generated + + final ClientOptInput input = new ClientOptInput() + .openAPI(openAPI) + .config(codegen); //Using JavaJAXRSSpecServerCodegen + + final DefaultGenerator generator = new DefaultGenerator(); + final List files = generator.opts(input).generate(); //When generating files + + //Then the java files are compilable + validateJavaSourceFiles(files); + + //And the generated interface contains CompletionStage + TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java"); + assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"), + "\nimport java.util.concurrent.CompletionStage;\n", + "\nCompletionStage pingGet();\n"); + } + + @Test + public void generateApiWithAsyncSupportAndInterfaceOnlyAndResponse() throws Exception { + final File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + final OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI(); + + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled + codegen.additionalProperties().put(INTERFACE_ONLY, true); //And only interfaces are generated + codegen.additionalProperties().put(RETURN_RESPONSE, true); //And return type is Response + + final ClientOptInput input = new ClientOptInput() + .openAPI(openAPI) + .config(codegen); //Using JavaJAXRSSpecServerCodegen + + final DefaultGenerator generator = new DefaultGenerator(); + final List files = generator.opts(input).generate(); //When generating files + + //Then the java files are compilable + validateJavaSourceFiles(files); + + //And the generated interface contains CompletionStage + TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java"); + assertFileContains(output.toPath().resolve( "src/gen/java/org/openapitools/api/PingApi.java"), + "\nimport java.util.concurrent.CompletionStage;\n", + "\nCompletionStage pingGet();\n"); + } + + + @Test + public void generatePetstoreAPIWithAsyncSupport() throws Exception { + final File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + final OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/petstore.yaml", null, new ParseOptions()).getOpenAPI(); + + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled + codegen.additionalProperties().put(INTERFACE_ONLY, true); //And only interfaces are generated + + final ClientOptInput input = new ClientOptInput() + .openAPI(openAPI) + .config(codegen); //using JavaJAXRSSpecServerCodegen + + final DefaultGenerator generator = new DefaultGenerator(); + final List files = generator.opts(input).generate(); //When generating files + + //Then the java files are compilable + validateJavaSourceFiles(files); + + //And the generated interfaces contains CompletionStage + TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PetApi.java"); + assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PetApi.java"), + "\nimport java.util.concurrent.CompletionStage;\n", + "CompletionStage deletePet", //Support empty response + "CompletionStage> findPetsByStatus", //Support type of arrays response + "CompletionStage getPetById" //Support single type response + ); + + TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/StoreApi.java"); + assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/StoreApi.java"), + "\nimport java.util.concurrent.CompletionStage;\n", + "CompletionStage>" //Support map response + ); + + TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/UserApi.java"); + assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/UserApi.java"), + "\nimport java.util.concurrent.CompletionStage;\n", + "CompletionStage" //Support simple types + ); + } + + @Test + public void generatePingWithAsyncSupportPrimitiveType() throws Exception { + final File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + final OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/issue_4832.yaml", null, new ParseOptions()).getOpenAPI(); + + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(SUPPORT_ASYNC, true); //Given support async is enabled + codegen.additionalProperties().put(INTERFACE_ONLY, true); //And only interfaces are generated + codegen.additionalProperties().put(USE_TAGS, true); //And use tags to generate everything in PingApi.java + + final ClientOptInput input = new ClientOptInput() + .openAPI(openAPI) + .config(codegen); //using JavaJAXRSSpecServerCodegen + + final DefaultGenerator generator = new DefaultGenerator(); + final List files = generator.opts(input).generate(); //When generating files + + //Then the java files are compilable + validateJavaSourceFiles(files); + + //And the generated interfaces contains CompletionStage with proper classes instead of primitive types + TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java"); + TestUtils.assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"), + "CompletionStage pingGetBoolean", //Support primitive types response + "CompletionStage pingGetInteger" //Support primitive types response + ); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/issue_4832.yaml b/modules/openapi-generator/src/test/resources/3_0/issue_4832.yaml new file mode 100644 index 00000000000..581ffb146f6 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/issue_4832.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.1 +info: + title: ping that return primitive types + version: '1.0' +servers: + - url: 'http://localhost:8082/' +paths: + /pingBoolean: + get: + operationId: pingGetBoolean + tags: [Ping] + responses: + '200': + description: OK + content: + 'application/json': + schema: + type: boolean + /pingInteger: + get: + operationId: pingGetInteger + tags: [Ping] + responses: + '200': + description: OK + content: + 'application/json': + schema: + type: integer + format: int32 +