Kotlin Spring boot #ConfigurationProperties for list - spring

I want to read the yaml config files using Kotlin and below is my code:
application.yml
message:
messages:
- name: abc
type: aaa
size: 10
- name: xyz
type: bbb
size: 20
MessageConfig.kt
package com.example.demokotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
import java.math.BigDecimal
#ConfigurationProperties(prefix = "message")
#Configuration
class MessageConfig {
lateinit var messages: List<Message>
}
class Message {
lateinit var name: String
lateinit var type: String
lateinit var size: BigDecimal
}
Class to use the config:
package com.example.demokotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
#Component
class MessageService #Autowired constructor(private var messageConfig: MessageConfig) {
fun createMessage(): String {
println("in service........")
println(messageConfig.messages[0].name)
println(messageConfig.messages[0].type)
println(messageConfig.messages[0].size)
return "create a message......."
}
}
Looks like if the yaml file has array/list, Kotlin can't read it properly but it works without array.
I have exactly the same code and works for Java. Something wrong with my Kotlin code?

You are encountering this bug. Simply changing
lateinit var messages: List<Message>
to
var messages: MutableList<Message> = mutableListOf()
makes your code work. Here is a full working example.
edit (March 2019):
As of SB 2.0.0.RC1 and Kotlin 1.2.20, you can use lateinit or a nullable var.
Docs
edit (May 2020):
As of SB 2.2.0 you can use #ConstructorBinding along with #ConfigurationProperties to set val properties on a data class.
Using the original class as an example, you can now write it like so:
#ConstructorBinding
#ConfigurationProperties(prefix = "message")
data class MessageConfig(val messages: List<Message>) {
data class Message(
val name: String,
val type: String,
val size: BigDecimal
)
}

Is Fixed in Kotlin 1.3.11 with spring-boot 2.10, sample provided in MessageConfig.kt works now
#PropertySource("classpath:application.yml")
#ConfigurationProperties(value = "message")
class MessageConfig {
lateinit var messages: List<Message>
}
class Message {
lateinit var name: String
lateinit var type: String
lateinit var size: BigDecimal
}

Related

Why does Hibernate Validator ignore my custom annotations when written in Kotlin?

