From 613f27be85deda1cc3eb49300ca5fdfa139980b7 Mon Sep 17 00:00:00 2001 From: patpatpat123 <43899031+patpatpat123@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:13:11 +0800 Subject: [PATCH 1/3] Update pom.xml --- pom.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pom.xml b/pom.xml index 1ee1f29..6e5c82d 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,22 @@ 3.6.4 provided + + org.apache.maven + maven-artifact + 3.8.4 + provided + + + org.apache.maven.reporting + maven-reporting-api + 3.1.0 + + + org.apache.maven.reporting + maven-reporting-impl + 3.1.0 + From bf1a0948170a8b94f7c0130f88abf9b4ef2701bd Mon Sep 17 00:00:00 2001 From: patpatpat123 <43899031+patpatpat123@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:13:32 +0800 Subject: [PATCH 2/3] Update README.md --- README.md | 190 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 128 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 6f7fbfa..a984a94 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ ## **Introduction to springdoc-openapi-maven-plugin** The aim of springdoc-openapi-maven-plugin is to generate json and yaml OpenAPI description during runtime. If you want to get swagger definitions properly, the application should completely running as locally. -The plugin works during integration-tests phase, and generate the OpenAPI description. -The plugin works in conjunction with spring-boot-maven plugin. +The plugin works during integration-tests phase, and generate the OpenAPI description. +The plugin works in conjunction with spring-boot-maven plugin. You can test it during the integration tests phase using the maven command: @@ -16,54 +16,54 @@ In order to use this functionality, you need to add the plugin declaration on th ```xml - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot-maven-plugin.version} - - -Dspring.application.admin.enabled=true - - - - pre-integration-test - - start - - - - post-integration-test - - stop - - - - - - org.springdoc - springdoc-openapi-maven-plugin - last-release-version - - - integration-test - - generate - - - - + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-maven-plugin.version} + + -Dspring.application.admin.enabled=true + + + + pre-integration-test + + start + + + + post-integration-test + + stop + + + + + + org.springdoc + springdoc-openapi-maven-plugin + last-release-version + + + integration-test + + generate + + + + ``` - + ## **Custom settings of the springdoc-openapi-maven-plugin** It possible to customise the following plugin properties: * attachArtifact: install / deploy the api doc to the repository * The default value is: false -* apiDocsUrl: The local url of your (json or yaml). +* apiDocsUrl: The local url of your (json or yaml). * The default value is: http://localhost:8080/v3/api-docs * outputDir: The output directory, where to generate the OpenAPI description. The directory name shouldn't start with "/". * The default value is: ${project.build.directory} -* outputFileName: The file name that contains the OpenAPI description. +* outputFileName: The file name that contains the OpenAPI description. * The default value is: openapi.json * skip: Skip execution if set to true. * The default value is: false @@ -74,31 +74,97 @@ It possible to customise the following plugin properties: ```xml - org.springdoc - springdoc-openapi-maven-plugin - last-release-version - - - integration-test - - generate - - - - - http://localhost:8080/v3/api-docs - openapi.json - home/springdoc/maven-output - false - true - - header1value - header2value - - + org.springdoc + springdoc-openapi-maven-plugin + last-release-version + + + integration-test + + generate + + + + + http://localhost:8080/v3/api-docs + openapi.json + home/springdoc/maven-output + false + true + + header1value + header2value + + ``` +## **Site Report: Swagger UI in Maven Project Reports** + +The plugin provides a `report` goal that generates a static Swagger UI page and places it in the Maven site under **Project Reports**. + +The report reads the OpenAPI spec from the file produced by the `generate` goal (default `target/openapi.json`). If the file does not exist, it falls back to fetching from `apiDocsUrl`. + +**Recommended workflow:** run `mvn verify` first (to generate the spec), then `mvn site`. + +Add the plugin to the `` section of your POM: + +```xml + + + + org.springdoc + springdoc-openapi-maven-plugin + last-release-version + + + + report + + + + + + +``` + +### Report configuration properties + +| Property | Default | Description | +|---|---|---| +| `springdoc.report.skip` | `false` | Skip report generation | +| `springdoc.apiDocsFile` | `${project.build.directory}/openapi.json` | Path to a pre-generated OpenAPI spec file | +| `springdoc.apiDocsUrl` | `http://localhost:8080/v3/api-docs` | Fallback URL when spec file is absent | +| `springdoc.report.name` | `OpenAPI Documentation` | Name in Project Reports navigation | +| `springdoc.report.description` | `OpenAPI specification rendered with Swagger UI` | Description in Project Reports | +| `springdoc.report.directory` | `springdoc-openapi` | Subdirectory under the site output | +| `springdoc.swaggerUiVersion` | `5` | Swagger UI version for CDN (unpkg.com) | + +### Example with custom configuration + +```xml + + + + org.springdoc + springdoc-openapi-maven-plugin + last-release-version + + API Reference + 5.18.2 + + + + + report + + + + + + +``` + # **Thank you for the support** * Thanks a lot [JetBrains](https://www.jetbrains.com/?from=springdoc-openapi) for supporting springdoc-openapi project. From 17349c37d19c6b4938ad56cc8235b67ed11fcf1c Mon Sep 17 00:00:00 2001 From: patpatpat123 <43899031+patpatpat123@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:14:01 +0800 Subject: [PATCH 3/3] Create SpringDocOpenApiReportMojo.java --- .../plugin/SpringDocOpenApiReportMojo.java | 283 ++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 src/main/java/org/springdoc/maven/plugin/SpringDocOpenApiReportMojo.java diff --git a/src/main/java/org/springdoc/maven/plugin/SpringDocOpenApiReportMojo.java b/src/main/java/org/springdoc/maven/plugin/SpringDocOpenApiReportMojo.java new file mode 100644 index 0000000..2362240 --- /dev/null +++ b/src/main/java/org/springdoc/maven/plugin/SpringDocOpenApiReportMojo.java @@ -0,0 +1,283 @@ +package org.springdoc.maven.plugin; + +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.reporting.AbstractMavenReport; +import org.apache.maven.reporting.MavenReportException; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Locale; +import java.util.Map; + +/** + * Generates a static Swagger UI page from an OpenAPI specification + * and integrates it into the Maven project site under Project Reports. + * + *

