Skip to content

Commit 880df7a

Browse files
authored
[maven-plugin] Generate hash from actual resolved spec rather than inputSpec file (OpenAPITools#18849)
* Ensure temp directories are deleted after test execution * Implement test that external $ref changes are reflected in checksum * 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 OpenAPITools#4512 and OpenAPITools#16489 * Use try-with-resources to ensure stream is closed properly on exit * Fix deprecation warning on SimpleLocalRepositoryManagerFactory no-arg constructor * 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 * Use non-blocking java.nio API for file existence checks
1 parent 446e168 commit 880df7a

5 files changed

Lines changed: 337 additions & 176 deletions

File tree

modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java

Lines changed: 34 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,14 @@
1717

1818
package org.openapitools.codegen.plugin;
1919

20-
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
21-
import static org.openapitools.codegen.config.CodegenConfiguratorUtils.*;
22-
20+
import com.google.common.hash.Hashing;
21+
import com.google.common.io.Files;
22+
import io.swagger.parser.OpenAPIParser;
2323
import io.swagger.v3.core.util.Json;
2424
import io.swagger.v3.core.util.Yaml;
2525
import io.swagger.v3.parser.OpenAPIResolver;
2626
import io.swagger.v3.parser.OpenAPIV3Parser;
27-
import io.swagger.v3.parser.core.models.AuthorizationValue;
28-
import java.io.File;
29-
import java.io.FileOutputStream;
30-
import java.io.IOException;
31-
import java.net.MalformedURLException;
32-
import java.net.URI;
33-
import java.net.URISyntaxException;
34-
import java.net.URL;
35-
import java.net.URLClassLoader;
36-
import java.net.URLConnection;
37-
import java.nio.channels.Channels;
38-
import java.nio.channels.FileChannel;
39-
import java.nio.channels.ReadableByteChannel;
40-
import java.nio.charset.StandardCharsets;
41-
import java.nio.file.Path;
42-
import java.text.MessageFormat;
43-
import java.util.ArrayList;
44-
import java.util.HashMap;
45-
import java.util.List;
46-
import java.util.Locale;
47-
import java.util.Map;
48-
import java.util.Set;
49-
50-
import com.google.common.io.ByteSource;
51-
import com.google.common.io.CharSource;
5227
import io.swagger.v3.parser.core.models.ParseOptions;
53-
import io.swagger.v3.parser.util.ClasspathHelper;
5428
import lombok.Setter;
5529
import org.apache.commons.io.FileUtils;
5630
import org.apache.commons.io.FilenameUtils;
@@ -59,30 +33,28 @@
5933
import org.apache.maven.plugin.AbstractMojo;
6034
import org.apache.maven.plugin.MojoExecution;
6135
import org.apache.maven.plugin.MojoExecutionException;
62-
import org.apache.maven.plugins.annotations.Component;
63-
import org.apache.maven.plugins.annotations.LifecyclePhase;
64-
import org.apache.maven.plugins.annotations.Mojo;
65-
import org.apache.maven.plugins.annotations.Parameter;
66-
import org.apache.maven.plugins.annotations.ResolutionScope;
36+
import org.apache.maven.plugins.annotations.*;
6737
import org.apache.maven.project.MavenProject;
68-
6938
import org.apache.maven.project.MavenProjectHelper;
70-
import org.openapitools.codegen.CliOption;
71-
import org.openapitools.codegen.ClientOptInput;
72-
import org.openapitools.codegen.CodegenConfig;
73-
import org.openapitools.codegen.CodegenConstants;
74-
import org.openapitools.codegen.DefaultGenerator;
75-
import org.openapitools.codegen.auth.AuthParser;
39+
import org.openapitools.codegen.*;
7640
import org.openapitools.codegen.config.CodegenConfigurator;
7741
import org.openapitools.codegen.config.GlobalSettings;
7842
import org.openapitools.codegen.config.MergedSpecBuilder;
79-
import org.sonatype.plexus.build.incremental.BuildContext;
80-
import org.sonatype.plexus.build.incremental.DefaultBuildContext;
8143
import org.slf4j.Logger;
8244
import org.slf4j.LoggerFactory;
45+
import org.sonatype.plexus.build.incremental.BuildContext;
46+
import org.sonatype.plexus.build.incremental.DefaultBuildContext;
8347

84-
import com.google.common.hash.Hashing;
85-
import com.google.common.io.Files;
48+
import java.io.File;
49+
import java.io.IOException;
50+
import java.net.*;
51+
import java.nio.charset.StandardCharsets;
52+
import java.nio.file.Path;
53+
import java.text.MessageFormat;
54+
import java.util.*;
55+
56+
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
57+
import static org.openapitools.codegen.config.CodegenConfiguratorUtils.*;
8658

8759
/**
8860
* Goal which generates client/server code from a OpenAPI json/yaml definition.
@@ -609,19 +581,11 @@ public void execute() throws MojoExecutionException {
609581
}
610582

611583
if (Boolean.TRUE.equals(skipIfSpecIsUnchanged)) {
612-
File storedInputSpecHashFile = getHashFile(inputSpecFile);
584+
final File storedInputSpecHashFile = getHashFile(inputSpecFile);
613585
if (storedInputSpecHashFile.exists()) {
614-
String inputSpecHash = null;
615-
try {
616-
inputSpecHash = calculateInputSpecHash(inputSpecFile);
617-
} catch (IOException ex) {
618-
ex.printStackTrace();
619-
}
620-
@SuppressWarnings("UnstableApiUsage")
621586
String storedInputSpecHash = Files.asCharSource(storedInputSpecHashFile, StandardCharsets.UTF_8).read();
622-
if (storedInputSpecHash.equals(inputSpecHash)) {
623-
getLog().info(
624-
"Code generation is skipped because input was unchanged");
587+
if (storedInputSpecHash.equals(calculateInputSpecHash(inputSpec))) {
588+
getLog().info("Code generation is skipped because input was unchanged");
625589
return;
626590
}
627591
}
@@ -1010,17 +974,15 @@ public void execute() throws MojoExecutionException {
1010974

1011975
// Store a checksum of the input spec
1012976
File storedInputSpecHashFile = getHashFile(inputSpecFile);
1013-
String inputSpecHash = calculateInputSpecHash(inputSpecFile);
1014-
1015977
if (storedInputSpecHashFile.getParent() != null && !new File(storedInputSpecHashFile.getParent()).exists()) {
1016978
File parent = new File(storedInputSpecHashFile.getParent());
1017979
if (!parent.mkdirs()) {
1018980
throw new RuntimeException("Failed to create the folder " + parent.getAbsolutePath() +
1019981
" to store the checksum of the input spec.");
1020982
}
1021983
}
1022-
Files.asCharSink(storedInputSpecHashFile, StandardCharsets.UTF_8).write(inputSpecHash);
1023984

985+
Files.asCharSink(storedInputSpecHashFile, StandardCharsets.UTF_8).write(calculateInputSpecHash(inputSpec));
1024986
} catch (Exception e) {
1025987
// Maven logs exceptions thrown by plugins only if invoked with -e
1026988
// I find it annoying to jump through hoops to get basic diagnostic information,
@@ -1035,45 +997,21 @@ public void execute() throws MojoExecutionException {
1035997
}
1036998

1037999
/**
1038-
* Calculate openapi specification file hash. If specification is hosted on remote resource it is downloaded first
1000+
* Calculate an SHA256 hash for the openapi specification.
1001+
* If the specification is hosted on a remote resource it is downloaded first.
10391002
*
1040-
* @param inputSpecFile - Openapi specification input file to calculate its hash.
1041-
* Does not take into account if input spec is hosted on remote resource
1042-
* @return openapi specification file hash
1043-
* @throws IOException
1003+
* @param inputSpec - Openapi specification input file. Can denote a URL or file path.
1004+
* @return openapi specification hash
10441005
*/
1045-
private String calculateInputSpecHash(File inputSpecFile) throws IOException {
1046-
1047-
URL inputSpecRemoteUrl = inputSpecRemoteUrl();
1048-
1049-
File inputSpecTempFile = inputSpecFile;
1050-
1051-
if (inputSpecRemoteUrl != null) {
1052-
inputSpecTempFile = java.nio.file.Files.createTempFile("openapi-spec", ".tmp").toFile();
1053-
1054-
URLConnection conn = inputSpecRemoteUrl.openConnection();
1055-
if (isNotEmpty(auth)) {
1056-
List<AuthorizationValue> authList = AuthParser.parse(auth);
1057-
for (AuthorizationValue a : authList) {
1058-
conn.setRequestProperty(a.getKeyName(), a.getValue());
1059-
}
1060-
}
1061-
try (ReadableByteChannel readableByteChannel = Channels.newChannel(conn.getInputStream())) {
1062-
FileChannel fileChannel;
1063-
try (FileOutputStream fileOutputStream = new FileOutputStream(inputSpecTempFile)) {
1064-
fileChannel = fileOutputStream.getChannel();
1065-
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
1066-
}
1067-
}
1068-
}
1069-
1070-
ByteSource inputSpecByteSource =
1071-
inputSpecTempFile.exists()
1072-
? Files.asByteSource(inputSpecTempFile)
1073-
: CharSource.wrap(ClasspathHelper.loadFileFromClasspath(inputSpecTempFile.toString().replaceAll("\\\\","/")))
1074-
.asByteSource(StandardCharsets.UTF_8);
1075-
1076-
return inputSpecByteSource.hash(Hashing.sha256()).toString();
1006+
private String calculateInputSpecHash(String inputSpec) {
1007+
final ParseOptions parseOptions = new ParseOptions();
1008+
parseOptions.setResolve(true);
1009+
1010+
final URL remoteUrl = inputSpecRemoteUrl();
1011+
return Hashing.sha256().hashBytes(
1012+
new OpenAPIParser().readLocation(remoteUrl == null ? inputSpec : remoteUrl.toString(), null, parseOptions)
1013+
.getOpenAPI().toString().getBytes(StandardCharsets.UTF_8)
1014+
).toString();
10771015
}
10781016

10791017
/**

0 commit comments

Comments
 (0)