Kotlin support for JDBI SqlObject gives UnsupportedOperationException - kotlin

Extending the Kotlin equivalent of the Dropwizard JDBI3 setup listed in the official Dropwizard documentation, I fail to get automatic parameter binding without #Bind and the Kotlin-specific mapping magic for JDBI to work as shown in Kotlin support for SqlObject. Instead of this...
data class Thing(val id: Int, val name: String,
val nullable: String?,
val nullableDefaultedNull: String? = null,
val nullableDefaultedNotNull: String? = "not null",
val defaulted: String = "default value")
interface ThingDao {
#SqlUpdate("insert into something (id, name) values (:something.id, :something.name)")
fun insert(something: Thing)
#SqlQuery("select id, name from something")
fun list(): List<Thing>
...
}
..I always have to do:
interface ThingDao {
#SqlUpdate("insert into something (id, name) values (:id, :name)")
fun insert(#Bind("id") id: Int?, #Bind("name") name: String)
#SqlQuery("select id, name from something")
fun list(): List<Thing>
...
}
Gradle has these JDBI-specific settings:
...
compile "io.dropwizard:dropwizard-jdbi3:1.3.5"
compile "org.jdbi:jdbi3-sqlobject:3.3.0"
compile "org.jdbi:jdbi3-postgres:3.3.0"
compile "org.jdbi:jdbi3-kotlin:3.3.0"
compile "org.jdbi:jdbi3-kotlin-sqlobject:3.3.0"
....
The Dropwizard application has the following run configuration:
override fun run(configuration: MyConfig, environment: Environment) {
val factory = JdbiFactory()
val jdbi = factory.build(environment, configuration.database, "postgresql")
// This is said to install all available plugins and is thus redundant.
// I have tried to include various combinations of the following in
// some desperation. None work.
jdbi.installPlugins()
jdbi.installPlugin(SqlObjectPlugin()) // This...
jdbi.installPlugin(KotlinPlugin())
jdbi.installPlugin(KotlinSqlObjectPlugin()) // ..and this alone are said to do the job
...
Otherwise, everything seems to run just fine with custom UUID mappings, Jackson Kotlin data object mapping and such.
The result of using :something.id in particular always is:
java.lang.UnsupportedOperationException: No argument factory registered for 'Thing(...'

The solutions suggested in the comments all work. Thanks very much! The trouble seemingly arose in an unfortunate sequence of adding the right libraries and fixing the code in a process that left the impression of things not working (I am still not a fan of annotation-mania).
Summary for anyone that might find anything useful in it:
Adding #BindBean made data classes work as arguments indeed.
Leaving out #Bind for arguments apparently worked for quite a while, which I just didn't notice...
..possibly in parts as I incorrectly tried to use it with data classes, too: #Bind("something") something: Thing instead of the correct (but still unnecessary) #BindBean("something") something: Thing
Given this, removing #Bind and #BindBean worked (at least with the 1001st gradle clean build).
I removed compile "org.jdbi:jdbi3-sqlobject:$jdbi_version", which either way doesn't seem to have any effect on things working properly (as Dropwizard is said to reference it already).
For the record, these are the dependencies that seem to work for me right now:
buildscript {
ext.kotlin_version = '1.2.51'
ext.dropwizard_version = '1.3.5'
ext.jdbi_version = '3.3.0'
...
}
...
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "io.dropwizard:dropwizard-core:$dropwizard_version"
compile "io.dropwizard:dropwizard-jdbi3:$dropwizard_version"
compile "io.dropwizard:dropwizard-auth:$dropwizard_version"
compile "io.dropwizard:dropwizard-views-freemarker:$dropwizard_version"
// This is to serve static content from resources/static
compile "io.dropwizard:dropwizard-assets:$dropwizard_version"
compile 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.4'
compile 'com.fasterxml.jackson.module:jackson-modules-java8:2.9.4'
// Removed as redundant
// compile "org.jdbi:jdbi3-sqlobject:$jdbi_version"
compile "org.jdbi:jdbi3-postgres:$jdbi_version"
// So, these are still required as Dropwizard doesn't know Kotlin
compile "org.jdbi:jdbi3-kotlin:$jdbi_version"
compile "org.jdbi:jdbi3-kotlin-sqlobject:$jdbi_version"
// Database
compile 'org.postgresql:postgresql:42.1.4'
// ...
testCompile "io.dropwizard:dropwizard-testing:$dropwizard_version"
// ...
}

Related

Room returns java.lang.Object for all my suspended Dao functions

I have a project with a Room database, which was working fine, and now for some reason I can't find out, it isn't anymore.
I am getting the following error when compiling:
C:\(...)\FlightDao.java:18: error: Not sure how to convert a Cursor to this method's return type (java.lang.Object).
public abstract java.lang.Object getFlightById(int id, #org.jetbrains.annotations.NotNull()
This is from a class that is generated from my Dao:
#Dao
interface FlightDao {
#Query("SELECT * FROM FlightData WHERE DELETEFLAG == 0 AND flightID = :id LIMIT 1")
suspend fun getFlightById(id: Int): FlightData?
}
Now, if I change the function to not suspend, return a Flow and get its first result, it doesnt give an error. But, according to documentation I should do it the way I did, as it is a one-shot operation.
My gradle file has these (entire file here):
apply plugin: 'kotlin-kapt'
(...)
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
and now I am at a loss for why this isn't working. Only things I find are about suspend not being compatible with LiveData, but I don't use LiveData. As far as I can tell, I did it exactly as in the documentation. Anybody has any ideas? (I did try clean and rebuild)
Kotlin version 1.7.0
Room version 2.4.2
I fixed this by switching from kapt to ksp (link)
Still not a clue why kapt didn't want to do it anymore, but meh, it works now :)
new gradle:
plugins {
id 'com.google.devtools.ksp' version '1.7.0-1.0.6'
}
(...)
dependencies {
implementation "androidx.room:room-runtime:$room_version"
ksp "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
(...)
}
try to delete ? in FlightData for null safety
second notice you must write query for condition with one equal sign not twice like programming
use this
#Query("SELECT * FROM FlightData WHERE DELETEFLAG = 0 AND flightID = :id LIMIT 1")

Kotlin - IntelliJ cannot resolve the reference (from synthetic class)

I am using Kotlin and I have the kotlinc compiler plugin (using arrow-meta library) in place which changes the .class by adding for example new properties or new method etc during kotlin compilation time. for example, the original source Kotlin A.kt is like below
#MetaData
data class A (val x: String, val y: String)
after applying compiler plugin, the .class will be altered to (from source perspective), basically I will add implicitly MetaData into the primary constructor for all class so long as it is with annonation #MetaData in place, plus a new method fun getMetaData() generated
data class A(val x: String, val y:String, val myMeta: MetaData) {
fun getMetaData() {
//some logic check
return myMeta
}
}
now when it comes to use the new synthetic "class" manipulated as below, IntelliJ complains it cannot find resolve A (it has only the constructor with 2 parameters not 3) and cannot resolve the the synthetic method getMetaData() either.
val x = A("ba", "fo", MetaData(..))
val y = x.getMetaData()
can somebody shed some light on it?
I know lombok seems no problem with it after adding its #Getter annotation for example into Java source code, IntelliJ can recognize its getXXX method (which is generated by lombok).
I don't know how to implement the same for my case for kotlin language. please include the detailed steps if possible.

Create a Gradle function for dependencies block in Kotlin

Currently, I'm creating a function, which is available for the dependencies block in Groovy with:
project.dependencies.ext.foo = { String value ->
project.files(extension.getFooDependency(project).jarFiles).asFileTree
}
Thanks to that, I'm able to do:
afterEvaluate {
dependencies {
compileOnly foo('junit')
}
}
I'm converting the Groovy code to Kotlin, and I'm wondering how to rewrite this foo extension.
I've ended up with:
project.dependencies.extensions.extraProperties.set("foo", Action { value: String ->
project.files(extension.getIdeaDependency(project).jarFiles).asFileTree
})
After calling foo('junit'), I get the following exception:
> Could not find method foo() for arguments [junit] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
I do not think that would work the same way in Kotlin DSL. Instead, you may declare a Kotlin extension function somewhere in the project. Then calling it would include all necessary receivers to you.
For multiple projects, I would recommend using a buildSrc project. Declarations there are visible to all project files below.
Speaking about Groovy and Kotlin support, I would do something like that:
private fun getFooImpl(scope: getFooImpl, name: String) { /*here is the implementation */ }
fun DependencyHandlerScope.getFoo(name:String) = getFooImpl(this, name)
//in Groovy
project.dependencies.extensions.extraProperties.set("foo", {getFooImpl(..)})
The same code could fit into a plugin as well. A more generic way could be to register a custom DLS extension, so to allow a custom block-like thisIsMyPlugin { .. } in the Gradle DSL and define all necessary helper functions in the extension class. Here the downside is in forcing users to wrap their code into the thisIsMyPlugin block.

kotlin : cannot parse string to enum with Jackson

I use the lib jackson-module-kotlin to parse string of json into object.
My issue is when I parse a string into an enum , and when I launch with intellij, I have this stack trace:
Caused by: kotlin.reflect.jvm.internal.KotlinReflectionInternalError:
Reflection on built-in Kotlin types is not yet fully supported. No
metadata found for public final val name: kotlin.String defined in
kotlin.Enum[DeserializedPropertyDescriptor#212b316a]
I don't have this issue when I launch with maven.
I use kotlin 1.1.51, with intellij kotlin plugin 1.2.0-release-IJ2017.3-1, I target a JVM 1.8, and i use jackson-module-kotlin version 2.8.7
what should I do?
enum class CType { DEAL, FILE }
data class Code(val code: String, val type: CType)
fun testDeserialization() {
val mapper = jacksonObjectMapper()
// following line throws an exception:
mapper.readValue("""{"code":"A","type":"DEAL"}""", Code::class.java)
}
The only way I got it working is by adding additional #JvmStatic annotation. I had mapper.registerModule(new KotlinModule()); and all, nothing worked but this:
package nc.features.algo.model
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
enum class LHStatus (
#get:JsonValue val code: Int
) {
LH_POS_OVU_WAITING(1),
LH_NEG_OVU_WAITING(2),
;
companion object {
#JsonCreator
#JvmStatic
fun deser(code: Int?): LHStatus? {
if (code == null) return null
for (i in values()) {
if (i.code == code) return i
}
return null
}
}
}
You have to do a few things.
Update Jackson dependencies to the latest version (right now, 2.9.4).
Update Kotlin version to a version equal or greater than 1.3.0.
Be sure to add the following dependencies to your build.gradle:
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version"
... then you call registerKotlinModule() on your Jackson ObjectMapper and the code of your enum should be just like this:
enum class CType(#get:JsonValue val value: String) {
DEAL("deal"),
FILE("file");
companion object {
#JsonCreator
fun fromString(value: String): CType? {
for (type in CType.values()) {
if (type.name.equals(value, true)) {
return gender
}
}
return null
}
}
}
Intellij is most likely using the kotlin compiler version 1.2.0 (from the plugin) and it doesn't seem to support reflection properly.
I suggest you do one of the following:
Upgrade your kotlin version in maven and the intellij kotlin plugin to newer versions (e.g. 1.2.30). If you do that, you also have to update jackson-module-kotlin to >= 1.9, since there is an incompatibility with kotlin 1.2 (see here).
Set the kotlin compiler version to 1.1 in Intellij Idea settings.
It is generally a good idea to use the same version of kotlin in Intellij Idea and maven/gradle.
You need to use the Kotlin module for Jackson that is compatible with Kotlin 1.2.x, this includes minimally these three versions of the module:
2.9.4.1 (works with any 2.9.x of Jackson, but best to use most recent)
2.8.11.1 (for Jackson 2.8.x)
2.7.9.1 (for Jackson 2.7.x)
Otherwise, you will run into a problem with library mismatches.
The jackson-module-kotlin homepage lists these as the current versions, but they are likely to change and you can check the various Maven repository search engines to see which library versions are available and which dependencies they have on Kotlin to find matching versions.
Also note you can import the extensions for the ObjectMapper class and use reified types, so instead of:
val something = mapper.readValue("""{"code":"A","type":"DEAL"}""", Code::class.java)
you would have:
val something: Code = mapper.readValue("""{"code":"A","type":"DEAL"}""")
or alternatively:
val something = mapper.readValue<Code>("""{"code":"A","type":"DEAL"}""")
It is usually bad to use the erased type (i.e. Whatever::class.java) since this does not work for anything with generic type parameters, and using reified types also works nicely when deserializing into collections.

How to use DeprecationLevel.ERROR

Let's say I am writing a library and have a class that looks something like this (contrived example, but shows self reference:
import java.util.logging.Logger
class MyClass(private val myNum: Int) {
companion object {
private val LOG = Logger.getLogger(MyClass::class.java.canonicalName)
}
constructor() : this(1337)
fun addTo(num: Int): Int {
LOG.fine { "Adding num $num to $myNum" }
return myNum + num
}
fun doubleAdd(num: Int): Int = 2 * addTo(num)
}
Now, I have decided that I want to deprecate this class and have my consumers move on to to better things, so I give them a warning.
#Deprecated("Don't use!", level = DeprecationLevel.WARNING)
class MyClass(private val myNum: Int) {
// ...
}
Now, after some more time I'd like to increase the strictness with my deprecation. I still want the library to be binary compatible, so I do not remove the code I see that there is the DeprecationLevel.ERROR available, so I try to use it.
#Deprecated("Don't use!", level = DeprecationLevel.ERROR)
class MyClass(private val myNum: Int) {
// ...
}
Except now, when I try to compile my own project, I get compiler errors:
e: /path/to/project/src/main/kotlin/MyClass.kt: (7, 44): Using 'MyClass' is an error. Don't use!
e: /path/to/project/src/main/kotlin/MyClass.kt: (10, 23): Using 'MyClass' is an error. Don't use!
This is on both the MyClass reference and the this primary constructor reference.
What is the point of DeprecationLevel.ERROR? If I am using it wrong, what is the intended use, and how do I use it?
NOTE: This whole example was done with Kotlin 1.2.21
It does exactly what is described in the documentation: DeprecationLevel
ERROR means usage of that code generates an error in the compiler. This is when you know using the code is going to cause problems and you'd rather crash the compilation, even if that code compiled fine previously.
There is also the HIDDEN deprecation level which does what you describe. It 'hides' the annotated element from the compiler but leaves it in the binary output. This will still cause a compilation error in your project because it is meant for binary compatibility, not newly compiled code.