Gradle with Kotlin DSL expecting Property<String> error - kotlin

I need to set this
avro {
templateDirectory = "/path/to/velocity/templates"
}
But getting error stating that's it's expecting Property<String> not a String.
Not clear on how to set this value as Property<String>?

If you're using the gradle-avro-plugin, do note that the configuration should be set as below for Kotlin DSL
avro {
templateDirectory.set("/path/to/velocity/templates" as String?)
}
The syntax for all the configurations are as below:
avro {
isCreateSetters.set(true)
isCreateOptionalGetters.set(false)
isGettersReturnOptional.set(false)
fieldVisibility.set("PUBLIC_DEPRECATED")
outputCharacterEncoding.set("UTF-8")
stringType.set("String")
templateDirectory.set(null as String?)
isEnableDecimalLogicalType.set(true)
dateTimeLogicalType.set("JSR310")
}
The reference is here.

Related

How to create a custom deserializer on spring kafka?

I've created a consumer to receive messages from a Kafka topic but I'm parsing the message manually, is there a way to parse automatically with a custom deserializer for this single topic consumer?
// I've tried to replace the second "String" with "MyMessage" but it didn't work
// e.g: onMessage(message: ConsumerRecord<String, MyMessage>)
fun onMessage(message: ConsumerRecord<String, String>) {
val myMessage = try {
jacksonObjectMapper.readValue<MyMessage>(message.value())
} catch (ex: Exception) {
logger.error(ex.message)
null
}
myMessage?.let{ // do stuff }
}
The expected result is:
fun onMessage(message: ConsumerRecord<String, MyMessage>) {
// do something with message.value() parameter that will be for sure a MyMessage instance.
}
In case of a parse error can I set a callback to handle this error?
Yes; just implement your own and add it to the consumer configuration value.deserializer.
https://kafka.apache.org/documentation/#consumerconfigs_value.deserializer
If you are using Spring Boot:
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.integration.spring.kafka.consumer.value-deserializer
If configured this way, it is not managed by Spring; if you want Spring to manage it, add it to the DefaultKafkaProducerFactory via a constructor or setter.
Spring for Apache Kafka provides a JSON deserializer:
https://docs.spring.io/spring-kafka/docs/current/reference/html/#json-serde

Calling PCEnhancerTask from Kotlin in Gradle

I need to call the OpenJPA PCEnhancerTask class from Kotlin instead of Groovy. The following code works just fine (based on a previous solution documented here):
def openJPAClosure = {
def entityFiles = sourceSets.main.output.classesDirs.asFileTree.matching {
include 'com/company/persist/*Entity.class'
}
println "Enhancing with OpenJPA:"
entityFiles.getFiles().each {
println it
}
ant.taskdef(
name : 'openjpac',
classpath : sourceSets.main.runtimeClasspath.asPath,
classname : 'org.apache.openjpa.ant.PCEnhancerTask'
)
ant.openjpac(
classpath: sourceSets.main.runtimeClasspath.asPath,
addDefaultConstructor: false,
enforcePropertyRestrictions: true) {
entityFiles.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
}
}
I was looking at the documentation on how to call Ant tasks from Gradle but I could not translate all the necessary steps using the GroovyBuilder. So instead I tough of calling the PCEnhancer directly:
fun openJPAEnrich() {
val entityFiles = sourceSets.main.get().output.classesDirs.asFileTree.matching {
include("com/company/persist/*Entity.class")
}
println("Enhancing with OpenJPA, the following files...")
entityFiles.getFiles().forEach() {
println(it)
}
org.apache.openjpa.ant.PCEnhancerTask.main(asList(entityFiles))
}
But it complains about not being able to find org.apache.openjpa in the classpath (but is it listed as a compilation dependency)
My questions are:
What is the correct way to translate the original Groovy construct to Kotlin using groovyBuilder
If is not possible, how you can correctly call PCEnhancer from Kotlin in Gradle?
So I ended making it work with a custom JavaExec Gradle task:
tasks.create<JavaExec>("openJPAEnrich") {
val entityFiles = sourceSets.main.get().output.classesDirs.asFileTree.matching {
include("com/company/persist/*Entity.class")
}
println("Enhancing with OpenJPA, the following files...")
entityFiles.files.forEach() {
println(it)
}
classpath = sourceSets.main.get().runtimeClasspath
main = "org.apache.openjpa.enhance.PCEnhancer"
args(listOf("-enforcePropertyRestrictions", "true", "-addDefaultConstructor", "false"))
entityFiles.forEach { classFile -> args?.add(classFile.toString())}
}
I was tempted to build my own custom Gradle task but for this felt overkill.
Thanks.
--Jose

Is this the correct way to process results through the Spring Integration Flow?

I am currently working on a personal project - in which I need my Spring application to take queries from an EMQX (MQTT Server) and query its data for corresponding results, and then push the results to a topic with the query UUID.
This is working - after many hours understanding how the Spring Integration framework works. But I think the way in which the handler is using "block" is incorrect - and not in keeping with the manner in which the Integration Flow should operate. Whilst this works I do want to make sure it is being done properly - out of respect for the work - and to avoid future issues.
The code snippet below should be enough to understand what it is that I'm trying to achieve - and where the potential issue lies.
#Bean
fun mqttInFlow() : Publisher<Message<String>> {
return IntegrationFlows.from(inbound())
.handle<String> { payload, headers ->
val emotionalOutput: EmotionalOutput = gson.fromJson(payload, EmotionalOutput::class.java)
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map {
MessageBuilder.withPayload(gson.toJson(it))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, "query/" + it.query_uuid).build()
}.block()
}
.channel(outgoingChannel())
.toReactivePublisher()
}
EDIT - Thanks for the advice - here is what I understood to be the potential edit for the Kotlin DSL solution - this is now producing an error - complaining that an output-channel or replyChannel was not available - nothing outside of the this function has been changed.
#Bean
fun newMqttInFlow() =
integrationFlow (inbound()) {
wireTap {
handle<String> { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid)
}
}
}
}
channel("outgoingChannel")
}
Exception is :
exception is org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
Although I have many years experience with Java - this approach is new - so thank you very much for your assistance. It's appreciated. If the whole class would be useful - I can post that.
EDIT
Here is the Configuration file - which might give a better insight into what might be causing this secondary error -
021-03-28 21:59:48.008 ERROR 84492 --- [T Call: divnrin] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessageHandlingException: error occurred in message handler [bean 'mqttOutbound'; defined in: 'class path resource [io/divnr/appserver/configuration/MQTTConfiguration.class]'; from source: 'org.springframework.core.type.classreading.SimpleMethodMetadata#4a9419d7']; nested exception is java.lang.IllegalArgumentException: This default converter can only handle 'byte[]' or 'String' payloads; consider adding a transformer to your flow definition, or provide a BytesMessageMapper, or subclass this converter for reactor.core.publisher.MonoMapFuseable payloads, failedMessage=GenericMessage [payload=MonoMapFuseable, headers={mqtt_receivedRetained=false, mqtt_id=0, mqtt_duplicate=false, id=c5a75283-c0fe-ebac-4168-dabddd989da9, mqtt_receivedTopic=source/d9e50e8f-67e0-4505-7ca2-4d05b1242207, mqtt_receivedQos=0, timestamp=1616961588004}]
at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:192)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:65)
at
The full class is provided here.
#Configuration
#EnableIntegration
#IntegrationComponentScan
class MQTTConfiguration(val emotionalPrintService: EmotionalPrintService,
val gson: Gson,
val applicationConfiguration: ApplicationConfiguration) {
#Bean
fun mqttServiceFactory() : MqttPahoClientFactory {
return DefaultMqttPahoClientFactory().apply {
connectionOptions = MqttConnectOptions().apply {
serverURIs = arrayOf<String>(applicationConfiguration.mqttServerAddress)
}
}
}
#Bean
fun newMqttInFlow() =
integrationFlow (inbound()) {
handle<String> { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid).build()
}
}
}
channel(outgoingChannel())
}
#Bean
#ServiceActivator(requiresReply = "false", inputChannel = "outgoingChannel")
fun mqttOutbound(): MessageHandler {
val messageHandler = MqttPahoMessageHandler("divnrout", mqttServiceFactory())
messageHandler.setAsync(true)
return messageHandler
}
#Bean
fun outgoingChannel() : FluxMessageChannel {
return FluxMessageChannel()
}
#Bean
fun inbound(): MessageProducerSupport {
return MqttPahoMessageDrivenChannelAdapter("divnrin", mqttServiceFactory(),
"source/" + applicationConfiguration.sourceUuid).apply {
setConverter(DefaultPahoMessageConverter())
setQos(1)
}
}
}
You indeed don't need that block() in the end of your handle(). You just can return the Mono from that emotionalPrintService.populateEmotionalOutput() and the framework will take for you about the proper subscription and back-pressure handling.
What you would need yet is to make that outgoingChannel() as a FluxMessageChannel.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/reactive-streams.html#reactive-streams
Plus consider to move your IntegrationFlow solution to the proper Kotlin DSL: https://docs.spring.io/spring-integration/docs/current/reference/html/kotlin-dsl.html#kotlin-dsl
Also: when it is a FluxMessageChannel in the end of flow, there is no reason to worry about a toReactivePublisher() - the FluxMessageChannel is a Publisher<Message<?>> by itself.
UPDATE
The problem is here:
handle<String>( { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid).build()
}
}
}) { async(true) }
See that async(true) option. Unfortunately in the current version we don't let it to process reactive reply in the reactive manner by default. You have to say that you'd like to be an async at this end-point. So, your Publisher reply and and FluxMessageChannel as an output is going to do the proper trick.

