From c9ec084418df9d1df0e3bb8d552bc110348bdfd8 Mon Sep 17 00:00:00 2001 From: Jim Schubert Date: Sat, 25 Jan 2020 18:28:16 -0500 Subject: [PATCH] :bug: Fixing some issues with threading and NPE (#5107) * :bug: Fixing some issues with threading and NPE After running Sonar on the master branch, some major analysis opportunities were displayed. This fixes the use of SimpleDateFormat stored as static fields. SimpleDateFormat is not thread-safe, and may retain data across threads. While there's no indicator that this has caused any issues (these are mostly used for example code), we should follow these best practices. This also fixes a handful of NPE and other minor issues such as comparing Boolean.TRUE to strings and no wrapping some closeables in try-with-resources. * [cli] Unit test GenerateBatch custom deserialization helper * Quiet batch mode in sonar.yml * Suppress unnecessary warnings (ThreadLocals in static fields) --- .github/workflows/sonar.yml | 2 +- bin/ci/java-jaxrs-datelib-j8.json | 2 +- .../java-jaxrs-resteasy-eap-java8-server.json | 2 +- .../java-jaxrs-resteasy-eap-joda-server.json | 2 +- bin/ci/java-jaxrs-resteasy-joda-server.json | 2 +- .../codegen/cmd/GenerateBatch.java | 49 +++++--- .../codegen/cmd/GenerateBatchTest.java | 102 ++++++++++++++++ .../batch/common/jaxrs-datelib-j8.json | 7 ++ .../batch/common/jaxrs-datelib-j8.yaml | 6 + .../batch/jaxrs-datelib-j8-json-include.yaml | 8 ++ .../batch/jaxrs-datelib-j8-yaml-include.json | 10 ++ .../resources/batch/jaxrs-datelib-j8.json | 10 ++ .../resources/batch/jaxrs-datelib-j8.yaml | 8 ++ .../test/resources/batch/specs/petstore.yaml | 111 ++++++++++++++++++ .../codegen/plugin/CodeGenMojo.java | 13 +- .../AbstractTypeScriptClientCodegen.java | 10 +- .../languages/JavaCXFExtServerCodegen.java | 63 +++++----- .../PythonAbstractConnexionServerCodegen.java | 4 +- .../PythonClientExperimentalCodegen.java | 2 +- .../codegen/languages/RClientCodegen.java | 6 +- .../codegen/languages/RustServerCodegen.java | 7 +- .../codegen/languages/Swift4Codegen.java | 4 +- .../languages/Swift5ClientCodegen.java | 4 +- .../TypeScriptFetchClientCodegen.java | 2 +- .../TypeScriptReduxQueryClientCodegen.java | 2 +- .../codegen/utils/ModelUtils.java | 16 +-- .../codegen/utils/URLPathUtils.java | 36 +++--- 27 files changed, 380 insertions(+), 110 deletions(-) create mode 100644 modules/openapi-generator-cli/src/test/java/org/openapitools/codegen/cmd/GenerateBatchTest.java create mode 100644 modules/openapi-generator-cli/src/test/resources/batch/common/jaxrs-datelib-j8.json create mode 100644 modules/openapi-generator-cli/src/test/resources/batch/common/jaxrs-datelib-j8.yaml create mode 100644 modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8-json-include.yaml create mode 100644 modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8-yaml-include.json create mode 100644 modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8.json create mode 100644 modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8.yaml create mode 100644 modules/openapi-generator-cli/src/test/resources/batch/specs/petstore.yaml diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 76c8a9a864f..dbe6db54b6e 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -21,4 +21,4 @@ jobs: - name: Jacoco Aggregate run: mvn jacoco:report-aggregate - name: Publish to Sonar - run: mvn sonar:sonar -Dsonar.projectKey=OpenAPITools_openapi-generator -Dsonar.organization=openapitools -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=${{ secrets.SONAR_LOGIN }} -Dsonar.branch.name=${GITHUB_REF##*/} + run: mvn -B -q sonar:sonar -Dsonar.projectKey=OpenAPITools_openapi-generator -Dsonar.organization=openapitools -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=${{ secrets.SONAR_LOGIN }} -Dsonar.branch.name=${GITHUB_REF##*/} diff --git a/bin/ci/java-jaxrs-datelib-j8.json b/bin/ci/java-jaxrs-datelib-j8.json index 89e9b9c7fec..f97c7cfa718 100644 --- a/bin/ci/java-jaxrs-datelib-j8.json +++ b/bin/ci/java-jaxrs-datelib-j8.json @@ -1,5 +1,5 @@ { - "!include": "./bin/jaxrs-datelib-j8.json", + "!include": "bin/jaxrs-datelib-j8.json", "generatorName": "jaxrs-jersey", "inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml", "outputDir": "samples/server/petstore/jaxrs-datelib-j8/", diff --git a/bin/ci/java-jaxrs-resteasy-eap-java8-server.json b/bin/ci/java-jaxrs-resteasy-eap-java8-server.json index d16a63b474c..2a7af42d82b 100644 --- a/bin/ci/java-jaxrs-resteasy-eap-java8-server.json +++ b/bin/ci/java-jaxrs-resteasy-eap-java8-server.json @@ -1,5 +1,5 @@ { - "!include": "./bin/jaxrs-resteasy-eap-java8-petstore-server.json", + "!include": "bin/jaxrs-resteasy-eap-java8-petstore-server.json", "artifactId": "jaxrs-resteasy-eap-java8-server", "generatorName": "jaxrs-resteasy-eap", "inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml", diff --git a/bin/ci/java-jaxrs-resteasy-eap-joda-server.json b/bin/ci/java-jaxrs-resteasy-eap-joda-server.json index d974ed72f8e..242744045b1 100644 --- a/bin/ci/java-jaxrs-resteasy-eap-joda-server.json +++ b/bin/ci/java-jaxrs-resteasy-eap-joda-server.json @@ -1,5 +1,5 @@ { - "!include": "./bin/jaxrs-resteasy-eap-joda-petstore-server.json", + "!include": "bin/jaxrs-resteasy-eap-joda-petstore-server.json", "artifactId": "jaxrs-resteasy-eap-joda-server", "generatorName": "jaxrs-resteasy-eap", "inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml", diff --git a/bin/ci/java-jaxrs-resteasy-joda-server.json b/bin/ci/java-jaxrs-resteasy-joda-server.json index ea2484571fe..aa243b2b0d2 100644 --- a/bin/ci/java-jaxrs-resteasy-joda-server.json +++ b/bin/ci/java-jaxrs-resteasy-joda-server.json @@ -1,5 +1,5 @@ { - "!include": "./bin/jaxrs-resteasy-joda-petstore-server.json", + "!include": "bin/jaxrs-resteasy-joda-petstore-server.json", "artifactId": "jaxrs-resteasy-joda-server", "generatorName": "jaxrs-resteasy", "inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml", diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/GenerateBatch.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/GenerateBatch.java index f1e6632b339..fde5f6ea4a3 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/GenerateBatch.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/GenerateBatch.java @@ -123,27 +123,12 @@ public class GenerateBatch implements Runnable { } } - LOGGER.info(String.format(Locale.ROOT, "Batch generation using up to %d threads.\nIncludes: %s\nRoot: %s", numThreads, includesDir.getAbsolutePath(), rootDir.toAbsolutePath().toString())); // Create a module which loads our config files, but supports a special "!include" key which can point to an existing config file. // This allows us to create a sort of meta-config which holds configs which are otherwise required at CLI time (via generate task). // That is, this allows us to create a wrapper config for generatorName, inputSpec, outputDir, etc. - SimpleModule module = new SimpleModule("GenerateBatch"); - module.setDeserializerModifier(new BeanDeserializerModifier() { - @Override - public JsonDeserializer modifyDeserializer(DeserializationConfig config, - BeanDescription bd, JsonDeserializer original) { - JsonDeserializer result; - if (bd.getBeanClass() == DynamicSettings.class) { - result = new DynamicSettingsRefSupport(original, includesDir); - } else { - result = original; - } - return result; - } - }); - + SimpleModule module = getCustomDeserializationModel(includesDir); List configurators = configs.stream().map(config -> CodegenConfigurator.fromFile(config, module)).collect(Collectors.toList()); // it doesn't make sense to interleave INFO level logs, so limit these to only ERROR. @@ -169,6 +154,8 @@ public class GenerateBatch implements Runnable { System.out.println("COMPLETE."); } catch (InterruptedException e) { e.printStackTrace(); + // re-interrupt + Thread.currentThread().interrupt(); } } @@ -227,6 +214,28 @@ public class GenerateBatch implements Runnable { } } + static SimpleModule getCustomDeserializationModel(final File includesDir) { + // Create a module which loads our config files, but supports a special "!include" key which can point to an existing config file. + // This allows us to create a sort of meta-config which holds configs which are otherwise required at CLI time (via generate task). + // That is, this allows us to create a wrapper config for generatorName, inputSpec, outputDir, etc. + SimpleModule module = new SimpleModule("GenerateBatch"); + module.setDeserializerModifier(new BeanDeserializerModifier() { + @Override + public JsonDeserializer modifyDeserializer(DeserializationConfig config, + BeanDescription bd, JsonDeserializer original) { + JsonDeserializer result; + if (bd.getBeanClass() == DynamicSettings.class) { + result = new DynamicSettingsRefSupport(original, includesDir); + } else { + result = original; + } + return result; + } + }); + + return module; + } + static class DynamicSettingsRefSupport extends DelegatingDeserializer { private static final String INCLUDE = "!include"; private File scanDir; @@ -255,11 +264,13 @@ public class GenerateBatch implements Runnable { // load the file into the tree node and continue parsing as normal ((ObjectNode) node).remove(INCLUDE); - JsonParser includeParser = codec.getFactory().createParser(includeFile); - TreeNode includeNode = includeParser.readValueAsTree(); + TreeNode includeNode; + try (JsonParser includeParser = codec.getFactory().createParser(includeFile)) { + includeNode = includeParser.readValueAsTree(); + } ObjectReader reader = codec.readerForUpdating(node); - TreeNode updated = reader.readValue(includeFile); + TreeNode updated = reader.readValue(includeNode.traverse()); JsonParser updatedParser = updated.traverse(); updatedParser.nextToken(); return super.deserialize(updatedParser, ctx); diff --git a/modules/openapi-generator-cli/src/test/java/org/openapitools/codegen/cmd/GenerateBatchTest.java b/modules/openapi-generator-cli/src/test/java/org/openapitools/codegen/cmd/GenerateBatchTest.java new file mode 100644 index 00000000000..c06d7abfb93 --- /dev/null +++ b/modules/openapi-generator-cli/src/test/java/org/openapitools/codegen/cmd/GenerateBatchTest.java @@ -0,0 +1,102 @@ +package org.openapitools.codegen.cmd; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.openapitools.codegen.config.Context; +import org.openapitools.codegen.config.GeneratorSettings; +import org.openapitools.codegen.config.WorkflowSettings; +import org.testng.ITestContext; +import org.testng.TestRunner; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.testng.Assert.*; + +@SuppressWarnings("RedundantThrows") +public class GenerateBatchTest { + private static final String SPEC_FILE = "batch/specs/petstore.yaml"; + private static final String JAXRS_DATELIB_J8_JSON = "jaxrs-datelib-j8.json"; + private static final String JAXRS_DATELIB_J8_YAML = "jaxrs-datelib-j8.yaml"; + private static final String JAXRS_DATELIB_J8_YAML_INCLUDE_JSON = "jaxrs-datelib-j8-yaml-include.json"; + private static final String JAXRS_DATELIB_J8_JSON_INCLUDE_YAML = "jaxrs-datelib-j8-json-include.yaml"; + Path workingDirectory; + + @BeforeTest + public void setUp(ITestContext ctx) throws IOException { + workingDirectory = Paths.get("src", "test", "resources", "batch"); + } + + @DataProvider(name = "customIncludeDeserializerFiles") + public Object[][] customIncludeDeserializerFiles() { + return new Object[][] { + {JAXRS_DATELIB_J8_JSON}, + {JAXRS_DATELIB_J8_YAML}, + {JAXRS_DATELIB_J8_JSON_INCLUDE_YAML} + }; + } + + @Test(dataProvider = "customIncludeDeserializerFiles") + public void testDeserializerWithJsonInclude(String file) throws IOException { + String config = getTargetResourceAsFile(file).toString(); + SimpleModule module = GenerateBatch.getCustomDeserializationModel(getIncludesDir()); + CodegenConfigurator loaded = CodegenConfigurator.fromFile(config, module); + + Map expectedAdditionalProperties = new HashMap<>(); + expectedAdditionalProperties.put("serverPort", "8082"); + expectedAdditionalProperties.put("dateLibrary", "java8"); + expectedAdditionalProperties.put("hideGenerationTimestamp", true); + expectedAdditionalProperties.put("serializableModel", true); + expectedAdditionalProperties.put("withXml", true); + expectedAdditionalProperties.put("java8", true); + expectedAdditionalProperties.put("useBeanValidation", true); + + assertNotNull(loaded); + + Context context = loaded.toContext(); + WorkflowSettings workflowSettings = context.getWorkflowSettings(); + GeneratorSettings generatorSettings = context.getGeneratorSettings(); + + assertNotNull(workflowSettings); + assertNotNull(generatorSettings); + + assertEquals(generatorSettings.getGeneratorName(), "jaxrs-jersey"); + assertEquals(workflowSettings.getOutputDir(), "outputDir"); + assertEquals(workflowSettings.getInputSpec(), SPEC_FILE); + assertTrue(generatorSettings.getAdditionalProperties().size() >= 7); + + Set> actualSet = generatorSettings.getAdditionalProperties().entrySet(); + assertTrue(actualSet.containsAll(expectedAdditionalProperties.entrySet())); + } + + @SuppressWarnings("unused") + @Test( + expectedExceptions = { RuntimeException.class }, + expectedExceptionsMessageRegExp = "Unable to deserialize config file: .*" + ) + public void testInvalidDeserializerWithIncludeOption() { + // JSON is valid YAML, but not the other way around, so we can't load a YAML include from a JSON config + // to do so would require additional work. + String config = getTargetResourceAsFile(JAXRS_DATELIB_J8_YAML_INCLUDE_JSON).toString(); + SimpleModule module = GenerateBatch.getCustomDeserializationModel(getIncludesDir()); + CodegenConfigurator loaded = CodegenConfigurator.fromFile(config, module); + fail("Expected an exception when trying to load a YAML include from a JSON file"); + } + + private File getIncludesDir() { + // The includes directory would be "batch" under resources here, as everything is relative to this directory. + return workingDirectory.toFile(); + } + + private File getTargetResourceAsFile(String relative) { + return workingDirectory.resolve(relative).toAbsolutePath().toFile(); + } +} \ No newline at end of file diff --git a/modules/openapi-generator-cli/src/test/resources/batch/common/jaxrs-datelib-j8.json b/modules/openapi-generator-cli/src/test/resources/batch/common/jaxrs-datelib-j8.json new file mode 100644 index 00000000000..45c69a2d305 --- /dev/null +++ b/modules/openapi-generator-cli/src/test/resources/batch/common/jaxrs-datelib-j8.json @@ -0,0 +1,7 @@ +{ + "serializableModel": true, + "withXml": true, + "dateLibrary": "java8", + "java8": true, + "useBeanValidation": true +} diff --git a/modules/openapi-generator-cli/src/test/resources/batch/common/jaxrs-datelib-j8.yaml b/modules/openapi-generator-cli/src/test/resources/batch/common/jaxrs-datelib-j8.yaml new file mode 100644 index 00000000000..216aa6d2dcb --- /dev/null +++ b/modules/openapi-generator-cli/src/test/resources/batch/common/jaxrs-datelib-j8.yaml @@ -0,0 +1,6 @@ +--- +serializableModel: true +withXml: true +dateLibrary: java8 +java8: true +useBeanValidation: true diff --git a/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8-json-include.yaml b/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8-json-include.yaml new file mode 100644 index 00000000000..121076c9652 --- /dev/null +++ b/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8-json-include.yaml @@ -0,0 +1,8 @@ +--- +"!include": common/jaxrs-datelib-j8.json +generatorName: jaxrs-jersey +inputSpec: batch/specs/petstore.yaml +outputDir: outputDir +additionalProperties: + hideGenerationTimestamp: true + serverPort: '8082' diff --git a/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8-yaml-include.json b/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8-yaml-include.json new file mode 100644 index 00000000000..bb8957cf653 --- /dev/null +++ b/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8-yaml-include.json @@ -0,0 +1,10 @@ +{ + "!include": "common/jaxrs-datelib-j8.yaml", + "generatorName": "jaxrs-jersey", + "inputSpec": "batch/specs/petstore.yaml", + "outputDir": "outputDir", + "additionalProperties": { + "hideGenerationTimestamp": true, + "serverPort": "8082" + } +} \ No newline at end of file diff --git a/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8.json b/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8.json new file mode 100644 index 00000000000..c3f03859f04 --- /dev/null +++ b/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8.json @@ -0,0 +1,10 @@ +{ + "!include": "common/jaxrs-datelib-j8.json", + "generatorName": "jaxrs-jersey", + "inputSpec": "batch/specs/petstore.yaml", + "outputDir": "outputDir", + "additionalProperties": { + "hideGenerationTimestamp": true, + "serverPort": "8082" + } +} \ No newline at end of file diff --git a/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8.yaml b/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8.yaml new file mode 100644 index 00000000000..bab10df8dd1 --- /dev/null +++ b/modules/openapi-generator-cli/src/test/resources/batch/jaxrs-datelib-j8.yaml @@ -0,0 +1,8 @@ +--- +"!include": common/jaxrs-datelib-j8.yaml +generatorName: jaxrs-jersey +inputSpec: batch/specs/petstore.yaml +outputDir: outputDir +additionalProperties: + hideGenerationTimestamp: true + serverPort: '8082' diff --git a/modules/openapi-generator-cli/src/test/resources/batch/specs/petstore.yaml b/modules/openapi-generator-cli/src/test/resources/batch/specs/petstore.yaml new file mode 100644 index 00000000000..10f4c499ff8 --- /dev/null +++ b/modules/openapi-generator-cli/src/test/resources/batch/specs/petstore.yaml @@ -0,0 +1,111 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java index 6dc8fb5fd0f..49fb45efa07 100644 --- a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java +++ b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java @@ -777,12 +777,13 @@ public class CodeGenMojo extends AbstractMojo { conn.setRequestProperty(auth.getKeyName(), auth.getValue()); } } - ReadableByteChannel readableByteChannel = Channels.newChannel(conn.getInputStream()); - - FileOutputStream fileOutputStream = new FileOutputStream(inputSpecTempFile); - FileChannel fileChannel = fileOutputStream.getChannel(); - - fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); + try (ReadableByteChannel readableByteChannel = Channels.newChannel(conn.getInputStream())) { + FileChannel fileChannel; + try (FileOutputStream fileOutputStream = new FileOutputStream(inputSpecTempFile)) { + fileChannel = fileOutputStream.getChannel(); + fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); + } + } } ByteSource inputSpecByteSource = diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java index ab6869f5c37..0285772a69a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java @@ -49,7 +49,9 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp public static final String NPM_VERSION = "npmVersion"; public static final String SNAPSHOT = "snapshot"; - protected static final SimpleDateFormat SNAPSHOT_SUFFIX_FORMAT = new SimpleDateFormat("yyyyMMddHHmm", Locale.ROOT); + // NOTE: SimpleDateFormat is not thread-safe and may not be static unless it is thread-local + @SuppressWarnings("squid:S5164") + protected static final ThreadLocal SNAPSHOT_SUFFIX_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmm", Locale.ROOT)); protected String modelPropertyNaming = "camelCase"; protected Boolean supportsES6 = false; @@ -158,7 +160,7 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp " Required to generate a full package")); this.cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package. If not provided, using the version from the OpenAPI specification file.").defaultValue(this.getNpmVersion())); this.cliOptions.add(CliOption.newBoolean(SNAPSHOT, - "When setting this property to true, the version will be suffixed with -SNAPSHOT." + this.SNAPSHOT_SUFFIX_FORMAT.toPattern(), + "When setting this property to true, the version will be suffixed with -SNAPSHOT." + this.SNAPSHOT_SUFFIX_FORMAT.get().toPattern(), false)); } @@ -204,9 +206,9 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp if (additionalProperties.containsKey(SNAPSHOT) && Boolean.parseBoolean(additionalProperties.get(SNAPSHOT).toString())) { if (npmVersion.toUpperCase(Locale.ROOT).matches("^.*-SNAPSHOT$")) { - this.setNpmVersion(npmVersion + "." + SNAPSHOT_SUFFIX_FORMAT.format(new Date())); + this.setNpmVersion(npmVersion + "." + SNAPSHOT_SUFFIX_FORMAT.get().format(new Date())); } else { - this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.format(new Date())); + this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.get().format(new Date())); } } additionalProperties.put(NPM_VERSION, npmVersion); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaCXFExtServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaCXFExtServerCodegen.java index 355afddd0b5..0ed1116bff6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaCXFExtServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaCXFExtServerCodegen.java @@ -25,24 +25,11 @@ import java.math.BigDecimal; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.TimeZone; -import java.util.TreeSet; +import java.util.*; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.openapitools.codegen.CliOption; -import org.openapitools.codegen.CodegenConstants; import org.openapitools.codegen.CodegenModel; import org.openapitools.codegen.CodegenOperation; import org.openapitools.codegen.CodegenParameter; @@ -275,10 +262,25 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF private static final String INDENT = " "; - private static final SimpleDateFormat ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + // SimpleDateFormat is not thread-safe, and may not be stored in a static field unless stored by ThreadLocal. + // It's not enough to add a ThreadLocal at the usage site. + @SuppressWarnings("squid:S5164") + private static final ThreadLocal ISO8601_DATE_FORMAT = ThreadLocal.withInitial(() -> + { + SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + f.setTimeZone(TimeZone.getTimeZone("UTC")); + return f; + }); - private static final SimpleDateFormat ISO8601_DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", - Locale.getDefault()); + // SimpleDateFormat is not thread-safe, and may not be stored in a static field unless stored by ThreadLocal. + // It's not enough to add a ThreadLocal at the usage site. + @SuppressWarnings("squid:S5164") + private static final ThreadLocal ISO8601_DATETIME_FORMAT = ThreadLocal.withInitial(() -> + { + SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.getDefault()); + f.setTimeZone(TimeZone.getTimeZone("UTC")); + return f; + }); private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; @@ -292,13 +294,11 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF "LocalDateTime", "LocalDate"); static { - ISO8601_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - ISO8601_DATETIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); long minDate = 0; long maxDate = 0; try { - minDate = ISO8601_DATETIME_FORMAT.parse("1970-01-01T00:00:00Z").getTime(); - maxDate = ISO8601_DATETIME_FORMAT.parse("2099-12-31T23:59:59Z").getTime(); + minDate = ISO8601_DATETIME_FORMAT.get().parse("1970-01-01T00:00:00Z").getTime(); + maxDate = ISO8601_DATETIME_FORMAT.get().parse("2099-12-31T23:59:59Z").getTime(); } catch (ParseException e) { // Won't happen with the values provided. } @@ -383,12 +383,15 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF short max = var == null || var.maximum == null ? Byte.MAX_VALUE : Byte.parseByte(var.maximum); short exclusiveMin = (short) (var != null && var.exclusiveMinimum ? 1 : 0); short inclusiveMax = (short) (var == null || !var.exclusiveMaximum ? 1 : 0); - int itemCount = Math.max(var.itemCount, var.minItems == null ? 1 : Math.max(1, var.minItems)); + int itemCount = 0; + if (var != null) { + itemCount = Math.max(var.itemCount, var.minItems == null ? 1 : Math.max(1, var.minItems)); + } byte[] randomBytes = new byte[itemCount]; for (int i = 0; i < itemCount; i++) randomBytes[i] = (byte) (min + exclusiveMin + ((max + inclusiveMax - min - exclusiveMin) * Math.random())); String randomBytesBase64 = Base64.getEncoder().encodeToString(randomBytes); - if (loadTestDataFromFile) + if (loadTestDataFromFile && var != null) var.addTestData(randomBytesBase64); else buffer.append('"'); @@ -431,13 +434,10 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF * @param buffer * @param indent * @param op + * @param var * @param localVars * @param models - * @param type - * @param baseType - * @param isListContainer - * @param isMapContainer - * @param localVar + * * @return localVar with a numeric suffix if necessary to ensure uniqueness. */ private String appendLocalVariable(StringBuilder buffer, String indent, CodegenOperation op, CodegenVariable var, @@ -582,7 +582,7 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF long minDate = MIN_DATE; long maxDate = MAX_DATE; if (var != null) { - DateFormat df = var.dataFormat.equals("date-time") ? ISO8601_DATETIME_FORMAT : ISO8601_DATE_FORMAT; + DateFormat df = var.dataFormat.equals("date-time") ? ISO8601_DATETIME_FORMAT.get() : ISO8601_DATE_FORMAT.get(); String isoFormat = var.dataFormat.equals("date-time") ? "date-time" : "full-date"; if (var.minimum != null) { try { @@ -622,10 +622,10 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF Date randomDate = new Date(randomDateLong); switch (var.dataFormat) { case "date": - var.addTestData(ISO8601_DATE_FORMAT.format(randomDate)); + var.addTestData(ISO8601_DATE_FORMAT.get().format(randomDate)); break; case "date-time": - var.addTestData(ISO8601_DATETIME_FORMAT.format(randomDate)); + var.addTestData(ISO8601_DATETIME_FORMAT.get().format(randomDate)); break; } } else { @@ -787,7 +787,6 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF * @param localVar The variable whose value is to be set. * @param localVars Tracks local variables which have been allocated. * @param models A map of models, keyed on class name. - * @param type The value type. */ private void appendScalarValue(StringBuilder buffer, String indent, CodegenOperation op, CodegenVariable var, String localVar, Collection localVars, Map models) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAbstractConnexionServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAbstractConnexionServerCodegen.java index 3954e98f9bb..21dd07fb653 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAbstractConnexionServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAbstractConnexionServerCodegen.java @@ -516,7 +516,7 @@ public class PythonAbstractConnexionServerCodegen extends DefaultCodegen impleme if (pathExtensions != null) { // Get and remove the (temporary) vendor extension String openapiPathname = (String) pathExtensions.remove("x-python-connexion-openapi-name"); - if (openapiPathname != null && openapiPathname != pythonPathname) { + if (openapiPathname != null && !openapiPathname.equals(pythonPathname)) { LOGGER.info("Path '" + pythonPathname + "' is not consistant with the original OpenAPI definition. It will be replaced back by '" + openapiPathname + "'"); paths.remove(pythonPathname); paths.put(openapiPathname, path); @@ -535,7 +535,7 @@ public class PythonAbstractConnexionServerCodegen extends DefaultCodegen impleme String swaggerParameterName = (String) parameterExtensions.remove("x-python-connexion-openapi-name"); if (swaggerParameterName != null) { String pythonParameterName = parameter.getName(); - if (swaggerParameterName != pythonParameterName) { + if (!swaggerParameterName.equals(pythonParameterName)) { LOGGER.info("Reverting name of parameter '" + pythonParameterName + "' of operation '" + operation.getOperationId() + "' back to '" + swaggerParameterName + "'"); parameter.setName(swaggerParameterName); } else { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java index 7e31e615773..b2a6a04b958 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java @@ -818,7 +818,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { if (ModelUtils.isFreeFormObject(p) && ModelUtils.getAdditionalProperties(p) == null) { return prefix + "bool, date, datetime, dict, float, int, list, str" + fullSuffix; } - if ((ModelUtils.isMapSchema(p) || p.getType() == "object") && ModelUtils.getAdditionalProperties(p) != null) { + if ((ModelUtils.isMapSchema(p) || "object".equals(p.getType())) && ModelUtils.getAdditionalProperties(p) != null) { Schema inner = ModelUtils.getAdditionalProperties(p); return prefix + "{str: " + getTypeString(inner, "(", ")") + "}" + fullSuffix; } else if (ModelUtils.isArraySchema(p)) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java index 273978d0a71..a6551c03012 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java @@ -710,11 +710,7 @@ public class RClientCodegen extends DefaultCodegen implements CodegenConfig { } else if (codegenParameter.isMapContainer) { // TODO: map return "TODO"; } else if (languageSpecificPrimitives.contains(codegenParameter.dataType)) { // primitive type - if ("character".equals(codegenParameter.dataType)) { - return codegenParameter.example; - } else { - return codegenParameter.example; - } + return codegenParameter.example; } else { // model // look up the model if (modelMaps.containsKey(codegenParameter.dataType)) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java index ed185188667..8815ca4e511 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java @@ -664,7 +664,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig { // Get the original API response so we get process the schema // directly. ApiResponse original; - if (rsp.code == "0") { + if ("0".equals(rsp.code)) { original = operation.getResponses().get("default"); } else { original = operation.getResponses().get(rsp.code); @@ -714,10 +714,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig { String firstProduces = null; if (original.getContent() != null) { - for (String mimetype : original.getContent().keySet()) { - firstProduces = mimetype; - break; - } + firstProduces = original.getContent().keySet().stream().findFirst().orElse(null); } // The output mime type. This allows us to do sensible fallback diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift4Codegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift4Codegen.java index eb669b02df1..77389ce3f90 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift4Codegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift4Codegen.java @@ -1011,7 +1011,7 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig { return "\"" + codegenParameter.paramName + "_example\""; } } else if ("Bool".equals(codegenParameter.dataType)) { // boolean - if (Boolean.TRUE.equals(codegenParameter.example)) { + if (Boolean.parseBoolean(codegenParameter.example)) { return "true"; } else { return "false"; @@ -1051,7 +1051,7 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig { return "\"" + codegenProperty.name + "_example\""; } } else if ("Bool".equals(codegenProperty.dataType)) { // boolean - if (Boolean.TRUE.equals(codegenProperty.example)) { + if (Boolean.parseBoolean(codegenProperty.example)) { return "true"; } else { return "false"; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java index 5bc9f1ad4d4..610da984c85 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java @@ -993,7 +993,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig return "\"" + codegenParameter.paramName + "_example\""; } } else if ("Bool".equals(codegenParameter.dataType)) { // boolean - if (Boolean.TRUE.equals(codegenParameter.example)) { + if (Boolean.parseBoolean(codegenParameter.example)) { return "true"; } else { return "false"; @@ -1033,7 +1033,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig return "\"" + codegenProperty.name + "_example\""; } } else if ("Bool".equals(codegenProperty.dataType)) { // boolean - if (Boolean.TRUE.equals(codegenProperty.example)) { + if (Boolean.parseBoolean(codegenProperty.example)) { return "true"; } else { return "false"; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java index b21869c7489..7e6bdd3d131 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java @@ -283,7 +283,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege Map _operations = (Map) operations.get("operations"); List operationList = (List) _operations.get("operation"); for (CodegenOperation op : operationList) { - if(op.returnType == "object") { + if("object".equals(op.returnType)) { op.isMapContainer = true; op.returnSimpleType = false; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptReduxQueryClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptReduxQueryClientCodegen.java index 560083e6018..020752f84a0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptReduxQueryClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptReduxQueryClientCodegen.java @@ -255,7 +255,7 @@ public class TypeScriptReduxQueryClientCodegen extends AbstractTypeScriptClientC Map _operations = (Map) operations.get("operations"); List operationList = (List) _operations.get("operation"); for (CodegenOperation op : operationList) { - if(op.returnType == "object") { + if("object".equals(op.returnType)) { op.isMapContainer = true; op.returnSimpleType = false; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java index cf2ca082caa..a539cddb1fd 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java @@ -35,11 +35,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; import java.util.stream.Collectors; @@ -246,10 +242,14 @@ public class ModelUtils { if (parameters != null) { for (Parameter p : parameters) { Parameter parameter = getReferencedParameter(openAPI, p); - if (parameter.getSchema() != null) { - visitSchema(openAPI, parameter.getSchema(), null, visitedSchemas, visitor); + if (parameter != null) { + if (parameter.getSchema() != null) { + visitSchema(openAPI, parameter.getSchema(), null, visitedSchemas, visitor); + } + visitContent(openAPI, parameter.getContent(), visitor, visitedSchemas); + } else { + LOGGER.warn("Unreferenced parameter found."); } - visitContent(openAPI, parameter.getContent(), visitor, visitedSchemas); } } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java index abb50ca4963..e1d3611fedf 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java @@ -195,29 +195,31 @@ public class URLPathUtils { */ public static String getHost(OpenAPI openAPI, final Map userDefinedVariables) { if (openAPI.getServers() != null && openAPI.getServers().size() > 0) { - return sanitizeUrl(getServerURL(openAPI.getServers().get(0), userDefinedVariables).toString()); + URL url = getServerURL(openAPI.getServers().get(0), userDefinedVariables); + return url != null ? sanitizeUrl(url.toString()) : ""; } return LOCAL_HOST; } private static String sanitizeUrl(String url) { - if (url.startsWith("//")) { - url = "http:" + url; - LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url); - } else if (url.startsWith("/")) { - url = LOCAL_HOST + url; - LOGGER.warn("'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [{}] for server URL [{}]", LOCAL_HOST, url); - } else if (!url.matches("[a-zA-Z][0-9a-zA-Z.+\\-]+://.+")) { - // Add http scheme for urls without a scheme. - // 2.0 spec is restricted to the following schemes: "http", "https", "ws", "wss" - // 3.0 spec does not have an enumerated list of schemes - // This regex attempts to capture all schemes in IANA example schemes which - // can have alpha-numeric characters and [.+-]. Examples are here: - // https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml - url = "http://" + url; - LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url); + if (url != null) { + if (url.startsWith("//")) { + url = "http:" + url; + LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url); + } else if (url.startsWith("/")) { + url = LOCAL_HOST + url; + LOGGER.warn("'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [{}] for server URL [{}]", LOCAL_HOST, url); + } else if (!url.matches("[a-zA-Z][0-9a-zA-Z.+\\-]+://.+")) { + // Add http scheme for urls without a scheme. + // 2.0 spec is restricted to the following schemes: "http", "https", "ws", "wss" + // 3.0 spec does not have an enumerated list of schemes + // This regex attempts to capture all schemes in IANA example schemes which + // can have alpha-numeric characters and [.+-]. Examples are here: + // https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml + url = "http://" + url; + LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url); + } } - return url; }