spring-bootkotlingradle

Gradle - where to get `filesMatching` and `expand` for Spring Boot project


Trying to use the following DSL for my Spring Boot 3.4 project and my build.gradle.kts has the following - it complains that it cannot find filesMatching or expand methods here.

tasks.named("processResources") {
    filesMatching("application.properties") {
        expand(project.properties)
    }
}

What plugins do I need? I have this:

plugins {
    id("io.spring.dependency-management") version "1.1.7"
    id("org.springframework.boot") version "3.4.4"
    id("org.owasp.dependencycheck") version "12.1.0"
    kotlin("jvm") version "1.9.23"
    kotlin("plugin.spring") version "1.9.23"
}

How do I get these methods available here?


Solution

  • Project Directory

    demo-kotlin-app
    ├── settings.gradle.kts
    ├── build.gradle.kts
    ├── gradle.properties
    └── src
        └── main
            ├── kotlin
            │   └── com
            │       └── example
            │           ├── DemoKotlinAppApplication.kt
            │           ├── model
            │           │   └── Person.kt
            │           ├── repository
            │           │   └── PersonRepository.kt
            │           ├── service
            │           │   └── PersonService.kt
            │           └── controller
            │               └── PersonController.kt
            └── resources
                ├── application.properties
                └── logback-spring.xml
    

    settings.gradle.kts

    The contents of this file are the default settings, they have not been changed and are not important.

    rootProject.name = "demo-kotlin-app"
    

    build.gradle.kts

    plugins {
        kotlin("jvm") version "1.9.25"
        kotlin("plugin.spring") version "1.9.25"
        id("org.springframework.boot") version "3.4.4"
        id("io.spring.dependency-management") version "1.1.7"
        kotlin("plugin.jpa") version "1.9.25"
    }
    
    group = "com.example"
    version = "0.0.1-SNAPSHOT"
    
    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(17)
        }
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation("org.springframework.boot:spring-boot-starter-data-jpa")
            implementation("org.springframework.boot:spring-boot-starter-validation")
        implementation("org.springframework.boot:spring-boot-starter-web")
        implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
        implementation("org.jetbrains.kotlin:kotlin-reflect")
            implementation("org.springframework.boot:spring-boot-starter-logging")
            implementation("com.mysql:mysql-connector-j")
    }
    
    kotlin {
        compilerOptions {
            freeCompilerArgs.addAll("-Xjsr305=strict")
        }
    }
    
    allOpen {
        annotation("jakarta.persistence.Entity")
        annotation("jakarta.persistence.MappedSuperclass")
        annotation("jakarta.persistence.Embeddable")
    }
    
    tasks.named<ProcessResources>("processResources") {
        filesMatching("application.properties") {
            expand(project.properties)
        }
    }
    

    Note:

    My Config:

    tasks.named<ProcessResources>("processResources") {
        filesMatching("application.properties") {
            expand(project.properties)
        }
    }
    

    Your Config:

    tasks.named("processResources") {
        filesMatching("application.properties") {
            expand(project.properties)
        }
    }
    

    gradle.properties

    The settings in this file are configured to be used with expand(project.properties), which converts the ${XXX} placeholders in the application.properties file.

    serverPort=8080
    databaseUrl=jdbc:mysql://localhost:3306/demodb
    jdbcDriver=com.mysql.cj.jdbc.Driver
    databaseUser=demouser
    databasePassword=Passw0rd!
    dialect=org.hibernate.dialect.MySQL8Dialect
    

    DemoKotlinAppApplication.kt

    package com.example
    
    import org.springframework.boot.autoconfigure.SpringBootApplication
    import org.springframework.boot.runApplication
    
    @SpringBootApplication
    class DemoKotlinAppApplication
    
    fun main(args: Array<String>) {
        runApplication<DemoKotlinAppApplication>(*args)
    }
    

    Person.kt

    package com.example.model
    
    import jakarta.persistence.Entity
    import jakarta.persistence.GeneratedValue
    import jakarta.persistence.GenerationType
    import jakarta.persistence.Id
    
    @Entity
    data class Person(
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        val id: Long = 0,
        val name: String,
        val email: String
    )
    

    PersonRepository.kt

    package com.example.repository
    
    import com.example.model.Person
    import org.springframework.data.jpa.repository.JpaRepository
    import org.springframework.stereotype.Repository
    
    @Repository
    interface PersonRepository : JpaRepository<Person, Long> {
        fun findByNameContaining(name: String): List<Person>
    }
    

    PersonService.kt

    package com.example.service
    
    import com.example.model.Person
    import com.example.repository.PersonRepository
    import org.slf4j.Logger
    import org.slf4j.LoggerFactory
    import org.springframework.stereotype.Service
    
    @Service
    class PersonService(private val personRepository: PersonRepository) {
        private val logger: Logger = LoggerFactory.getLogger(PersonService::class.java)
    
        fun getAllPersons(): List<Person> {
            logger.debug("Fetching all persons from the database")
            return personRepository.findAll()
        }
    
        fun getPersonById(id: Long): Person? {
            logger.debug("Fetching person with ID: $id")
            return personRepository.findById(id).orElse(null)
        }
    
        fun createPerson(name: String, email: String): Person {
            logger.debug("Creating person with name: $name and email: $email")
            val person = Person(name = name, email = email)
            return personRepository.save(person)
        }
    
        fun getPersonsByName(name: String): List<Person> {
            logger.debug("Searching for persons with name containing: $name")
            return personRepository.findByNameContaining(name)
        }
    }
    

    PersonController.kt

    package com.example.controller
    
    import com.example.model.Person
    import com.example.service.PersonService
    import org.slf4j.Logger
    import org.slf4j.LoggerFactory
    import org.springframework.http.HttpStatus
    import org.springframework.web.bind.annotation.*
    
    @RestController
    @RequestMapping("/persons")
    class PersonController(private val personService: PersonService) {
        private val logger: Logger = LoggerFactory.getLogger(PersonController::class.java)
    
        @GetMapping
        fun getAllPersons(): List<Person> {
            logger.info("Fetching all persons")
            return personService.getAllPersons()
        }
    
        @GetMapping("/{id}")
        fun getPersonById(@PathVariable id: Long): Person? {
            logger.info("Fetching person with ID: $id")
            return personService.getPersonById(id)
        }
    
        @PostMapping
        @ResponseStatus(HttpStatus.CREATED)
        fun createPerson(@RequestBody person: Person): Person {
            logger.info("Creating new person: ${person.name}")
            return personService.createPerson(person.name, person.email)
        }
    
        @GetMapping("/search")
        fun getPersonsByName(@RequestParam name: String): List<Person> {
            logger.info("Searching persons with name: $name")
            return personService.getPersonsByName(name)
        }
    }
    

    logback-spring.xml

    <configuration>
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <logger name="org.springframework" level="INFO"/>
        <logger name="com.example" level="DEBUG"/>
    
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </configuration>
    

    application.properties

    The ${XXXX} placeholders in this file will be replaced with the contents of the gradle.properties file using expand(project.properties).

    spring.application.name=demo-kotlin-app
    server.port=${serverPort}
    
    spring.datasource.url=${databaseUrl}
    spring.datasource.username=${databaseUser}
    spring.datasource.password=${databasePassword}
    spring.datasource.driver-class-name=${jdbcDriver}
    spring.jpa.database-platform=${dialect}
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.format_sql=true
    spring.jpa.properties.hibernate.type.descriptor.sql.BasicBinder.log.level=TRACE
    
    logging.level.org.springframework=INFO
    logging.level.com.example=DEBUG
    logging.level.root=INFO
    logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
    

    Build

    My build instructions are as follows:

    gradle clean build
    

    Run

    This example requires MySQL, and its configuration is as follows:

    Before executing the following commands, make sure that MySQL is running.

    java -jar build/libs/demo-kotlin-app-0.0.1-SNAPSHOT.jar
    

    Test

    create data:

    curl -X POST http://localhost:8080/persons \
      -H "Content-Type: application/json" \
      -d '{
            "name": "John Doe",
            "email": "john.doe@example.com"
          }'
    

    read all data:

    curl -X GET http://localhost:8080/persons
    

    read id = 1 data

    curl -X GET http://localhost:8080/persons/1
    

    Check

    unzip demo-kotlin-app-0.0.1-SNAPSHOT.jar (demo-kotlin-app/build/libs/demo-kotlin-app-0.0.1-SNAPSHOT.jar)

    demo-kotlin-app/build/libs/demo-kotlin-app-0.0.1-SNAPSHOT/BOOT-INF/classes

    application.properties

    This file's ${XXXX} content has been converted, and it appears as expected.

    spring.application.name=demo-kotlin-app
    server.port=8080
    
    spring.datasource.url=jdbc:mysql://localhost:3306/demodb
    spring.datasource.username=demouser
    spring.datasource.password=Passw0rd!
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.format_sql=true
    spring.jpa.properties.hibernate.type.descriptor.sql.BasicBinder.log.level=TRACE
    
    logging.level.org.springframework=INFO
    logging.level.com.example=DEBUG
    logging.level.root=INFO
    logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
    

    My Environment

    $ gradle -v
    
    ------------------------------------------------------------
    Gradle 8.10
    ------------------------------------------------------------
    
    Build time:    2024-08-14 11:07:45 UTC
    Revision:      fef2edbed8af1022cefaf44d4c0514c5f89d7b78
    
    Kotlin:        1.9.24
    Groovy:        3.0.22
    Ant:           Apache Ant(TM) version 1.10.14 compiled on August 16 2023
    Launcher JVM:  17.0.12 (Eclipse Adoptium 17.0.12+7)
    Daemon JVM:    /home/demo/.sdkman/candidates/java/17.0.12-tem (no JDK specified, using current Java home)
    OS:            Linux 6.8.0-57-generic amd64