Spring nested ConfigurationProperties and Kotlin - spring

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()

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

Unit test POST API in spring-boot + kotlin + Junit

I'm pretty new to spring boot and kotlin. I've started with one basic app from net and writing unit test, but I'm getting following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: articleRepository.save(article) must not be null
Let me show you the code: Entity Class
#Entity
data class Article (
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
#get: NotBlank
val title: String = "",
#get: NotBlank
val content: String = ""
)
controller:
#PostMapping("/articles")
fun createNewArticle(#Valid #RequestBody article: Article) : Article {
return articleRepository.save(article)
}
Repository:
#Repository
interface ArticleRepository : JpaRepository<Article, Long>
Test File:
RunWith(SpringRunner::class)
#SpringBootTest
class KotlinDemoApplicationTests {
lateinit var mvc: MockMvc
#InjectMocks
lateinit var controller: ArticleController
#Mock
lateinit var respository: ArticleRepository
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
mvc = MockMvcBuilders.standaloneSetup(controller).setMessageConverters(MappingJackson2HttpMessageConverter()).build()
}
#Test
fun createBlog() {
var article = Article(1, "Test", "Test Content")
var jsonData = jacksonObjectMapper().writeValueAsString(article)
mvc.perform(MockMvcRequestBuilders.post("/api/articles/").contentType(MediaType.APPLICATION_JSON).content(jsonData))
.andExpect(MockMvcResultMatchers.status().isOk)
.andDo(MockMvcResultHandlers.print())
.andReturn()
}
}
When I'm running this test file, getting error mentioned above.
Please help me with this.
The problem is your ArticleRepository mock.
While you correctly inject it into your Controller, you're not specifiying what a call to save should return. It therefore returns null, which is not allowed in Kotin because you specified it as non-optional.
Either you allow your controller's createNewArticle to return null, by adding a ?, that is changing its signature to
fun createNewArticle(#Valid #RequestBody article: Article) : Article? {...}
Or you set-up the mock so that it does not return null, but an article.
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
...
`when`(respository.save(any())
.thenReturn(Article()) // creates a new article
}
(Alternatively, there's also Mockito's returnsFirstArg() in case you don't want to invoke the construtor.)
Note that using any() in this case will only work if you're using mockito-kotlin
If you don't want to use it, check this answer

Kotlin Spring boot #ConfigurationProperties for list

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
}

Resources