In a Kotlin project I am trying to get some Springboot Configuration properties JSR303 validated. Some custom validations seem to be necessary for me:
#Validated
#ConfigurationProperties("dtn")
class ExecutableAdapterConfiguration {
#FileFound // <-- custom
#Executable // <-- custom
lateinit var executable: Path
#NotEmpty
lateinit var user: String
#NotEmpty
lateinit var password: String
#NotEmpty
lateinit var productId: String
#NotEmpty
lateinit var version: String
}
#Constraint(validatedBy = [ExecutableValidator::class])
annotation class Executable(
val message: String = "Missing permissions to execute '${validatedValue}'",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class ExecutableValidator : ConstraintValidator<Executable, Path> {
override fun isValid(value: Path?, context: ConstraintValidatorContext): Boolean {
return value == null || !value.exists() || value.isExecutable()
}
}
#Constraint(validatedBy = [FileFoundValidator::class])
annotation class FileFound(
val message: String = "File '${validatedValue}' not found",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class FileFoundValidator : ConstraintValidator<Executable, Path> {
override fun isValid(value: Path?, context: ConstraintValidatorContext): Boolean {
return value != null && value.exists()
}
}
The path to the executable is supposed to be existing and, well, executable. When the Validator logic is tested (gradle bootRun, application.yaml points to dummy path), the two new validators are not executed. I was debugging into Hibernate Validator and when it lists found annotations, the custom ones are not added in the bean meta data definition. But when I do the same with a Java annotation definition, it is found and used.

How do I set and read properties in a SpringBoot application using Kotlin?

I'm a Java developer new to Kotlin and I'm trying to access values that I set in an application.yml file.
application.yml
q:
client:
apiUrl: https://app.abc.com/api/integr/v1.0
apiToken: abc
apiSecret: abc
authEmail: abc#example.com
sourceName: abc
This is my configuration class, which follows a similar pattern to Java.
#Component
#FeignClient(name = "q", url = "\${q.client.api-url}")
interface QClient {
#PostMapping("/user/get")
fun findUser(#RequestBody request: QRequest?):
QResponse<List<QUser?>?>
#PostMapping("/user/delete")
fun deleteUser(#RequestBody request: QRequest?): QResponse<DeleteResponse?>?
#Configuration
class QConfig {
#Value("\${q.client.apiToken}")
private val apiToken: String? = null
#Value("\${q.client.apiSecret}")
private val apiSecret: String? = null
#Value("\${q.client.authEmail}")
private val authEmail: String? = null
#Value("\${q.client.sourceName}")
private val sourceName: String? = null
fun createAuthRequest(): QAuth {
return QAuth(apiToken, apiSecret, authEmail, sourceName)
}
}
I don't want to assign null as default values for the instance variables, but Kotlin wants me to declare them like this to avoid null references.
I need to create an auth request and I'm calling the config class from the main class.
private fun generateRequest(email: String): QRequest {
val config = QClient.QConfig()
val auth = config.createAuthRequest()
return QRequest(auth, email)
}
But when debugging it just returns null values.
So after googling, I changed my approach and set all the key values into parameters of QConfig class like this:
#Configuration
class QConfig(
#Value("\${q.client.apiToken}") private val apiToken: String,
#Value("\${q.client.apiSecret}") private val apiSecret: String,
#Value("\${q.client.authEmail}") private val authEmail: String,
#Value("\${q.client.sourceName}") private val sourceName: String
) {
fun createAuthRequest(): QAuth {
return QAuth(apiToken, apiSecret, authEmail, sourceName)
}
}
The problem I faced here was it acts as a constructor and expects me to pass arguments while creating an instance for the QConfig class on the main class, which I wont have in the main class.
How can I get the values from the application.yml and access them as from instance variables?
You can use #ConfigurationProperties (ref)
#ConfigurationProperties("q.client")
#ConstructorBinding
data class ClientConfig(
val apiUrl: String, // variable name should be same as the one defined in application.yaml
val apiToken: String,
...other properties
)
#SpringBootApplication
#ConfigurationPropertiesScan
class SpringStackoverflowApplication {
#Autowired
private lateinit var clientConfig: ClientConfig
#EventListener(ApplicationReadyEvent::class)
fun doSomething() {
println("FOOBAR: $clientConfig")
}
}
fun main(args: Array<String>) {
runApplication<SpringStackoverflowApplication>(*args)
}
I solved this with Joffrey's reply, I used this format of config file
#Component
#Configuration
class QConfig {
#Value("\${q.client.apiToken}")
private val apiToken: String? = null
#Value("\${q.client.apiSecret}")
private val apiSecret: String? = null
#Value("\${q.client.authEmail}")
private val authEmail: String? = null
#Value("\${q.client.sourceName}")
private val sourceName: String? = null
fun createAuthRequest(): QAuth {
return QAuth(apiToken, apiSecret, authEmail, sourceName)
}
}
Then created the instance of QConfig like this on main class
#Autowired
val config = QConfig()
My bad, tried creating reference of class manually instead of using AutoWire. When started it pulled all the env variables passed on .yml file into the local variables.

Kotlin & Jackson: type error when specifying custom serialisation for a data class field

I have a Kotlin data class that is serialised to JSON in a Spring Boot project. I'd like to customise how date is formatted when serialising to JSON. The name of the field should be serialised using default rules. That expresses what I'd like to do:
class ZonedDateTimeSerialiser : JsonSerializer<ZonedDateTime>() {
#Throws(IOException::class)
fun serialize(value: ZonedDateTime, gen: JsonGenerator, serializers: SerializerProvider?) {
val parseDate: String? = value.withZoneSameInstant(ZoneId.of("Europe/Warsaw"))
.withZoneSameLocal(ZoneOffset.UTC)
.format(DateTimeFormatter.ISO_DATE_TIME)
gen.writeString(parseDate)
}
}
data class OrderNotesRequest(
#JsonSerialize(using = ZonedDateTimeSerialiser::class)
val date: ZonedDateTime = ZonedDateTime.now()
)
But I get a type error:
Type mismatch.
Required:
KClass<out (JsonSerializer<Any!>..JsonSerializer<*>?)>
Found:
KClass<ZonedDateTimeSerialiser>
I did try switching the parameter to annotation to contentUsing but the type error remained the same.
Following is working for me
object JacksonRun {
#JvmStatic
fun main(args: Array<String>) {
val objMapper = ObjectMapper().apply {
registerModule(KotlinModule())
}
val order = OrderNotesRequest()
println(objMapper.writeValueAsString(order))
}
}
data class OrderNotesRequest(
#JsonSerialize(using = ZonedDateTimeSerialiser::class)
val date: ZonedDateTime = ZonedDateTime.now()
)
class ZonedDateTimeSerialiser : JsonSerializer<ZonedDateTime>() {
#Throws(IOException::class)
override fun serialize(value: ZonedDateTime, gen: JsonGenerator, serializers: SerializerProvider?) {
val parseDate: String = value.withZoneSameInstant(ZoneId.of("Europe/Warsaw"))
.withZoneSameLocal(ZoneOffset.UTC)
.format(DateTimeFormatter.ISO_DATE_TIME)
gen.writeString(parseDate)
}
}
build.gradle.kts:
dependencies {
implementation("com.fasterxml.jackson.core:jackson-core:2.13.2")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.13.2")
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.2")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0")
}
Gives me output:
{"date":"2022-03-21T10:29:19.381498Z"}
Do make sure you have the correct import for JsonSerializer
import com.fasterxml.jackson.databind.JsonSerializer
and add override marker to serialize method

Spring Boot REST API - Deserialise a query attribute with square braces with Jackson

i am calling a legacy rest endpoint. There i have to deserialise a query parameter in a rest endpoint with square braces author[name].
Question: Is it possible to deserialise the attribute author name into the AuthorDto(spring boot + kotlin)?
import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.JsonProperty
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
#RestController
class AuthorController {
#GetMapping("/author/{id}")
fun getAuthorFromLegacyApi(
#PathVariable("id") id: Long,
authorDto: AuthorDto?
) = ResponseEntity.ok(authorDto)
}
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
data class AuthorDto(
#RequestParam(name = "author[name]")
val name: String?,
// long list of different query parameters
val country: String? = null,
)
code: https://github.com/nusmanov/spring-boot-validation-hibernate/blob/main/src/main/kotlin/com/example/bookvalidation/author/AuthorController.kt
to test: GET http://localhost:8080/author/5?author[name]=Joe
there is a junit test see above
Yes, it is possible, if you annotate authorDto with #RequestParam("author[name]"):
#GetMapping("/author/{id}")
fun getAuthorFromLegacyApi(#PathVariable("id") id: Long, #RequestParam("author[name]") authorDto: AuthorDto) = ResponseEntity.ok(authorDto.name)
How about using the map.
#GetMapping("/author/{id}")
fun getAuthorFromLegacyApi(#PathVariable("id") id: Long, #RequestParam authorDto: Map<String, String>): String {
return "Hello $id ${authorDto["author[name]"]} ${authorDto["currency"]}"
}
I tried with author/5?author%5Bname%5D=Tom&currency=INR got the response of Hello 5 Tom INR

Spring nested ConfigurationProperties and Kotlin

I want to use nested ConfigurationProperties in spring boot which is works in java but not in kotlin. I have got the following lines in my app property:
cert.signing-cert.filePath=truststore.jks
cert.signing-cert.password=xxxx
cert.private-signing.filePath=sign_test.p12
cert.private-signing.password=xxxx
cert.private-encrpytion.filePath=encryption_test.p12
cert.private-encrpytion.password=xxx
And I created this structure to handle these properties:
#Component
#ConfigurationProperties("cert")
class CertConfig {
lateinit var signingCert: SigningCert
lateinit var privateSigning: PrivateSigning
lateinit var privateEncrpytion: PrivateEncrpytion
class SigningCert {
lateinit var filePath: String
lateinit var password: String
}
class PrivateSigning {
lateinit var filePath: String
lateinit var password: String
}
class PrivateEncrpytion {
lateinit var filePath: String
lateinit var password: String
}
}
I added kapt plugin and dependecy to my build.gradle.kts
kotlin("kapt") version "1.3.50"
...
kapt("org.springframework.boot:spring-boot-configuration-processor")
implementation("org.springframework.boot:spring-boot-configuration-processor")
And I got this exception when I run this code:
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property signingCert has not been initialized
I forgot to initialize the properties.
var signingCert: SigningCert = SigningCert()
var privateSigning: PrivateSigning = PrivateSigning()
var privateEncrpytion: PrivateEncrpytion = PrivateEncrpytion()

Resources