plugins { `java-platform` //id( "java" ) id( "org.springframework.boot") version "3.4.1" id( "io.spring.dependency-management") version "1.1.7" // manages implicit dependencies id( "io.openapiprocessor.openapi-processor") version "2023.2" // generates Controller-interface and resources from API-spec id( "com.github.jk1.dependency-license-report") version "2.9" // checks dependency-license compatibility id( "org.owasp.dependencycheck") version "12.0.1" // checks dependencies for known vulnerabilities id( "com.diffplug.spotless") version "7.0.2" // formats + checks formatting for source-code id( "jacoco") // determines code-coverage of tests id( "info.solidsoft.pitest") version "1.15.0" // performs mutation testing id( "se.patrikerdes.use-latest-versions") version "0.2.18" // updates module and plugin versions id( "com.github.ben-manes.versions") version "0.52.0" // determines which dependencies have updates } // HOWTO: find out which dependency versions are managed by Spring Boot: // https://docs.spring.io/spring-boot/appendix/dependency-versions/coordinates.html group = "net.hostsharing" version = "0.0.1-SNAPSHOT" tasks.named("wrapper") { distributionType = Wrapper.DistributionType.BIN gradleVersion = "8.5" } // TODO.impl: self-attaching is deprecated, see: // https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3 configurations { named("compileOnly") { extendsFrom(configurations.named("annotationProcessor").get()) } named("testCompile") { extendsFrom(configurations.named("testAnnotationProcessor").get()) // Only JUnit 5 (Jupiter) should be used at compile time. // For runtime it's still needed by testcontainers, though. exclude(group = "junit", module = "junit") exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } } extensions.configure { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) vendor.set(JvmVendorSpec.ADOPTIUM) implementation.set(JvmImplementation.VENDOR_SPECIFIC) } } extra["testcontainersVersion"] = "1.17.3" allprojects { repositories { mavenCentral() maven { url = uri("https://repo.spring.io/milestone") } maven { url = uri("https://repo.spring.io/snapshot") } } dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-rest") implementation("org.springframework.boot:spring-boot-starter-jdbc") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-security") implementation("com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.10.0") implementation("org.springdoc:springdoc-openapi:2.8.3") classpath("org.postgresql:postgresql") classpath("org.liquibase:liquibase-core") classpath("io.hypersistence:hypersistence-utils-hibernate-63:3.9.0") classpath("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") classpath("org.openapitools:jackson-databind-nullable:0.2.6") classpath("org.apache.commons:commons-text:1.13.0") classpath("net.java.dev.jna:jna:5.16.0") classpath("org.modelmapper:modelmapper:3.2.2") classpath("org.iban4j:iban4j:3.2.10-RELEASE") classpath("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3") classpath("org.reflections:reflections:0.10.2") compileOnly("org.projectlombok:lombok") testCompileOnly("org.projectlombok:lombok") // TODO.impl: version conflict with SpringDoc, check later and re-enable if fixed // developmentOnly "org.springframework.boot:spring-boot-devtools" annotationProcessor("org.projectlombok:lombok") testAnnotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.testcontainers:testcontainers") testImplementation("org.testcontainers:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.testcontainers:postgresql") testImplementation("com.tngtech.archunit:archunit-junit5:1.3.0") testImplementation("io.rest-assured:spring-mock-mvc") testImplementation("org.hamcrest:hamcrest-core") testImplementation("org.pitest:pitest-junit5-plugin:1.2.1") testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.wiremock:wiremock-standalone:3.10.0") } } // Java Compiler Options tasks.withType().configureEach { options.compilerArgs.add("-parameters") // keep parameter names => no need for @Param for SpringData } // Configure tests tasks.withType { useJUnitPlatform() jvmArgs("-Duser.language=en", "-Duser.country=US") } // OpenAPI Source Code Generation //openapiProcessor { // springRoot { // processorName = "spring" // processor = "io.openapiprocessor:openapi-processor-spring:2022.5" // apiPath "$projectDir/src/main/resources/api-definition/api-definition.yaml" // mapping "$projectDir/src/main/resources/api-definition/api-mappings.yaml" // targetDir layout.buildDirectory.dir("generated/sources/openapi-javax") // showWarnings true // openApiNullable true // } // springRbac { // processorName "spring" // processor "io.openapiprocessor:openapi-processor-spring:2022.5" // apiPath "$projectDir/src/main/resources/api-definition/rbac/rbac.yaml" // mapping "$projectDir/src/main/resources/api-definition/rbac/api-mappings.yaml" // targetDir layout.buildDirectory.dir("generated/sources/openapi-javax") // showWarnings true // openApiNullable true // } // springTest { // processorName "spring" // processor "io.openapiprocessor:openapi-processor-spring:2022.5" // apiPath "$projectDir/src/main/resources/api-definition/test/test.yaml" // mapping "$projectDir/src/main/resources/api-definition/test/api-mappings.yaml" // targetDir layout.buildDirectory.dir("generated/sources/openapi-javax") // showWarnings true // openApiNullable true // } // springHsOffice { // processorName "spring" // processor "io.openapiprocessor:openapi-processor-spring:2022.5" // apiPath "$projectDir/src/main/resources/api-definition/hs-office/hs-office.yaml" // mapping "$projectDir/src/main/resources/api-definition/hs-office/api-mappings.yaml" // targetDir layout.buildDirectory.dir("generated/sources/openapi-javax") // showWarnings true // openApiNullable true // } // springHsBooking { // processorName "spring" // processor "io.openapiprocessor:openapi-processor-spring:2022.5" // apiPath "$projectDir/src/main/resources/api-definition/hs-booking/hs-booking.yaml" // mapping "$projectDir/src/main/resources/api-definition/hs-booking/api-mappings.yaml" // targetDir layout.buildDirectory.dir("generated/sources/openapi-javax") // showWarnings true // openApiNullable true // } // springHsHosting { // processorName "spring" // processor "io.openapiprocessor:openapi-processor-spring:2022.5" // apiPath "$projectDir/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml" // mapping "$projectDir/src/main/resources/api-definition/hs-hosting/api-mappings.yaml" // targetDir layout.buildDirectory.dir("generated/sources/openapi-javax") // showWarnings true // openApiNullable true // } //} //sourceSets.main.java.srcDir "build/generated/sources/openapi" //abstract class ProcessSpring extends DefaultTask {} //tasks.register("processSpring", ProcessSpring) //["processSpringRoot", // "processSpringRbac", // "processSpringTest", // "processSpringHsOffice", // "processSpringHsBooking", // "processSpringHsHosting" //].each { // project.tasks.processSpring.dependsOn it //} //project.tasks.processResources.dependsOn processSpring // project.tasks.compileJava.dependsOn processSpring // Rename javax to jakarta in OpenApi generated java files because // io.openapiprocessor.openapi-processor 2022.5 does not yet support the openapiprocessor useSpringBoot3 config option. // TODO.impl: Upgrade to io.openapiprocessor.openapi-processor >= 2024.2 // and use either `bean-validation: true` in api-mapping.yaml or `useSpringBoot3 true` (not sure where exactly). //task openApiGenerate(type: Copy) { // from layout.buildDirectory.dir("generated/sources/openapi-javax") // into layout.buildDirectory.dir("generated/sources/openapi") // filter { line -> line.replaceAll("javax", "jakarta") } } //compileJava.source layout.buildDirectory.dir("generated/sources/openapi") //compileJava.dependsOn openApiGenerate // openApiGenerate.dependsOn processSpring // Spotless Code Formatting // spotless { // java { // removeUnusedImports() // leadingTabsToSpaces(4) // endWithNewline() // toggleOffOn() // // target fileTree(rootDir) { // include "**/*.java" // exclude "**/generated/**/*.java" // } // } // } //project.tasks.check.dependsOn(spotlessCheck) // HACK: no idea why spotless uses the output of these tasks, but we get warnings without those //project.tasks.spotlessJava.dependsOn( // tasks.generateLicenseReport, // // tasks.pitest, TODO.test: PiTest currently does not work, needs to be fixed // tasks.jacocoTestReport, // tasks.processResources, // tasks.processTestResources) // OWASP Dependency Security Test //dependencyCheck { // nvd { // apiKey = project.properties["OWASP_API_KEY"] // set it in ~/.gradle/gradle.properties // delay = 16000 // } // format = "ALL" // suppressionFile = "etc/owasp-dependency-check-suppression.xml" // failOnError = true // failBuildOnCVSS = 5 //} //project.tasks.check.dependsOn(dependencyCheckAnalyze) //project.tasks.dependencyCheckAnalyze.doFirst { // Why not doLast? See README.md! // println "OWASP Dependency Security Report: file:///${project.rootDir}/build/reports/dependency-check-report.html" //} // License Check //licenseReport { // excludeBoms = true // allowedLicensesFile = new File("$projectDir/etc/allowed-licenses.json") //} //project.tasks.check.dependsOn(checkLicense) // HOWTO: run all tests except import- and scenario-tests: gw test //test { // finalizedBy jacocoTestReport // generate report after tests // excludes = [ // "net.hostsharing.hsadminng.**.generated.**", // ] // useJUnitPlatform { // excludeTags "importOfficeData", "importHostingAssets", "scenarioTest" // } //} // JaCoCo Test Code Coverage for unit-tests //jacoco { // toolVersion = "0.8.10" //} //jacocoTestReport { // dependsOn test // afterEvaluate { // classDirectories.setFrom(files(classDirectories.files.collect { // fileTree(dir: it, exclude: [ // "net/hostsharing/hsadminng/**/generated/**/*.class", // "net/hostsharing/hsadminng/hs/HsadminNgApplication.class" // ]) // })) // } // doFirst { // Why not doLast? See README.md! // println "HTML Jacoco Test Code Coverage Report: file://${reports.html.outputLocation.get()}/index.html" // } //} //project.tasks.check.dependsOn(jacocoTestCoverageVerification) //jacocoTestCoverageVerification { // violationRules { // rule { // limit { // minimum = 0.80 // TODO.test: improve instruction coverage // } // } // // // element: PACKAGE, BUNDLE, CLASS, SOURCEFILE or METHOD // // counter: INSTRUCTION, BRANCH, LINE, COMPLEXITY, METHOD, or CLASS // // value: TOTALCOUNT, COVEREDCOUNT, MISSEDCOUNT, COVEREDRATIO or MISSEDRATIO // // rule { // element = "CLASS" // excludes = [ // "net.hostsharing.hsadminng.**.generated.**", // "net.hostsharing.hsadminng.rbac.test.dom.TestDomainEntity", // "net.hostsharing.hsadminng.HsadminNgApplication", // "net.hostsharing.hsadminng.ping.PingController", // "net.hostsharing.hsadminng.rbac.generator.*", // "net.hostsharing.hsadminng.rbac.grant.RbacGrantsDiagramService", // "net.hostsharing.hsadminng.rbac.grant.RbacGrantsDiagramService.Node", // "net.hostsharing.hsadminng.**.*Repository", // "net.hostsharing.hsadminng.mapper.Mapper" // ] // // limit { // counter = "LINE" // value = "COVEREDRATIO" // minimum = 0.75 // TODO.test: improve line coverage // } // } // rule { // element = "METHOD" // excludes = [ // "net.hostsharing.hsadminng.**.generated.**", // "net.hostsharing.hsadminng.HsadminNgApplication.main", // "net.hostsharing.hsadminng.ping.PingController.*" // ] // // limit { // counter = "BRANCH" // value = "COVEREDRATIO" // minimum = 0.00 // TODO.test: improve branch coverage // } // } // } //} // HOWTO: run all unit-tests which don"t need a database: gw-test unitTest //tasks.register("unitTest", Test) { // useJUnitPlatform { // excludeTags "importOfficeData", "importHostingAssets", "scenarioTest", "generalIntegrationTest", // "officeIntegrationTest", "bookingIntegrationTest", "hostingIntegrationTest" // } // // group "verification" // description "runs all unit-tests which do not need a database" // // mustRunAfter spotlessJava //} // HOWTO: run all integration tests which are not specific to a module, like base, rbac, config etc. //tasks.register("generalIntegrationTest", Test) { // useJUnitPlatform { // includeTags "generalIntegrationTest" // } // // group "verification" // description "runs integration tests which are not specific to a module, like base, rbac, config etc." // // mustRunAfter spotlessJava //} // HOWTO: run all integration tests of the office module: gw-test officeIntegrationTest //tasks.register("officeIntegrationTest", Test) { // useJUnitPlatform { // includeTags "officeIntegrationTest" // } // // group "verification" // description "runs integration tests of the office module" // // mustRunAfter spotlessJava //} // HOWTO: run all integration tests of the booking module: gw-test bookingIntegrationTest //tasks.register("bookingIntegrationTest", Test) { // useJUnitPlatform { // includeTags "bookingIntegrationTest" // } // // group "verification" // description "runs integration tests of the booking module" // // mustRunAfter spotlessJava //} // HOWTO: run all integration tests of the hosting module: gw-test hostingIntegrationTest //tasks.register("hostingIntegrationTest", Test) { // useJUnitPlatform { // includeTags "hostingIntegrationTest" // } // // group "verification" // description "runs integration tests of the hosting module" // // mustRunAfter spotlessJava //} //tasks.register("importOfficeData", Test) { // useJUnitPlatform { // includeTags "importOfficeData" // } // // group "verification" // description "run the import jobs as tests" // // mustRunAfter spotlessJava //} //tasks.register("importHostingAssets", Test) { // useJUnitPlatform { // includeTags "importHostingAssets" // } // // group "verification" // description "run the import jobs as tests" // // mustRunAfter spotlessJava //} //tasks.register("scenarioTest", Test) { // useJUnitPlatform { // includeTags "scenarioTest" // } // // group "verification" // description "run the import jobs as tests" // // mustRunAfter spotlessJava //} // pitest mutation testing //pitest { // targetClasses = ["net.hostsharing.hsadminng.**"] // excludedClasses = [ // "net.hostsharing.hsadminng.config.**", // // "net.hostsharing.hsadminng.**.*Controller", // "net.hostsharing.hsadminng.**.generated.**" // ] // // targetTests = ["net.hostsharing.hsadminng.**.*UnitTest", "net.hostsharing.hsadminng.**.*RestTest"] // excludedTestClasses = ["**AcceptanceTest*", "**IntegrationTest*", "**ImportOfficeData", "**ImportHostingAssets"] // // pitestVersion = "1.17.0" // junit5PluginVersion = "1.1.0" // // threads = 4 // // // As Java unit tests are pretty pointless in our case, this maybe makes not much sense. // mutationThreshold = 71 // coverageThreshold = 57 // testStrengthThreshold = 87 // // outputFormats = ["XML", "HTML"] // timestampedReports = false //} // project.tasks.check.dependsOn(project.tasks.pitest) TODO.test: PiTest currently does not work, needs to be fixed //project.tasks.pitest.doFirst { // Why not doLast? See README.md! // println "PiTest Mutation Report: file:///${project.rootDir}/build/reports/pitest/index.html" //} // Dependency Versions Upgrade //useLatestVersions { // finalizedBy check //} //def isNonStable = { String version -> // def stableKeyword = ["RELEASE", "FINAL", "GA"].any { it -> version.toUpperCase().contains(it) } // def regex = /^[0-9,.v-]+(-r)?$/ // return !stableKeyword && !(version ==~ regex) //} //tasks.named("dependencyUpdates").configure { // rejectVersionIf { // isNonStable(it.candidate.version) // } //} // Generate HTML from Markdown scenario-test-reports using Pandoc: //tasks.register("convertMarkdownToHtml") { // description = "Generates HTML from Markdown scenario-test-reports using Pandoc." // group = "Conversion" // // // Define the template file and input directory // def templateFile = file("doc/scenarios/.template.html") // // // Task configuration and execution // doFirst { // // Check if pandoc is installed // try { // exec { // commandLine "pandoc", "--version" // } // } catch (Exception) { // throw new GradleException("Pandoc is not installed or not found in the system path.") // } // // // Check if the template file exists // if (!templateFile.exists()) { // throw new GradleException("Template file "doc/scenarios/.template.html" not found.") // } // } // // doLast { // // Gather all Markdown files in the current directory // fileTree(dir: ".", include: "build/doc/scenarios/*.md").each { file -> // // Corrected way to create the output file path // def outputFile = new File(file.parent, file.name.replaceAll(/\.md$/, ".html")) // // // Execute pandoc for each markdown file // exec { // commandLine "pandoc", file.absolutePath, "--template", templateFile.absolutePath, "-o", outputFile.absolutePath // } // // println "Converted ${file.name} to ${outputFile.name}" // } // } //} //convertMarkdownToHtml.dependsOn scenarioTest // shortcut for compiling all files // tasks.register("compile") { // dependsOn "compileJava", "compileTestJava" // }