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

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.

Related

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

How to save field as JSON with Spring Data R2DBC and Postgres

There are any way to save some field of entity as Json with spring-data-r2dbc?
Example:
#Table("A")
class A {
#Id
var id: String = "1"
var some: MutableMap<String, String> = mutableMapOf()
}
And table:
create table A (
id varchar(255) not null primary key,
some jsonb
)
I've looked at Convertors of Spring data R2DBC, but It is necessery to write separate convertor for every class. Does it possible to generate converters to Json dynamically in runtime for all classes inherited from some special interface or annotated by specific annotation?
Thanks!
annotation class StoreJson
...
#Bean
fun converters() = R2dbcCustomConversions(
Reflections().getTypesAnnotatedWith(StoreJson::class.java).map { clz ->
mutableListOf(
#WritingConverter
object : GenericConverter {
override fun getConvertibleTypes() = setOf(GenericConverter.ConvertiblePair(clz, Json::class.java))
override fun convert(source: Any?, p1: TypeDescriptor, p2: TypeDescriptor) =
Json.of(objectMapper.writeValueAsString(source))
},
#ReadingConverter
object : GenericConverter {
override fun getConvertibleTypes() = setOf(GenericConverter.ConvertiblePair(Json::class.java, clz))
override fun convert(source: Any?, p1: TypeDescriptor, p2: TypeDescriptor) =
objectMapper.readValue((source as Json).asString(), clz)
}
)
}.flatten().toMutableList()
)

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