Skip to content
Merged
10 changes: 3 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ The plugin does this with help of springdoc-openapi-core.
Compatibility Notes
-------------------

The plugin is built on Gradle version 6.4.1.
The plugin is built on Gradle version 7.0.

Dependencies
------------
This plugin has a runtime dependency on the the following plugins:
This plugin has a runtime dependency on the following plugins:

1. Spring Boot Gradle plugin - `org.springframework.boot`
2. Gradle process plugin - `com.github.johnrengelman.processes`

Hence these plugins also needs to be added to your Gradle builds.
2. Gradle process plugin - `com.github.psxpaul.execfork`

Note: You will also need the springdoc-core jar file to be present in your Spring Boot application.

Expand All @@ -32,7 +30,6 @@ Gradle Groovy DSL
```groovy
plugins {
id "org.springframework.boot" version "2.3.0.RELEASE"
id "com.github.johnrengelman.processes" version "0.5.0"
id "org.springdoc.openapi-gradle-plugin" version "1.3.3"
}
```
Expand All @@ -41,7 +38,6 @@ Gradle Kotlin DSL
```groovy
plugins {
id("org.springframework.boot") version "2.3.0.RELEASE"
id("com.github.johnrengelman.processes") version "0.5.0"
id("org.springdoc.openapi-gradle-plugin") version "1.3.3"
}
```
Expand Down
7 changes: 6 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ repositories {
url = uri("https://repo.spring.io/libs-release/")
}
gradlePluginPortal()
maven {
name = "Gradle Plugins Maven Repository"
url = uri("https://plugins.gradle.org/m2/")
}
}