Converting Groovy tasks build.gradle to kotlin kts file

I am trying to convert my build.gradle file in Groovy to a kotlin .kts file, I have some tasks in the gradle file that I dont know how to convert correctly
task androidJavadocs(type: Javadoc) {
failOnError = false
source = android.sourceSets.main.java.srcDirs
ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
classpath += files(ext.androidJar)
exclude '**/R.html', '**/R.*.html', '**/index.html'
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
I dont quite understand how to use the "type", I was going down this path
val androidJavadocs by tasks.withType<Javadoc>(){
}
but its giving me the error
Type 'DomainObjectCollection<Javadoc!>' has no method
'getValue(Build_gradle, KProperty<*>)' and thus it cannot serve as a
delegate
How do I convert those groovy tasks to kotlin tasks correctly?
You can use the named() method to configure existing tasks and the register() method to create new ones.
tasks {
register<Javadoc>("androidJavadocs") {
// Task's body here
}
}
// or
tasks.register<Javadoc>("androidJavadocs") {
// Task's body here
}
If you want to use Kotlin delegated properties
val androidJavadocs by tasks.registering(Javadoc::class) {
// Task's body here
}
The full answer to your question
val androidJavadocs by tasks.registering(Javadoc::class) {
isFailOnError = false
source = android.sourceSets.main.java.srcDirs
ext["androidJar"] = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
classpath += files(ext["androidJar"])
exclude("**/R.html", "**/R.*.html", "**/index.html")
}
val androidJavadocsJar by tasks.registering(Jar::class) {
classifier = "javadoc"
dependsOn(androidJavadocs)
from(androidJavadocs.get().destinationDir)
}

Kotlin type mismatch when using RxJava retryWhen operator

I am trying create Observable, which will retryWhen when the network connection will be established.
I've created subject:
private val retrySubject = PublishSubject.create<Unit>()()
And I am using it like this:
private fun publishNetworkReconnection() {
compositeDisposable?.add(
connectionHelper.observeConnection()
.subscribe {connected: Boolean
if(connected){
retrySubject.onNext(null)
}
}
)
}
Then I am trying to use it in my retryWhen operator:
val disposable =
Flowable.interval(0, UPDATE_INTERVAL, TimeUnit.SECONDS, Schedulers.io())
.onBackpressureDrop()
.flatMapCompletable {
revocationRepository.sync(event.id)
}
.retryWhen { retryHandler -> retryHandler.flatMap({ nothing -> retrySubject.asObservable() }) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ }, { Timber.e(it, "Unable to sync blacklist") })
compositeDisposable?.add(disposable)
}
How to use it properly in this case?
I am getting this error in Android Studio:
Type mismatch. Required: Publisher<< out (???..???) >>! Found: Observable<
Void
!>!
The problem is, you are mixing RxJava 1 - Subject.asObservable and RxJava 2 - Flowable.
The signature of retryWhen from RxJava 2 is:
Flowable<T> retryWhen(Function<? super Flowable<Throwable>,? extends Publisher<?>> handler)
so lambda inside retryWhen should return something that extends Publisher. Instead, you are returning Subject<Unit> which is converted to Observable<Void>, because apparently your Subject is from RxJava 1. And - because of RxJava version difference - obviously it doesn't extend Publisher.
So error message is correct, retryWhen expects Publisher from RxJava 2, but you give Obervable from RxJava 1. You didn't notice different packages, because they are not included in message.
The main issue is mixing code from RxJava 1 and RxJava 2 which is never good.