From ce0e948fdbb882f91e97a2df9e54c510e76bbdfc Mon Sep 17 00:00:00 2001 From: Philzen Date: Sun, 2 Jun 2024 23:56:33 +0200 Subject: [PATCH 1/7] Ensure temp directories are deleted after test execution --- .../codegen/plugin/CodeGenMojoTest.java | 96 +++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java index e87a391436d4..0be3db3fa140 100644 --- a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java +++ b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java @@ -16,17 +16,6 @@ package org.openapitools.codegen.plugin; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import org.apache.commons.io.FileUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.execution.DefaultMavenExecutionRequest; @@ -42,6 +31,18 @@ import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory; import org.eclipse.aether.repository.LocalRepository; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertThrows; + public class CodeGenMojoTest extends BaseTestCase { @Override protected void setUp() throws Exception { @@ -62,8 +63,7 @@ public void testCommonConfigurationWithURLInputSpec() throws Exception { @SuppressWarnings("unchecked") private void testCommonConfiguration(String profile) throws Exception { - File folder = Files.createTempDirectory("test").toFile(); - CodeGenMojo mojo = loadMojo(folder, "src/test/resources/default", profile); + CodeGenMojo mojo = loadMojo(newTempFolder(), "src/test/resources/default", profile); mojo.execute(); assertEquals("java", getVariableValueFromObject(mojo, "generatorName")); assertEquals("jersey2", getVariableValueFromObject(mojo, "library")); @@ -79,14 +79,14 @@ private void testCommonConfiguration(String profile) throws Exception { public void testHashGenerationFileContainsExecutionId() throws Exception { // GIVEN - Path folder = Files.createTempDirectory("test"); - CodeGenMojo mojo = loadMojo(folder.toFile(), "src/test/resources/default", "file", "executionId"); + final Path tempDir = newTempFolder(); + CodeGenMojo mojo = loadMojo(tempDir, "src/test/resources/default", "file", "executionId"); // WHEN mojo.execute(); // THEN - Path hashFolder = folder.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); + Path hashFolder = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); assertTrue(hashFolder.resolve("petstore.yaml-executionId.sha256").toFile().exists()); } @@ -97,21 +97,20 @@ public void testHashGenerationFileContainsExecutionId() throws Exception { * @throws Exception */ public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception { - //GIVEN /* Setup the mojo */ - final Path folder = Files.createTempDirectory("test-classpath"); - final CodeGenMojo mojo = loadMojo(folder.toFile(), "src/test/resources/classpath", null, "executionId"); + final Path tempDir = newTempFolder(); + final CodeGenMojo mojo = loadMojo(tempDir, "src/test/resources/classpath", null, "executionId"); /* Perform an initial generation */ mojo.execute(); /* Check the hash file was created */ - final Path hashFolder = folder.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); + final Path hashFolder = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); assertTrue(hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256").toFile().exists()); /* Remove the generated source */ - Files.walk(folder.resolve("target/generated-sources/common-maven/remote-openapi/src")) + Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src")) .sorted(Comparator.reverseOrder()) .map(Path::toFile) .forEach(File::delete); @@ -124,7 +123,7 @@ public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception // THEN /* Verify that the source directory has not been repopulated. If it has then we generated code again */ assertFalse("src directory should not have been regenerated", - folder.resolve("target/generated-sources/common-maven/remote-openapi/src").toFile().exists()); + tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src").toFile().exists()); } @@ -135,17 +134,16 @@ public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception * @throws Exception */ public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exception { - //GIVEN /* Setup the mojo */ - final Path folder = Files.createTempDirectory("test-classpath"); - final CodeGenMojo mojo = loadMojo(folder.toFile(), "src/test/resources/classpath", null, "executionId"); + final Path tempDir = newTempFolder(); + final CodeGenMojo mojo = loadMojo(tempDir, "src/test/resources/classpath", null, "executionId"); /* Perform an initial generation */ mojo.execute(); /* Check the hash file was created, proving a generation occurred */ - final Path hashFolder = folder.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); + final Path hashFolder = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); assertTrue(hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256").toFile().exists()); /* Update the hash contents to be a different value, simulating a spec change */ @@ -154,7 +152,7 @@ public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exceptio Arrays.asList("bd1bf4a953c858f9d47b67ed6029daacf1707e5cbd3d2e4b01383ba30363366f")); /* Remove the generated source */ - Files.walk(folder.resolve("target/generated-sources/common-maven/remote-openapi/src")) + Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src")) .sorted(Comparator.reverseOrder()) .map(Path::toFile) .forEach(File::delete); @@ -167,27 +165,26 @@ public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exceptio // THEN /* Verify that the source directory has not been repopulated. If it has then we generated code again */ assertTrue("src directory should have been regenerated", - folder.resolve("target/generated-sources/common-maven/remote-openapi/src").toFile().exists()); + tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src").toFile().exists()); } public void testCollapsedSpecProduced() throws Exception { // GIVEN - Path folder = Files.createTempDirectory("test"); - CodeGenMojo mojo = loadMojo(folder.toFile(), "src/test/resources/default", "file", "executionId"); + final Path tempDir = newTempFolder(); + CodeGenMojo mojo = loadMojo(tempDir, "src/test/resources/default", "file", "executionId"); // WHEN mojo.execute(); // THEN - File collapseSpecFile = folder.resolve("target/generated-sources/common-maven/remote-openapi/petstore-full-spec.yaml").toFile(); + File collapseSpecFile = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/petstore-full-spec.yaml").toFile(); assertTrue(collapseSpecFile.exists()); } public void testCollapsedSpecAddedToArtifacts() throws Exception { // GIVEN - Path folder = Files.createTempDirectory("test"); - CodeGenMojo mojo = loadMojo(folder.toFile(), "src/test/resources/default", "file", "executionId"); + CodeGenMojo mojo = loadMojo(newTempFolder(), "src/test/resources/default", "file", "executionId"); // WHEN mojo.execute(); @@ -201,8 +198,7 @@ public void testCollapsedSpecAddedToArtifacts() throws Exception { public void testAnyInputSpecMustBeProvided() throws Exception { // GIVEN - Path folder = Files.createTempDirectory("test"); - CodeGenMojo mojo = loadMojo(folder.toFile(), "src/test/resources/default", "file", "executionId"); + CodeGenMojo mojo = loadMojo(newTempFolder(), "src/test/resources/default", "file", "executionId"); mojo.inputSpec = null; mojo.inputSpecRootDirectory = null; @@ -215,8 +211,8 @@ public void testAnyInputSpecMustBeProvided() throws Exception { public void testInputSpecRootDirectoryDoesNotRequireInputSpec() throws Exception { // GIVEN - Path folder = Files.createTempDirectory("test"); - CodeGenMojo mojo = loadMojo(folder.toFile(), "src/test/resources/default", "file", "executionId"); + final Path tempDir = newTempFolder(); + CodeGenMojo mojo = loadMojo(tempDir, "src/test/resources/default", "file", "executionId"); mojo.inputSpec = null; mojo.inputSpecRootDirectory = "src/test/resources/default"; @@ -225,17 +221,16 @@ public void testInputSpecRootDirectoryDoesNotRequireInputSpec() throws Exception // THEN /* Check the hash file was created */ - final Path hashFolder = folder.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); + final Path hashFolder = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); assertTrue(hashFolder.resolve("_merged_spec.yaml-executionId.sha256").toFile().exists()); } - protected CodeGenMojo loadMojo(File temporaryFolder, String projectRoot, String profile) throws Exception { + protected CodeGenMojo loadMojo(Path temporaryFolder, String projectRoot, String profile) throws Exception { return loadMojo(temporaryFolder, projectRoot, profile, "default"); } - protected CodeGenMojo loadMojo(File temporaryFolder, String projectRoot, String profile, String executionId) throws Exception { - File file = new File(projectRoot); - FileUtils.copyDirectory(file, temporaryFolder); + protected CodeGenMojo loadMojo(Path temporaryFolder, String projectRoot, String profile, String executionId) throws Exception { + FileUtils.copyDirectory(new File(projectRoot), temporaryFolder.toFile()); MavenProject project = readMavenProject(temporaryFolder, profile); MavenSession session = newMavenSession(project); MojoExecution execution = newMojoExecution("generate"); @@ -249,22 +244,27 @@ private MojoExecution copyWithExecutionId(String executionId, MojoExecution exec return executionWithId; } - protected MavenProject readMavenProject(File basedir, String profile) - throws Exception { - File pom = new File(basedir, "pom.xml"); - LocalRepository localRepo = new LocalRepository(new File(basedir, "local-repo")); + protected MavenProject readMavenProject(Path basedir, String profile) throws Exception { + Path pom = basedir.resolve("pom.xml"); + LocalRepository localRepo = new LocalRepository(basedir.resolve("local-repo").toFile()); DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); session.setLocalRepositoryManager(new SimpleLocalRepositoryManagerFactory().newInstance(session, localRepo)); MavenExecutionRequest request = new DefaultMavenExecutionRequest(); - request.setBaseDirectory(basedir); + request.setBaseDirectory(basedir.toFile()); if (profile != null) { request.addActiveProfile(profile); } ProjectBuildingRequest configuration = request.getProjectBuildingRequest(); configuration.setRepositorySession(session); configuration.setResolveDependencies(true); - MavenProject project = lookup(ProjectBuilder.class).build(pom, configuration).getProject(); + MavenProject project = lookup(ProjectBuilder.class).build(pom.toFile(), configuration).getProject(); assertNotNull(project); return project; } + + static private Path newTempFolder() throws IOException { + var tempDir = Files.createTempDirectory("test"); + tempDir.toFile().deleteOnExit(); + return tempDir; + } } From 2d4abcc73e6776c6eb4a14731d6886cc60d5a9f2 Mon Sep 17 00:00:00 2001 From: Philzen Date: Mon, 3 Jun 2024 04:27:15 +0200 Subject: [PATCH 2/7] Implement test that external $ref changes are reflected in checksum --- .../codegen/plugin/CodeGenMojoTest.java | 31 +++++- .../test/resources/issue-16489/petstore.yaml | 105 ++++++++++++++++++ .../src/test/resources/issue-16489/pom.xml | 61 ++++++++++ .../resources/issue-16489/schemas/Pet.yaml | 29 +++++ 4 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/petstore.yaml create mode 100644 modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/pom.xml create mode 100644 modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/schemas/Pet.yaml diff --git a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java index 0be3db3fa140..54ba6631dd64 100644 --- a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java +++ b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java @@ -35,12 +35,14 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public class CodeGenMojoTest extends BaseTestCase { @@ -225,6 +227,33 @@ public void testInputSpecRootDirectoryDoesNotRequireInputSpec() throws Exception assertTrue(hashFolder.resolve("_merged_spec.yaml-executionId.sha256").toFile().exists()); } + /** + * Regression test for #16489 + */ + public void test_skipIfSpecIsUnchanged_recognizesUpdatesInExternalReferencedFile() throws Exception { + + //GIVEN + final Path tempDir = newTempFolder(); + final Path generatedDir = tempDir.resolve("target/generated-sources/issue-16489"); + final Path hashFile = generatedDir.resolve(".openapi-generator/petstore.yaml-default.sha256"); + final CodeGenMojo mojo = loadMojo(tempDir, "src/test/resources/issue-16489", null); + mojo.execute(); // Perform an initial generation + var currentHash = Files.readString(hashFile); // read hash + FileUtils.deleteDirectory(generatedDir.resolve("src").toFile()); // Remove the generated source + Files.writeString( // change schema definition in external file + tempDir.resolve("schemas/Pet.yaml"),"\n wrapped: true", StandardOpenOption.APPEND + ); + + // WHEN + mojo.execute(); // Execute the mojo again + + // THEN + assertNotEquals( + Files.readString(hashFile), currentHash, "Checksum should not be the same after external file change" + ); + assertTrue("Src directory should have been regenerated", Files.exists(generatedDir.resolve("src"))); + } + protected CodeGenMojo loadMojo(Path temporaryFolder, String projectRoot, String profile) throws Exception { return loadMojo(temporaryFolder, projectRoot, profile, "default"); } @@ -263,7 +292,7 @@ protected MavenProject readMavenProject(Path basedir, String profile) throws Exc } static private Path newTempFolder() throws IOException { - var tempDir = Files.createTempDirectory("test"); + final Path tempDir = Files.createTempDirectory("test"); tempDir.toFile().deleteOnExit(); return tempDir; } diff --git a/modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/petstore.yaml b/modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/petstore.yaml new file mode 100644 index 000000000000..88b995327575 --- /dev/null +++ b/modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/petstore.yaml @@ -0,0 +1,105 @@ +openapi: 3.0.0 +servers: + - url: 'http://petstore.swagger.io/v2' +info: + description: Sample file with just two endpoints and one schema (defined in an external file $ref) + version: 1.0.0 + title: OpenAPI Petstore +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' +components: + requestBodies: + Pet: + content: + application/json: + schema: + $ref: 'schemas/Pet.yaml' + application/xml: + schema: + $ref: 'schemas/Pet.yaml' + description: Pet object that needs to be added to the store + required: true + schemas: + Pet: + $ref: 'schemas/Pet.yaml' diff --git a/modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/pom.xml b/modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/pom.xml new file mode 100644 index 000000000000..54724ef7fa6c --- /dev/null +++ b/modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/pom.xml @@ -0,0 +1,61 @@ + + + + 4.0.0 + common.maven + issue-16489 + jar + 1.0.0-SNAPSHOT + OpenAPI Generator Configuration Test + https://openapi-generator.tech/ + + + + org.openapitools + openapi-generator-maven-plugin + 7.6.0 + + java + + true + + native + ${basedir}/target/generated-sources/issue-16489 + hideGenerationTimestamp=true + ${basedir}/petstore.yaml + false + false + false + false + false + + true + + + + + generate + generate-sources + + generate + + + + + + + \ No newline at end of file diff --git a/modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/schemas/Pet.yaml b/modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/schemas/Pet.yaml new file mode 100644 index 000000000000..8f93f9075f33 --- /dev/null +++ b/modules/openapi-generator-maven-plugin/src/test/resources/issue-16489/schemas/Pet.yaml @@ -0,0 +1,29 @@ +title: a Pet +description: A pet for sale in the pet store +type: object +required: + - name + - photoUrls +properties: + id: + type: integer + format: int64 + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold +xml: + name: Pet \ No newline at end of file From 719c3bac30819e0bad58886321b46c70d7ace89c Mon Sep 17 00:00:00 2001 From: Philzen Date: Tue, 4 Jun 2024 06:35:29 +0200 Subject: [PATCH 3/7] Generate hash checksum from actual resolved spec instead of inputSpec file Otherwise regeneration will not happen when skipIfSpecIsUnchanged is enabled, although formally the spec content has changed. Fixes #4512 and #16489 --- .../codegen/plugin/CodeGenMojo.java | 130 +++++------------- 1 file changed, 34 insertions(+), 96 deletions(-) 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 2db32f005177..d42cdc2cba93 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 @@ -17,40 +17,14 @@ package org.openapitools.codegen.plugin; -import static org.apache.commons.lang3.StringUtils.isNotEmpty; -import static org.openapitools.codegen.config.CodegenConfiguratorUtils.*; - +import com.google.common.hash.Hashing; +import com.google.common.io.Files; +import io.swagger.parser.OpenAPIParser; import io.swagger.v3.core.util.Json; import io.swagger.v3.core.util.Yaml; import io.swagger.v3.parser.OpenAPIResolver; import io.swagger.v3.parser.OpenAPIV3Parser; -import io.swagger.v3.parser.core.models.AuthorizationValue; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLConnection; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import com.google.common.io.ByteSource; -import com.google.common.io.CharSource; import io.swagger.v3.parser.core.models.ParseOptions; -import io.swagger.v3.parser.util.ClasspathHelper; import lombok.Setter; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; @@ -59,30 +33,28 @@ import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.annotations.Component; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.plugins.annotations.*; import org.apache.maven.project.MavenProject; - import org.apache.maven.project.MavenProjectHelper; -import org.openapitools.codegen.CliOption; -import org.openapitools.codegen.ClientOptInput; -import org.openapitools.codegen.CodegenConfig; -import org.openapitools.codegen.CodegenConstants; -import org.openapitools.codegen.DefaultGenerator; -import org.openapitools.codegen.auth.AuthParser; +import org.openapitools.codegen.*; import org.openapitools.codegen.config.CodegenConfigurator; import org.openapitools.codegen.config.GlobalSettings; import org.openapitools.codegen.config.MergedSpecBuilder; -import org.sonatype.plexus.build.incremental.BuildContext; -import org.sonatype.plexus.build.incremental.DefaultBuildContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonatype.plexus.build.incremental.BuildContext; +import org.sonatype.plexus.build.incremental.DefaultBuildContext; -import com.google.common.hash.Hashing; -import com.google.common.io.Files; +import java.io.File; +import java.io.IOException; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.isNotEmpty; +import static org.openapitools.codegen.config.CodegenConfiguratorUtils.*; /** * Goal which generates client/server code from a OpenAPI json/yaml definition. @@ -609,19 +581,11 @@ public void execute() throws MojoExecutionException { } if (Boolean.TRUE.equals(skipIfSpecIsUnchanged)) { - File storedInputSpecHashFile = getHashFile(inputSpecFile); + final File storedInputSpecHashFile = getHashFile(inputSpecFile); if (storedInputSpecHashFile.exists()) { - String inputSpecHash = null; - try { - inputSpecHash = calculateInputSpecHash(inputSpecFile); - } catch (IOException ex) { - ex.printStackTrace(); - } - @SuppressWarnings("UnstableApiUsage") String storedInputSpecHash = Files.asCharSource(storedInputSpecHashFile, StandardCharsets.UTF_8).read(); - if (storedInputSpecHash.equals(inputSpecHash)) { - getLog().info( - "Code generation is skipped because input was unchanged"); + if (storedInputSpecHash.equals(calculateInputSpecHash(inputSpec))) { + getLog().info("Code generation is skipped because input was unchanged"); return; } } @@ -1010,8 +974,6 @@ public void execute() throws MojoExecutionException { // Store a checksum of the input spec File storedInputSpecHashFile = getHashFile(inputSpecFile); - String inputSpecHash = calculateInputSpecHash(inputSpecFile); - if (storedInputSpecHashFile.getParent() != null && !new File(storedInputSpecHashFile.getParent()).exists()) { File parent = new File(storedInputSpecHashFile.getParent()); if (!parent.mkdirs()) { @@ -1019,8 +981,8 @@ public void execute() throws MojoExecutionException { " to store the checksum of the input spec."); } } - Files.asCharSink(storedInputSpecHashFile, StandardCharsets.UTF_8).write(inputSpecHash); + Files.asCharSink(storedInputSpecHashFile, StandardCharsets.UTF_8).write(calculateInputSpecHash(inputSpec)); } catch (Exception e) { // Maven logs exceptions thrown by plugins only if invoked with -e // I find it annoying to jump through hoops to get basic diagnostic information, @@ -1035,45 +997,21 @@ public void execute() throws MojoExecutionException { } /** - * Calculate openapi specification file hash. If specification is hosted on remote resource it is downloaded first + * Calculate an SHA256 hash for the openapi specification. + * If the specification is hosted on a remote resource it is downloaded first. * - * @param inputSpecFile - Openapi specification input file to calculate its hash. - * Does not take into account if input spec is hosted on remote resource - * @return openapi specification file hash - * @throws IOException + * @param inputSpec - Openapi specification input file. Can denote a URL or file path. + * @return openapi specification hash */ - private String calculateInputSpecHash(File inputSpecFile) throws IOException { - - URL inputSpecRemoteUrl = inputSpecRemoteUrl(); - - File inputSpecTempFile = inputSpecFile; - - if (inputSpecRemoteUrl != null) { - inputSpecTempFile = java.nio.file.Files.createTempFile("openapi-spec", ".tmp").toFile(); - - URLConnection conn = inputSpecRemoteUrl.openConnection(); - if (isNotEmpty(auth)) { - List authList = AuthParser.parse(auth); - for (AuthorizationValue a : authList) { - conn.setRequestProperty(a.getKeyName(), a.getValue()); - } - } - 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 = - inputSpecTempFile.exists() - ? Files.asByteSource(inputSpecTempFile) - : CharSource.wrap(ClasspathHelper.loadFileFromClasspath(inputSpecTempFile.toString().replaceAll("\\\\","/"))) - .asByteSource(StandardCharsets.UTF_8); - - return inputSpecByteSource.hash(Hashing.sha256()).toString(); + private String calculateInputSpecHash(String inputSpec) { + final ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolve(true); + + final URL remoteUrl = inputSpecRemoteUrl(); + return Hashing.sha256().hashBytes( + new OpenAPIParser().readLocation(remoteUrl == null ? inputSpec : remoteUrl.toString(), null, parseOptions) + .getOpenAPI().toString().getBytes(StandardCharsets.UTF_8) + ).toString(); } /** From 350d2fa30203000b3ffc2ca0e9f1a18bf30713ea Mon Sep 17 00:00:00 2001 From: Philzen Date: Tue, 4 Jun 2024 13:16:21 +0200 Subject: [PATCH 4/7] Use try-with-resources to ensure stream is closed properly on exit --- .../codegen/plugin/CodeGenMojoTest.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java index 54ba6631dd64..fd2c1c5e7ff5 100644 --- a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java +++ b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -112,11 +113,9 @@ public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception assertTrue(hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256").toFile().exists()); /* Remove the generated source */ - Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src")) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - + try (Stream files = Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src"))) { + files.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } // WHEN /* Execute the mojo again */ @@ -151,13 +150,13 @@ public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exceptio /* Update the hash contents to be a different value, simulating a spec change */ Files.write( hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256"), - Arrays.asList("bd1bf4a953c858f9d47b67ed6029daacf1707e5cbd3d2e4b01383ba30363366f")); + Arrays.asList("bd1bf4a953c858f9d47b67ed6029daacf1707e5cbd3d2e4b01383ba30363366f") + ); /* Remove the generated source */ - Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src")) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); + try(Stream files = Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src"))) { + files.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } // WHEN From 6a7b9f860cbcc97a76ce454b0de56c6cd63ec873 Mon Sep 17 00:00:00 2001 From: Philzen Date: Tue, 4 Jun 2024 13:22:52 +0200 Subject: [PATCH 5/7] Fix deprecation warning on SimpleLocalRepositoryManagerFactory no-arg constructor --- .../org/openapitools/codegen/plugin/CodeGenMojoTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java index fd2c1c5e7ff5..e411e176a78f 100644 --- a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java +++ b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java @@ -28,6 +28,7 @@ import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.repository.internal.MavenRepositorySystemUtils; import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.internal.impl.DefaultLocalPathComposer; import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory; import org.eclipse.aether.repository.LocalRepository; @@ -276,7 +277,9 @@ protected MavenProject readMavenProject(Path basedir, String profile) throws Exc Path pom = basedir.resolve("pom.xml"); LocalRepository localRepo = new LocalRepository(basedir.resolve("local-repo").toFile()); DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); - session.setLocalRepositoryManager(new SimpleLocalRepositoryManagerFactory().newInstance(session, localRepo)); + session.setLocalRepositoryManager( + new SimpleLocalRepositoryManagerFactory(new DefaultLocalPathComposer()).newInstance(session, localRepo) + ); MavenExecutionRequest request = new DefaultMavenExecutionRequest(); request.setBaseDirectory(basedir.toFile()); if (profile != null) { From a1ef038de437bfd227cebbc7d348886623068520 Mon Sep 17 00:00:00 2001 From: Philzen Date: Tue, 4 Jun 2024 13:27:14 +0200 Subject: [PATCH 6/7] Apply minor code cleanup - use fluent setters where possible - remove undocumented @throws from JavaDoc - use List.of() instead of Arrays.asList() for single-element-collection (more memory efficient) - fix some grammar issues in comments and JavaDoc --- .../codegen/plugin/CodeGenMojoTest.java | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java index e411e176a78f..f26d43238b04 100644 --- a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java +++ b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java @@ -37,7 +37,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -95,14 +94,12 @@ public void testHashGenerationFileContainsExecutionId() throws Exception { } /** - * For a Pom file which refers to a input file which will be on the classpath, as opposed to a file path, + * For a Pom file which refers to an input file which will be on the classpath, as opposed to a file path, * test that the spec is not regenerated when the hash has not changed. - * - * @throws Exception */ public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception { //GIVEN - /* Setup the mojo */ + /* Set up the mojo */ final Path tempDir = newTempFolder(); final CodeGenMojo mojo = loadMojo(tempDir, "src/test/resources/classpath", null, "executionId"); @@ -115,6 +112,7 @@ public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception /* Remove the generated source */ try (Stream files = Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src"))) { + //noinspection ResultOfMethodCallIgnored files.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } @@ -130,14 +128,12 @@ public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception } /** - * For a Pom file which refers to a input file which will be on the classpath, as opposed to a file path, + * For a Pom file which refers to an input file which will be on the classpath, as opposed to a file path, * test that the generated source is regenerated when the hash has changed. - * - * @throws Exception */ public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exception { //GIVEN - /* Setup the mojo */ + /* Set up the mojo */ final Path tempDir = newTempFolder(); final CodeGenMojo mojo = loadMojo(tempDir, "src/test/resources/classpath", null, "executionId"); @@ -150,12 +146,13 @@ public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exceptio /* Update the hash contents to be a different value, simulating a spec change */ Files.write( - hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256"), - Arrays.asList("bd1bf4a953c858f9d47b67ed6029daacf1707e5cbd3d2e4b01383ba30363366f") + hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256"), + List.of("bd1bf4a953c858f9d47b67ed6029daacf1707e5cbd3d2e4b01383ba30363366f") ); /* Remove the generated source */ try(Stream files = Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src"))) { + //noinspection ResultOfMethodCallIgnored files.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } @@ -165,7 +162,7 @@ public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exceptio mojo.execute(); // THEN - /* Verify that the source directory has not been repopulated. If it has then we generated code again */ + /* Verify that the source directory has been repopulated. */ assertTrue("src directory should have been regenerated", tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src").toFile().exists()); @@ -274,21 +271,21 @@ private MojoExecution copyWithExecutionId(String executionId, MojoExecution exec } protected MavenProject readMavenProject(Path basedir, String profile) throws Exception { - Path pom = basedir.resolve("pom.xml"); LocalRepository localRepo = new LocalRepository(basedir.resolve("local-repo").toFile()); DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); session.setLocalRepositoryManager( new SimpleLocalRepositoryManagerFactory(new DefaultLocalPathComposer()).newInstance(session, localRepo) ); - MavenExecutionRequest request = new DefaultMavenExecutionRequest(); - request.setBaseDirectory(basedir.toFile()); + MavenExecutionRequest request = new DefaultMavenExecutionRequest().setBaseDirectory(basedir.toFile()); if (profile != null) { request.addActiveProfile(profile); } - ProjectBuildingRequest configuration = request.getProjectBuildingRequest(); - configuration.setRepositorySession(session); - configuration.setResolveDependencies(true); - MavenProject project = lookup(ProjectBuilder.class).build(pom.toFile(), configuration).getProject(); + ProjectBuildingRequest configuration = request.getProjectBuildingRequest() + .setRepositorySession(session) + .setResolveDependencies(true); + MavenProject project = lookup(ProjectBuilder.class) + .build(basedir.resolve("pom.xml").toFile(), configuration) + .getProject(); assertNotNull(project); return project; } From 3eb09f4f631a41f481c642630da92d1bbd297139 Mon Sep 17 00:00:00 2001 From: Philzen Date: Tue, 4 Jun 2024 13:52:30 +0200 Subject: [PATCH 7/7] Use non-blocking java.nio API for file existence checks --- .../codegen/plugin/CodeGenMojoTest.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java index f26d43238b04..42db5395c259 100644 --- a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java +++ b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java @@ -89,8 +89,9 @@ public void testHashGenerationFileContainsExecutionId() throws Exception { mojo.execute(); // THEN - Path hashFolder = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); - assertTrue(hashFolder.resolve("petstore.yaml-executionId.sha256").toFile().exists()); + assertTrue(Files.exists(tempDir.resolve( + "target/generated-sources/common-maven/remote-openapi/.openapi-generator/petstore.yaml-executionId.sha256" + ))); } /** @@ -107,11 +108,13 @@ public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception mojo.execute(); /* Check the hash file was created */ - final Path hashFolder = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); - assertTrue(hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256").toFile().exists()); + final Path generatedDir = tempDir.resolve("target/generated-sources/common-maven/remote-openapi"); + assertTrue(Files.exists( + generatedDir.resolve(".openapi-generator/petstore-on-classpath.yaml-executionId.sha256") + )); /* Remove the generated source */ - try (Stream files = Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src"))) { + try (Stream files = Files.walk(generatedDir.resolve("src"))) { //noinspection ResultOfMethodCallIgnored files.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } @@ -122,9 +125,7 @@ public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception // THEN /* Verify that the source directory has not been repopulated. If it has then we generated code again */ - assertFalse("src directory should not have been regenerated", - tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src").toFile().exists()); - + assertFalse("src directory should not have been regenerated", Files.exists(generatedDir.resolve("src"))); } /** @@ -141,17 +142,17 @@ public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exceptio mojo.execute(); /* Check the hash file was created, proving a generation occurred */ - final Path hashFolder = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); - assertTrue(hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256").toFile().exists()); + final Path generatedDir = tempDir.resolve("target/generated-sources/common-maven/remote-openapi"); + assertTrue(Files.exists(generatedDir.resolve(".openapi-generator/petstore-on-classpath.yaml-executionId.sha256"))); /* Update the hash contents to be a different value, simulating a spec change */ Files.write( - hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256"), + generatedDir.resolve(".openapi-generator/petstore-on-classpath.yaml-executionId.sha256"), List.of("bd1bf4a953c858f9d47b67ed6029daacf1707e5cbd3d2e4b01383ba30363366f") ); /* Remove the generated source */ - try(Stream files = Files.walk(tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src"))) { + try(Stream files = Files.walk(generatedDir.resolve("src"))) { //noinspection ResultOfMethodCallIgnored files.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } @@ -163,9 +164,7 @@ public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exceptio // THEN /* Verify that the source directory has been repopulated. */ - assertTrue("src directory should have been regenerated", - tempDir.resolve("target/generated-sources/common-maven/remote-openapi/src").toFile().exists()); - + assertTrue("src directory should have been regenerated", Files.exists(generatedDir.resolve("src"))); } public void testCollapsedSpecProduced() throws Exception { @@ -177,8 +176,9 @@ public void testCollapsedSpecProduced() throws Exception { mojo.execute(); // THEN - File collapseSpecFile = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/petstore-full-spec.yaml").toFile(); - assertTrue(collapseSpecFile.exists()); + assertTrue(Files.exists( + tempDir.resolve("target/generated-sources/common-maven/remote-openapi/petstore-full-spec.yaml") + )); } public void testCollapsedSpecAddedToArtifacts() throws Exception { @@ -221,7 +221,7 @@ public void testInputSpecRootDirectoryDoesNotRequireInputSpec() throws Exception // THEN /* Check the hash file was created */ final Path hashFolder = tempDir.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); - assertTrue(hashFolder.resolve("_merged_spec.yaml-executionId.sha256").toFile().exists()); + assertTrue(Files.exists(hashFolder.resolve("_merged_spec.yaml-executionId.sha256"))); } /**