diff --git a/tools/gradle-plugin/build.gradle b/tools/gradle-plugin/build.gradle index 0a56824e6..ee16013c3 100644 --- a/tools/gradle-plugin/build.gradle +++ b/tools/gradle-plugin/build.gradle @@ -6,14 +6,10 @@ plugins { group = "io.smallrye" -if (JavaVersion.current().isJava9Compatible()) { - compileJava.options.compilerArgs.addAll(['--release', '8']) -} - compileJava { options.encoding = 'UTF-8' - sourceCompatibility = '1.8' - targetCompatibility = '1.8' + sourceCompatibility = '11' + targetCompatibility = '11' } compileTestJava { diff --git a/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/Configs.java b/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/Configs.java index 403dc4989..eea211973 100644 --- a/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/Configs.java +++ b/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/Configs.java @@ -64,6 +64,7 @@ class Configs implements SmallryeOpenApiProperties { final MapProperty scanResourceClasses; final Property outputFileTypeFilter; final Property encoding; + final ListProperty includeStandardJavaModules; Configs(ObjectFactory objects) { configProperties = objects.fileProperty(); @@ -99,6 +100,7 @@ class Configs implements SmallryeOpenApiProperties { scanResourceClasses = objects.mapProperty(String.class, String.class); outputFileTypeFilter = objects.property(String.class).convention("ALL"); encoding = objects.property(String.class).convention(StandardCharsets.UTF_8.name()); + includeStandardJavaModules = objects.listProperty(String.class); } Configs(ObjectFactory objects, SmallryeOpenApiExtension ext) { @@ -137,6 +139,7 @@ class Configs implements SmallryeOpenApiProperties { scanResourceClasses = objects.mapProperty(String.class, String.class).convention(ext.getScanResourceClasses()); outputFileTypeFilter = objects.property(String.class).convention(ext.getOutputFileTypeFilter()); encoding = objects.property(String.class).convention(ext.getEncoding()); + includeStandardJavaModules = objects.listProperty(String.class).convention(ext.getIncludeStandardJavaModules()); } Config asMicroprofileConfig() { @@ -349,4 +352,7 @@ public Property getEncoding() { return encoding; } + public ListProperty getIncludeStandardJavaModules() { + return includeStandardJavaModules; + } } diff --git a/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/GradleDependencyIndexCreator.java b/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/GradleDependencyIndexCreator.java index 21774a8d7..b000675c8 100644 --- a/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/GradleDependencyIndexCreator.java +++ b/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/GradleDependencyIndexCreator.java @@ -2,12 +2,17 @@ import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.time.LocalDateTime; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -32,12 +37,24 @@ public GradleDependencyIndexCreator(Logger logger) { this.logger = logger; } - IndexView createIndex(Set dependencies, FileCollection classesDirs) + IndexView createIndex(SmallryeOpenApiTask task) throws IOException { + Set dependencies = task.getScanDependenciesDisable().get().booleanValue() + ? Collections.emptySet() + : task.getClasspath().getFiles(); + FileCollection classesDirs = task.getClassesDirs(); + List> indexDurations = new ArrayList<>(); List indexes = new ArrayList<>(); + List includeStandardJavaModules = task.getIncludeStandardJavaModules().getOrElse(Collections.emptyList()); + logger.info("includeStandardJavaModules: " + includeStandardJavaModules); + + for (String moduleName : includeStandardJavaModules) { + indexes.add(indexJdkModule(moduleName)); + } + for (File f : classesDirs.getFiles()) { indexes.add(indexModuleClasses(f)); } @@ -65,6 +82,37 @@ IndexView createIndex(Set dependencies, FileCollection classesDirs) return CompositeIndex.create(indexes); } + private Index indexJdkModule(String moduleName) throws IOException { + Indexer indexer = new Indexer(); + FileSystem jrt = FileSystems.getFileSystem(URI.create("jrt:/")); + + for (Path root : jrt.getRootDirectories()) { + try (var walker = Files.walk(root)) { + walker + .filter(path -> path.startsWith("/modules/" + moduleName)) + .filter(path -> path.getFileName().toString().endsWith(".class")) + .map(path -> { + try { + return Files.newInputStream(path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }) + .forEach(stream -> { + try { + indexer.index(stream); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + return indexer.complete(); + } + private Index index(File artifact) throws IOException { Result result = JarIndexer.createJarIndex(artifact, new Indexer(), false, false, false); diff --git a/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiProperties.java b/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiProperties.java index 7e6637c44..b81a84a14 100644 --- a/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiProperties.java +++ b/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiProperties.java @@ -151,4 +151,9 @@ public interface SmallryeOpenApiProperties { * Output encoding for openapi document. */ Property getEncoding(); + + /** + * List of standard Java modules that should be made available to annotation scanning for introspection. + */ + ListProperty getIncludeStandardJavaModules(); } diff --git a/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiTask.java b/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiTask.java index e982d36a6..8fe1dead2 100644 --- a/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiTask.java +++ b/tools/gradle-plugin/src/main/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiTask.java @@ -14,8 +14,6 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Collection; -import java.util.Collections; -import java.util.Set; import java.util.stream.Stream; import javax.inject.Inject; @@ -33,6 +31,7 @@ import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.PathSensitive; @@ -93,13 +92,7 @@ public SmallryeOpenApiTask( public void generate() { try { clearOutput(); - - Set dependencies = properties.scanDependenciesDisable.get().booleanValue() - ? Collections.emptySet() - : classpath.getFiles(); - - IndexView index = new GradleDependencyIndexCreator(getLogger()).createIndex(dependencies, - classesDirs); + IndexView index = new GradleDependencyIndexCreator(getLogger()).createIndex(this); SmallRyeOpenAPI openAPI = generateOpenAPI(index, resourcesSrcDirs); write(openAPI); } catch (Exception ex) { @@ -108,6 +101,16 @@ public void generate() { } } + @Internal + FileCollection getClasspath() { + return this.classpath; + } + + @Internal + FileCollection getClassesDirs() { + return this.classesDirs; + } + private SmallRyeOpenAPI generateOpenAPI(IndexView index, FileCollection resourcesSrcDirs) { return SmallRyeOpenAPI.builder() .withConfig(properties.asMicroprofileConfig()) @@ -460,4 +463,11 @@ public Property getOutputFileTypeFilter() { public Property getEncoding() { return properties.encoding; } + + @Input + @Optional + @Override + public ListProperty getIncludeStandardJavaModules() { + return properties.includeStandardJavaModules; + } } diff --git a/tools/gradle-plugin/src/test/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiPluginTest.java b/tools/gradle-plugin/src/test/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiPluginTest.java index 95b5b5a60..0e553305b 100644 --- a/tools/gradle-plugin/src/test/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiPluginTest.java +++ b/tools/gradle-plugin/src/test/java/io/smallrye/openapi/gradleplugin/SmallryeOpenApiPluginTest.java @@ -21,10 +21,13 @@ import org.gradle.testfixtures.ProjectBuilder; import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.TextNode; import io.smallrye.openapi.api.OpenApiConfig.OperationIdStrategy; @@ -138,7 +141,7 @@ void taskPropertiesInheritance() { } @Test - void simpleProject(@TempDir Path buildDir) throws Exception { + void simpleProject(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path buildDir) throws Exception { // "Simple" Gradle project smokeProject(buildDir, false, SmallryeOpenApiPlugin.TASK_NAME); } @@ -156,13 +159,13 @@ void simpleProjectWithJustJsonOutput(@TempDir Path buildDir) throws Exception { } @Test - void quarkusProjectGenApiOnly(@TempDir Path buildDir) throws Exception { + void quarkusProjectGenApiOnly(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path buildDir) throws Exception { // Quarkus Gradle project, just call the generateOpenApiSpec task smokeProject(buildDir, true, SmallryeOpenApiPlugin.TASK_NAME); } @Test - void quarkusProject(@TempDir Path buildDir) throws Exception { + void quarkusProject(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path buildDir) throws Exception { // Quarkus Gradle project, perform a "full Quarkus build" smokeProject(buildDir, true, "quarkusBuild"); } @@ -190,8 +193,8 @@ void smokeProject(Path buildDir, boolean withQuarkus, String taskName, String ou "}", "", "dependencies {", - " implementation(\"javax.ws.rs:javax.ws.rs-api:2.1.1\")", - " implementation(\"org.eclipse.microprofile.openapi:microprofile-openapi-api:3.0\")", + " implementation(\"jakarta.ws.rs:jakarta.ws.rs-api:3.1.0\")", + " implementation(\"org.eclipse.microprofile.openapi:microprofile-openapi-api:4.0.1\")", "}", "", "smallryeOpenApi {", @@ -210,6 +213,7 @@ void smokeProject(Path buildDir, boolean withQuarkus, String taskName, String ou " operationIdStrategy.set(OperationIdStrategy.METHOD)", " filter.set(\"testcases.CustomOASFilter\")", " outputFileTypeFilter.set(\"" + outputFileTypeFilter + "\")", + " includeStandardJavaModules.set([ \"java.base\" ])", "}")); Path javaDir = Paths.get("src/main/java/testcases"); @@ -218,12 +222,13 @@ void smokeProject(Path buildDir, boolean withQuarkus, String taskName, String ou Files.write(buildDir.resolve(javaDir.resolve("DummyJaxRs.java")), asList("package testcases;", "", - "import javax.ws.rs.GET;", - "import javax.ws.rs.Path;", - "import javax.ws.rs.Produces;", - "import javax.ws.rs.core.MediaType;", + "import jakarta.ws.rs.GET;", + "import jakarta.ws.rs.Path;", + "import jakarta.ws.rs.Produces;", + "import jakarta.ws.rs.core.MediaType;", "import org.eclipse.microprofile.openapi.annotations.Operation;", "import org.eclipse.microprofile.openapi.annotations.media.Content;", + "import org.eclipse.microprofile.openapi.annotations.media.Schema;", "import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;", "import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;", "", @@ -235,10 +240,11 @@ void smokeProject(Path buildDir, boolean withQuarkus, String taskName, String ou " @APIResponses({", " @APIResponse(", " description = \"Dummy get thing.\",", - " content = @Content(mediaType = \"application/text\")", + " content = @Content(mediaType = \"application/text\",", + " schema = @Schema(implementation = java.util.concurrent.TimeUnit.class))", " )})", - " public String dummyThing() {", - " return \"foo\";", + " public java.util.concurrent.TimeUnit dummyThing() {", + " return java.util.concurrent.TimeUnit.HOURS;", " }", "}")); @@ -309,6 +315,9 @@ private static void checkGeneratedFiles(Path buildDir, String expectedOutputFile JsonNode paths = root.get("paths"); assertThat(paths.get("/mypath").get("get").get("operationId").asText()).isEqualTo("dummyThing"); + + JsonNode schemas = root.get("components").get("schemas"); + assertThat(((ArrayNode) schemas.get("TimeUnit").get("enum"))).contains(new TextNode("HOURS")); } if ("YAML".equals(expectedOutputFileType)) { diff --git a/tools/maven-plugin/src/main/java/io/smallrye/openapi/mavenplugin/GenerateSchemaMojo.java b/tools/maven-plugin/src/main/java/io/smallrye/openapi/mavenplugin/GenerateSchemaMojo.java index c8de6b7ba..664f58544 100644 --- a/tools/maven-plugin/src/main/java/io/smallrye/openapi/mavenplugin/GenerateSchemaMojo.java +++ b/tools/maven-plugin/src/main/java/io/smallrye/openapi/mavenplugin/GenerateSchemaMojo.java @@ -75,6 +75,13 @@ public class GenerateSchemaMojo extends AbstractMojo { @Parameter(defaultValue = "jar", property = "includeDependenciesTypes") private List includeDependenciesTypes; + /** + * List of standard Java modules that should be made available to annotation + * scanning for introspection. + */ + @Parameter(property = "includeStandardJavaModules") + private List includeStandardJavaModules; + /** * Skip execution of the plugin. */ @@ -279,7 +286,7 @@ public void execute() throws MojoExecutionException { if (!skip) { try { IndexView index = mavenDependencyIndexCreator.createIndex(mavenProject, scanDependenciesDisable, - includeDependenciesScopes, includeDependenciesTypes); + includeDependenciesScopes, includeDependenciesTypes, includeStandardJavaModules); SmallRyeOpenAPI openAPI = generateOpenAPI(index); write(openAPI); } catch (Exception ex) { diff --git a/tools/maven-plugin/src/main/java/io/smallrye/openapi/mavenplugin/MavenDependencyIndexCreator.java b/tools/maven-plugin/src/main/java/io/smallrye/openapi/mavenplugin/MavenDependencyIndexCreator.java index e2b2be402..d12c627aa 100644 --- a/tools/maven-plugin/src/main/java/io/smallrye/openapi/mavenplugin/MavenDependencyIndexCreator.java +++ b/tools/maven-plugin/src/main/java/io/smallrye/openapi/mavenplugin/MavenDependencyIndexCreator.java @@ -2,6 +2,10 @@ import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; @@ -72,10 +76,13 @@ public MavenDependencyIndexCreator() { ignoredArtifacts.add("org.apache.httpcomponents"); } - public IndexView createIndex(MavenProject mavenProject, boolean scanDependenciesDisable, - List includeDependenciesScopes, List includeDependenciesTypes) { + public IndexView createIndex(MavenProject mavenProject, + boolean scanDependenciesDisable, + List includeDependenciesScopes, + List includeDependenciesTypes, + List includeStandardJavaModules) { - List> indexDurations = new ArrayList<>(); + List> indexDurations = new ArrayList<>(); List artifacts = new ArrayList<>(); String buildOutput = mavenProject.getBuild().getOutputDirectory(); @@ -96,6 +103,16 @@ public IndexView createIndex(MavenProject mavenProject, boolean scanDependencies } List indexes = new ArrayList<>(); + + if (includeStandardJavaModules != null) { + for (String moduleName : includeStandardJavaModules) { + LocalDateTime start = LocalDateTime.now(); + indexes.add(indexJdkModule(moduleName)); + Duration duration = Duration.between(start, LocalDateTime.now()); + indexDurations.add(new AbstractMap.SimpleEntry<>("module:" + moduleName, duration)); + } + } + for (File artifact : artifacts) { try { if (artifact.isDirectory()) { @@ -124,7 +141,7 @@ public IndexView createIndex(MavenProject mavenProject, boolean scanDependencies return CompositeIndex.create(indexes); } - private void printIndexDurations(List> indexDurations) { + private void printIndexDurations(List> indexDurations) { if (logger.isDebugEnabled()) { logger.debug("Indexed directories/artifacts for annotation scanning:"); indexDurations.forEach(e -> logger.debug(" " + e.getKey() + " (index time " + e.getValue() + ")")); @@ -144,7 +161,7 @@ private boolean isIgnored(Artifact artifact, List includeDependenciesSco || ignoredArtifacts.contains(artifact.getGroupId() + ":" + artifact.getArtifactId()); } - private IndexView timeAndCache(List> indexDurations, File artifact, + private IndexView timeAndCache(List> indexDurations, File artifact, Callable callable) throws ExecutionException { LocalDateTime start = LocalDateTime.now(); IndexView result = indexCache.get(artifact.getAbsolutePath(), callable); @@ -156,6 +173,37 @@ private IndexView timeAndCache(List> indexDurations, F return result; } + private Index indexJdkModule(String moduleName) { + Indexer indexer = new Indexer(); + FileSystem jrt = FileSystems.getFileSystem(URI.create("jrt:/")); + + for (Path root : jrt.getRootDirectories()) { + try (var walker = Files.walk(root)) { + walker + .filter(path -> path.startsWith("/modules/" + moduleName)) + .filter(path -> path.getFileName().toString().endsWith(".class")) + .map(path -> { + try { + return Files.newInputStream(path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }) + .forEach(stream -> { + try { + indexer.index(stream); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + return indexer.complete(); + } + // index the classes of this Maven module private Index indexModuleClasses(File artifact) throws IOException { // Check first if the classes directory exists, before attempting to create an index for the classes diff --git a/tools/maven-plugin/src/test/java/io/smallrye/openapi/mavenplugin/BasicIT.java b/tools/maven-plugin/src/test/java/io/smallrye/openapi/mavenplugin/BasicIT.java index 5e53ebfeb..0717d0e36 100644 --- a/tools/maven-plugin/src/test/java/io/smallrye/openapi/mavenplugin/BasicIT.java +++ b/tools/maven-plugin/src/test/java/io/smallrye/openapi/mavenplugin/BasicIT.java @@ -55,6 +55,7 @@ void basic_info(MavenExecutionResult result) throws IOException { assertTrue(servers.contains(properties.get("server2").toString())); assertTrue(schema.getPaths().hasPathItem("/hello")); + assertTrue(schema.getComponents().getSchemas().get("TimeUnit").getEnumeration().contains("HOURS")); }; testSchema(result, schemaConsumer); diff --git a/tools/maven-plugin/src/test/resources-its/io/smallrye/openapi/mavenplugin/BasicIT/basic_info/pom.xml b/tools/maven-plugin/src/test/resources-its/io/smallrye/openapi/mavenplugin/BasicIT/basic_info/pom.xml index 782576922..9dcc7a527 100644 --- a/tools/maven-plugin/src/test/resources-its/io/smallrye/openapi/mavenplugin/BasicIT/basic_info/pom.xml +++ b/tools/maven-plugin/src/test/resources-its/io/smallrye/openapi/mavenplugin/BasicIT/basic_info/pom.xml @@ -68,6 +68,9 @@ UTF-8 + + java.base + diff --git a/tools/maven-plugin/src/test/resources-its/io/smallrye/openapi/mavenplugin/BasicIT/basic_info/src/main/java/io/smallrye/example/ExampleResource.java b/tools/maven-plugin/src/test/resources-its/io/smallrye/openapi/mavenplugin/BasicIT/basic_info/src/main/java/io/smallrye/example/ExampleResource.java index a8516fef4..5c784047a 100644 --- a/tools/maven-plugin/src/test/resources-its/io/smallrye/openapi/mavenplugin/BasicIT/basic_info/src/main/java/io/smallrye/example/ExampleResource.java +++ b/tools/maven-plugin/src/test/resources-its/io/smallrye/openapi/mavenplugin/BasicIT/basic_info/src/main/java/io/smallrye/example/ExampleResource.java @@ -1,5 +1,7 @@ package io.smallrye.example; +import java.util.concurrent.TimeUnit; + import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; @@ -13,4 +15,9 @@ public String sayHello(@QueryParam("greeting") String greeting) { return greeting + " world!"; } + @GET + @Path("/timeunit") + public TimeUnit getTimeUnit() { + return TimeUnit.HOURS; + } }