publishing {
Expand All @@ -45,7 +49,8 @@ dependencies {
implementation("khttp:khttp:1.0.0")
implementation("com.google.code.gson:gson:2.8.6")
implementation("org.awaitility:awaitility-kotlin:4.0.3")
implementation(files("$projectDir/libs/gradle-processes-0.5.0.jar"))
implementation("com.github.psxpaul:gradle-execfork-plugin:0.1.15")
implementation("org.springframework.boot:spring-boot-gradle-plugin:2.5.6")

testImplementation(gradleTestKit())
testImplementation(platform("org.junit:junit-bom:5.7.1"))
Expand Down
Binary file removed libs/gradle-processes-0.5.0.jar
Binary file not shown.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ const val EXTENSION_NAME = "openApi"
const val GROUP_NAME = "OpenApi"
const val OPEN_API_TASK_NAME = "generateOpenApiDocs"
const val OPEN_API_TASK_DESCRIPTION = "Generates the spring doc openapi file"
const val SPRING_BOOT_JAR_TASK_NAME = "bootJar"
const val SPRING_BOOT_RUN_TASK_NAME = "bootRun"
const val SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME = "bootRunMainClassName"
const val FORKED_SPRING_BOOT_RUN_TASK_NAME = "forkedSpringBootRun"
const val FINALIZER_TASK_NAME = "stopForkedSpringBoot"

const val DEFAULT_API_DOCS_URL = "http://localhost:8080/v3/api-docs"
const val DEFAULT_OPEN_API_FILE_NAME = "openapi.json"
const val DEFAULT_WAIT_TIME_IN_SECONDS = 30

const val SPRING_BOOT_PLUGIN = "org.springframework.boot"
const val PROCESS_PLUGIN = "com.github.johnrengelman.processes"
const val EXEC_FORK_PLUGIN = "com.github.psxpaul.execfork"

const val PROPS_LAUNCHER_CLASS = "org.springframework.boot.loader.PropertiesLauncher"
const val CLASS_PATH_PROPERTY_NAME = "java.class.path"
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ open class OpenApiExtension @Inject constructor(project: Project) {
val outputFileName: Property<String> = project.objects.property(String::class.java)
val outputDir: DirectoryProperty = project.objects.directoryProperty()
val waitTimeInSeconds: Property<Int> = project.objects.property(Int::class.java)
val forkProperties: Property<Any> = project.objects.property(Any::class.java)
val groupedApiMappings: MapProperty<String, String> = project.objects.mapProperty(String::class.java, String::class.java)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,54 @@

package org.springdoc.openapi.gradle.plugin

import com.github.psxpaul.task.JavaExecFork
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property
import java.util.*
import org.springframework.boot.gradle.tasks.run.BootRun

open class OpenApiGradlePlugin : Plugin<Project> {
private val logger = Logging.getLogger(OpenApiGradlePlugin::class.java)

override fun apply(project: Project) {
// Run time dependency on the following plugins
project.plugins.apply(SPRING_BOOT_PLUGIN)
project.plugins.apply(PROCESS_PLUGIN)
project.plugins.apply(EXEC_FORK_PLUGIN)

project.extensions.create(EXTENSION_NAME, OpenApiExtension::class.java, project)

project.afterEvaluate {
// Spring boot jar task
val bootJarTask = project.tasks.named(SPRING_BOOT_JAR_TASK_NAME)

val extension: OpenApiExtension = project.extensions.run {
getByName(EXTENSION_NAME) as OpenApiExtension
}
// The task, used to run the Spring Boot application (`bootRun`)
val bootRunTask = project.tasks.named(SPRING_BOOT_RUN_TASK_NAME)
// The task, used to resolve the application's main class (`bootRunMainClassName`)
val bootRunMainClassNameTask = project.tasks.named(SPRING_BOOT_RUN_MAIN_CLASS_NAME_TASK_NAME)

// Create a forked version spring boot run task
val forkedSpringBoot = project.tasks.register(FORKED_SPRING_BOOT_RUN_TASK_NAME, AnnotatedFork::class.java) { fork ->
fork.dependsOn(bootJarTask)

fork.onlyIf {
val bootJar = bootJarTask.get().outputs.files.first()
fork.commandLine = listOf("java", "-cp") +
listOf("$bootJar") + extractProperties(extension.forkProperties) + listOf(PROPS_LAUNCHER_CLASS)
true
}
}

val stopForkedSpringBoot = project.tasks.register(FINALIZER_TASK_NAME) {
it.dependsOn(forkedSpringBoot)
it.doLast {
forkedSpringBoot.get().processHandle.abort()
val forkedSpringBoot =
project.tasks.register(FORKED_SPRING_BOOT_RUN_TASK_NAME, JavaExecFork::class.java) { fork ->
fork.dependsOn(bootRunMainClassNameTask)

fork.onlyIf {
val bootRun = bootRunTask.get() as BootRun

// copy all system properties, excluding those starting with `java.class.path`
fork.systemProperties =
bootRun.systemProperties.filter { !it.key.startsWith(CLASS_PATH_PROPERTY_NAME) }

fork.workingDir = bootRun.workingDir
fork.args = bootRun.args?.toMutableList() ?: mutableListOf()
fork.classpath = bootRun.classpath
fork.main = bootRun.mainClass.get()
fork.jvmArgs = bootRun.jvmArgs
fork.environment = bootRun.environment
true
}
}
}

// This is my task. Before I can run it I have to run the dependent tasks
project.tasks.register(OPEN_API_TASK_NAME, OpenApiGeneratorTask::class.java) { openApiGenTask ->
openApiGenTask.dependsOn(forkedSpringBoot)
openApiGenTask.finalizedBy(stopForkedSpringBoot)
}
}
}

private fun extractProperties(forkProperties: Property<Any>) =
if (forkProperties.isPresent) {
when (val element = forkProperties.get()) {
is String -> element
.split("-D")
.filter { it.isNotEmpty() }
.filterNot { it.startsWith(CLASS_PATH_PROPERTY_NAME, true) }
.map { "-D${it.trim()}" }
is Properties -> element
.filterNot { it.key.toString().startsWith(CLASS_PATH_PROPERTY_NAME, true) }
.map { "-D${it.key}=${it.value}" }
else -> {
logger.warn("Failed to use the value set for 'forkProperties'. Only String and Properties objects are supported.")
emptyList()
}
}
} else emptyList()
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class OpenApiGradlePluginTest {
id 'java'
id 'org.springframework.boot' version '2.4.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'com.github.johnrengelman.processes' version '0.5.0'
id 'org.springdoc.openapi-gradle-plugin'
}

Expand Down Expand Up @@ -89,11 +88,11 @@ class OpenApiGradlePluginTest {
}

@Test
fun `using forked properties`() {
fun `using properties`() {
buildFile.writeText(
"""$baseBuildGradle
openApi{
forkProperties = "-Dspring.profiles.active=multiple-endpoints -Dsome.second.property=someValue"
bootRun {
args = ["--spring.profiles.active=multiple-endpoints", "--some.second.property=someValue"]
}
""".trimMargin()
)
Expand All @@ -106,8 +105,8 @@ class OpenApiGradlePluginTest {
fun `using forked properties via System properties`() {
buildFile.writeText(
"""$baseBuildGradle
openApi{
forkProperties = System.properties
bootRun {
systemProperties = System.properties
}
""".trimMargin()
)
Expand All @@ -120,8 +119,10 @@ class OpenApiGradlePluginTest {
fun `configurable wait time`() {
buildFile.writeText(
"""$baseBuildGradle
openApi{
forkProperties = "-Dspring.profiles.active=slower"
bootRun {
args = ["--spring.profiles.active=slower"]
}
openApi{
waitTimeInSeconds = 60
}
""".trimMargin()
Expand All @@ -135,9 +136,11 @@ class OpenApiGradlePluginTest {
fun `using different api url`() {
buildFile.writeText(
"""$baseBuildGradle
bootRun {
args = ["--spring.profiles.active=different-url"]
}
openApi{
apiDocsUrl = "http://localhost:8080/secret-api-docs"
forkProperties = "-Dspring.profiles.active=different-url"
}
""".trimMargin()
)
Expand All @@ -153,10 +156,12 @@ class OpenApiGradlePluginTest {

buildFile.writeText(
"""$baseBuildGradle
bootRun {
args = ["--spring.profiles.active=multiple-grouped-apis"]
}
openApi{
groupedApiMappings = ["http://localhost:8080/v3/api-docs/groupA": "$outputJsonFileNameGroupA",
"http://localhost:8080/v3/api-docs/groupB": "$outputJsonFileNameGroupB"]
forkProperties = "-Dspring.profiles.active=multiple-grouped-apis"
}
""".trimMargin()
)
Expand All @@ -174,12 +179,14 @@ class OpenApiGradlePluginTest {

buildFile.writeText(
"""$baseBuildGradle
bootRun {
args = ["--spring.profiles.active=multiple-grouped-apis"]
}
openApi{
apiDocsUrl = "http://localhost:8080/v3/api-docs/groupA"
outputFileName = "$outputJsonFileNameSingleGroupA"
groupedApiMappings = ["http://localhost:8080/v3/api-docs/groupA": "$outputJsonFileNameGroupA",
"http://localhost:8080/v3/api-docs/groupB": "$outputJsonFileNameGroupB"]
forkProperties = "-Dspring.profiles.active=multiple-grouped-apis"
}
""".trimMargin()
)
Expand Down