The spec is loaded from a file produced by the {@code generate} goal + * (default {@code ${project.build.directory}/openapi.json}). + * If the file does not exist, the mojo falls back to fetching the spec + * from {@code apiDocsUrl} (requires the application to be running).

+ * + *

Usage in a consumer POM:

+ *
+ * <reporting>
+ *   <plugins>
+ *     <plugin>
+ *       <groupId>org.springdoc</groupId>
+ *       <artifactId>springdoc-openapi-maven-plugin</artifactId>
+ *       <reportSets>
+ *         <reportSet>
+ *           <reports>
+ *             <report>report</report>
+ *           </reports>
+ *         </reportSet>
+ *       </reportSets>
+ *     </plugin>
+ *   </plugins>
+ * </reporting>
+ * 
+ * + * @author springdoc + */ +@Mojo(name = "report", defaultPhase = LifecyclePhase.SITE, threadSafe = true) +public class SpringDocOpenApiReportMojo extends AbstractMavenReport { + + private static final String GET = "GET"; + + /** + * Skip report generation. + */ + @Parameter(defaultValue = "false", property = "springdoc.report.skip") + private boolean skip; + + /** + * Path to a previously generated OpenAPI spec file. + * This is the primary source; if the file exists it is used directly. + * Typically produced by the {@code generate} goal. + */ + @Parameter(defaultValue = "${project.build.directory}/openapi.json", property = "springdoc.apiDocsFile") + private File apiDocsFile; + + /** + * Fallback URL to fetch the OpenAPI spec from when the file does not exist. + * The application must be running at this URL for the fallback to succeed. + */ + @Parameter(defaultValue = "http://localhost:8080/v3/api-docs", property = "springdoc.apiDocsUrl", required = true) + private String apiDocsUrl; + + /** + * HTTP headers sent when fetching the spec from {@code apiDocsUrl}. + */ + @Parameter(property = "headers") + private Map headers; + + /** + * Name shown in the site's Project Reports navigation. + */ + @Parameter(property = "springdoc.report.name", defaultValue = "OpenAPI Documentation") + private String siteReportName; + + /** + * Description shown in the site's Project Reports navigation. + */ + @Parameter(property = "springdoc.report.description", + defaultValue = "OpenAPI specification rendered with Swagger UI") + private String siteReportDescription; + + /** + * Subdirectory under the site output directory where + * the Swagger UI page and spec file are written. + */ + @Parameter(property = "springdoc.report.directory", defaultValue = "springdoc-openapi") + private String siteReportDirectory; + + /** + * Swagger UI version tag used for CDN references (unpkg.com). + * Examples: {@code "5"} (latest 5.x), {@code "5.18.2"} (pinned). + */ + @Parameter(property = "springdoc.swaggerUiVersion", defaultValue = "5") + private String swaggerUiVersion; + + @Override + public String getOutputName() { + return siteReportDirectory + "/index"; + } + + @Override + public String getName(Locale locale) { + return siteReportName; + } + + @Override + public String getDescription(Locale locale) { + return siteReportDescription; + } + + @Override + public boolean canGenerateReport() { + return !skip; + } + + @Override + public boolean isExternalReport() { + return true; + } + + @Override + protected void executeReport(Locale locale) throws MavenReportException { + getLog().info("Generating OpenAPI site report"); + + String specJson = loadSpecJson(); + + File reportOutputDir = new File( + getReportOutputDirectory(), siteReportDirectory); + if (!reportOutputDir.mkdirs() && !reportOutputDir.isDirectory()) { + throw new MavenReportException( + "Could not create report output directory: " + reportOutputDir); + } + + writeSwaggerUiPage(reportOutputDir, specJson); + writeSpecFile(reportOutputDir, specJson); + + getLog().info("OpenAPI report generated at " + reportOutputDir.getAbsolutePath()); + } + + /** + * Loads the OpenAPI spec JSON from the configured file, + * falling back to the configured URL if the file does not exist. + */ + private String loadSpecJson() throws MavenReportException { + if (apiDocsFile != null && apiDocsFile.isFile()) { + getLog().info("Reading OpenAPI spec from " + apiDocsFile.getAbsolutePath()); + try { + return new String( + Files.readAllBytes(apiDocsFile.toPath()), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new MavenReportException( + "Failed to read OpenAPI spec from " + apiDocsFile, e); + } + } + + getLog().info("Spec file not found at " + apiDocsFile + + ", fetching from " + apiDocsUrl); + try { + return fetchFromUrl(); + } catch (IOException e) { + throw new MavenReportException( + "Could not load OpenAPI spec from file (" + apiDocsFile + + ") or URL (" + apiDocsUrl + "). " + + "Run the 'generate' goal first or ensure the application is running.", + e); + } + } + + private String fetchFromUrl() throws IOException { + URL url = new URL(apiDocsUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + if (headers != null && !headers.isEmpty()) { + for (Map.Entry entry : headers.entrySet()) { + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } + } + connection.setRequestMethod(GET); + + int responseCode = connection.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + throw new IOException("HTTP " + responseCode + " from " + apiDocsUrl); + } + + try (InputStream is = connection.getInputStream()) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int len; + while ((len = is.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + return baos.toString(StandardCharsets.UTF_8.name()); + } + } + + private void writeSwaggerUiPage(File outputDir, String specJson) + throws MavenReportException { + String cdnBase = "https://unpkg.com/swagger-ui-dist@" + swaggerUiVersion; + // Prevent in JSON values from closing the script tag early + String safeSpecJson = specJson.replace("\n"); + html.append("\n"); + html.append("\n"); + html.append(" \n"); + html.append(" ").append(escapeHtml(siteReportName)).append("\n"); + html.append(" \n"); + html.append(" \n"); + html.append("\n"); + html.append("\n"); + html.append("
\n"); + html.append(" \n"); + html.append(" \n"); + html.append(" \n"); + html.append("\n"); + html.append("\n"); + + File indexFile = new File(outputDir, "index.html"); + try { + Files.write(indexFile.toPath(), + html.toString().getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new MavenReportException("Failed to write Swagger UI page", e); + } + } + + private void writeSpecFile(File outputDir, String specJson) + throws MavenReportException { + File specFile = new File(outputDir, "openapi.json"); + try { + Files.write(specFile.toPath(), + specJson.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new MavenReportException("Failed to write OpenAPI spec file", e); + } + } + + private static String escapeHtml(String text) { + if (text == null) { + return ""; + } + return text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """); + } +}