Binding custom data type transmit null to converter - kotlin

I'm using Jooq and Kotlin in my project. I have object EventEnvelope in which field of type Event is composed. I want to store this field as JSON in my DB (postgres). I prepared jooq custom datatype bindings and converter as it is described here -> https://www.jooq.org/doc/3.10/manual/code-generation/custom-data-type-bindings/
Below I paste converter, binding and gradle generator code.
My questions are:
Is it ok to use kotlin non null types with jooq bindings?
Is this configuration ok? What should I change?
When I want to store value my converter gets null in from func. I don't why is that.
I am out of ideas what should I do to fix it.
class JSONEventConverter constructor(
private val objectMapper: ObjectMapper,
private val schemaMatcher: SchemaMatcher
) : Converter<Any, Event> {
override fun from(databaseObject: Any): Event {
return schemaMatcher.parse(databaseObject.toString())
}
override fun to(userObject: Event): Any {
return objectMapper.writeValueAsString(userObject)
}
override fun fromType(): Class<Any> {
return Any::class.java
}
override fun toType(): Class<Event> {
return Event::class.java
}
companion object {
fun create(): JSONEventConverter {
return JSONEventConverter(jacksonObjectMapper(),
SchemaMatcher.create())
}
}
}
class PostgresJSONEventBinding : Binding<Any, Event> {
override fun register(ctx: BindingRegisterContext<Event>?) {
ctx!!.statement().registerOutParameter(ctx.index(), Types.VARCHAR)
}
override fun sql(ctx: BindingSQLContext<Event>?) {
ctx!!.render().visit(DSL.`val`(ctx.convert(converter())
.value())).sql("::json")
}
override fun converter(): Converter<Any, Event> {
return JSONEventConverter.create()
}
override fun get(ctx: BindingGetResultSetContext<Event>?) {
ctx!!.convert(converter())
.value(ctx.resultSet().getString(ctx.index()))
}
override fun get(ctx: BindingGetStatementContext<Event>?) {
ctx!!.convert(converter())
.value(ctx.statement().getString(ctx.index()))
}
override fun get(ctx: BindingGetSQLInputContext<Event>?) {
throw SQLFeatureNotSupportedException()
}
override fun set(ctx: BindingSetStatementContext<Event>?) {
ctx!!.statement().setString(ctx.index(),
Objects.toString(ctx.convert(converter()).value(), null))
}
override fun set(ctx: BindingSetSQLOutputContext<Event>?) {
throw SQLFeatureNotSupportedException()
}
}
generator {
name = 'org.jooq.util.DefaultGenerator'
strategy {
name = 'org.jooq.util.DefaultGeneratorStrategy'
}
database {
name = 'org.jooq.util.postgres.PostgresDatabase'
schemata {
schema {
inputSchema = someSchema
}
schema {
inputSchema = otherSchema
}
}
forcedTypes {
forcedType {
userType = 'package.Event'
binding = 'package.PostgresJSONEventBinding'
expression = 'someSchema\\.event_store\\.event'
}
}
}
generate {
relations = true
deprecated = false
records = true
immutablePojos = true
fluentSetters = true
}
target {
packageName = appName
}
}

Is it ok to use kotlin non null types with jooq bindings?
jOOQ (or any Java library) will not respect your Kotlin non-nullable guarantees and might produce null values where you wouldn't expect them. So, perhaps it's not a good idea after all.
At the interface between jOOQ and your code, you must ensure yourself that this cannot happen.
Is this configuration ok? What should I change?
That's an open ended question. If you have any specific questions, please ask.
When I want to store value my converter gets null in from func. I don't why is that.
There are not enough infos in your question to help you about this

Ok so in my case it was about java-kotlin interoperability between nullable types in Java and non-null types in kotlin. All I had to do was implementing converter using nullable types in kotlin (the ones with ?).
Correct converter look like this:
class JSONEventConverter constructor(
private val objectMapper: ObjectMapper,
private val schemaMatcher: SchemaMatcher
) : Converter<Any, Event> {
override fun from(databaseObject: Any?): Event? {
return databaseObject?.let { schemaMatcher.parse(it.toString()) }
}
override fun to(userObject: Event?): Any? {
return userObject?.let { objectMapper.writeValueAsString(it) }
}
override fun fromType(): Class<Any> {
return Any::class.java
}
override fun toType(): Class<Event> {
return Event::class.java
}
companion object {
fun create(): JSONEventConverter {
return JSONEventConverter(serializingObjectMapper(),
SchemaMatcher.create())
}
}
}

Related

How to handle Kotlin Jetpack Paging 3 exceptions?

I am new to kotlin and jetpack, I am requested to handle errors (exceptions) coming from the PagingData, I am not allowed to use Flow, I am only allowed to use LiveData.
This is the Repository:
class GitRepoRepository(private val service: GitRepoApi) {
fun getListData(): LiveData<PagingData<GitRepo>> {
return Pager(
// Configuring how data is loaded by adding additional properties to PagingConfig
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false
),
pagingSourceFactory = {
// Here we are calling the load function of the paging source which is returning a LoadResult
GitRepoPagingSource(service)
}
).liveData
}
}
This is the ViewModel:
class GitRepoViewModel(private val repository: GitRepoRepository) : ViewModel() {
private val _gitReposList = MutableLiveData<PagingData<GitRepo>>()
suspend fun getAllGitRepos(): LiveData<PagingData<GitRepo>> {
val response = repository.getListData().cachedIn(viewModelScope)
_gitReposList.value = response.value
return response
}
}
In the Activity I am doing:
lifecycleScope.launch {
gitRepoViewModel.getAllGitRepos().observe(this#PagingActivity, {
recyclerViewAdapter.submitData(lifecycle, it)
})
}
And this is the Resource class which I created to handle exceptions (please provide me a better one if there is)
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(msg: String, data: T?): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
As you can see I am using Coroutines and LiveData. I want to be able to return the exception when it occurs from the Repository or the ViewModel to the Activity in order to display the exception or a message based on the exception in a TextView.
Your GitRepoPagingSource should catch retryable errors and pass them forward to Paging as a LoadResult.Error(exception).
class GitRepoPagingSource(..): PagingSource<..>() {
...
override suspend fun load(..): ... {
try {
... // Logic to load data
} catch (retryableError: IOException) {
return LoadResult.Error(retryableError)
}
}
}
This gets exposed to the presenter-side of Paging as LoadState, which can be reacted to via LoadStateAdapter, .addLoadStateListener, etc as well as .retry. All of the presenter APIs from Paging expose these methods, such as PagingDataAdapter: https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter
You gotta pass your error handler to the PagingSource
class MyPagingSource(
private val api: MyApi,
private val onError: (Throwable) -> Unit,
): PagingSource<Int, MyModel>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, YourModel> {
try {
...
} catch(e: Exception) {
onError(e) // <-- pass your error listener here
}
}
}

Is it possible to verify at compile time whether the required function is called for the Factory Class in Kotlin?

class ModelFactory {
fun setA() : ModelFactory {
// blabla...
}
fun setB() : ModelFactory {
// blabla...
}
fun setC() : ModelFactory {
// blabla...
}
fun build() : Model {
// An error occurs if any of setA, setB, and setC is not called.
}
}
//example
fun successTest() {
ModelFactory().setA().setB().setC().build() // No error occurs at compile time
}
fun failTest() {
ModelFactory().setA().build() // An error occurs at compile time because setB and setC are not called.
}
It's awkward grammatically, but I think it's been expressed what I want.
I have already implemented an error-raising runtime for this requirement, but I want to check this at compile time.
If possible, I think I should use annotations. But is this really possible at compile time?
With Kotlin, I have been avoiding builder pattern, as we can always specify default values for non-mandatory fields.
If you still want to use a builder pattern, you can use Step builder pattern that expects all mandatory fields to be set before creating the object. Note that each setter method returns the reference of next setter interface. You can have multiple Step builders based on the combination of mandatory fields.
class Model(val a: String = "", val b: String = "", val c: String = "")
class StepBuilder {
companion object {
fun builder(): AStep = Steps()
}
interface AStep {
fun setA(a: String): BStep
}
interface BStep {
fun setB(b: String): CStep
}
interface CStep {
fun setC(c: String): BuildStep
}
interface BuildStep {
//fun setOptionalField(x: String): BuildStep
fun build(): Model
}
class Steps : AStep, BStep, CStep, BuildStep {
private lateinit var a: String
private lateinit var b: String
private lateinit var c: String
override fun setA(a: String): BStep {
this.a = a
return this
}
override fun setB(b: String): CStep {
this.b = b
return this
}
override fun setC(c: String): BuildStep {
this.c = c
return this
}
override fun build() = Model(a, b , c)
}
}
fun main() {
// cannot build until you call all three setters
val model = StepBuilder.builder().setA("A").setB("B").setC("C").build()
}

Kotlin fallback wrapper

I'm looking for an elegant solution to the following.
I'd like to implement a Wrapper class that:
Accepts 2 implementations of the same Interface, and returns a new instance of that same Interface.
Any method call to the Wrapper object, tries to call the same method on the 1st implementation.
If the first call results into UnsupportedOperationException, then the 2th implementation should be used instead.
interface API {
fun getData(): String
}
class Main: API {
override fun getData(): String {
throw UnsupportedOperationException()
}
}
class Fallback: API {
override fun getData(): String {
return "data"
}
}
class Wrapper {
companion object {
fun getInstance(main: API, fallback: API): API {
// TODO
}
}
}
class Test {
#Test
fun `invokes the fallback instance`() {
val wrapper = Wrapper.getInstance(Main(), Fallback())
val response = wrapper.getData()
assertEquals(response, "data")
}
}
The best thing I have come up with so far is Delegate with Overrides:
class Wrapper(fallback: API): API by Main() {
val fallback = fallback
override fun getData(): String {
return fallback.getData()
}
}
What I don't like about this solution is that:
It requires overriding each unsupported operation
It gets quite verbose as the Interface grows into a complex multilevel structure with more sub interfaces
I'd also like to avoid Reflection for performance reasons and because this is a Kotlin Multiplatform project.
Any suggestions are appreciated.
Thanks,
Juan
Your proposed solution won't work because it will always favor the fallback for any overridden function.
There's no solution for your needs that can avoid having to manually handle every function of your interface. But you can have an intermediate function that handles the cascading selection of implementation for functions with the same signature.
class Wrapper (private val delegates: Array<out API>): API {
companion object {
fun getInstance(vararg delegates: API) = Wrapper(delegates)
}
private fun <R> delegate0Arg(function: API.() -> R): R {
for (delegate in delegates) {
try {
return delegate.function()
} catch (e: UnsupportedOperationException) {
// continue
}
}
throw UnsupportedOperationException()
}
override val name: String get() = delegate0Arg(API::name)
override fun getData(): String = delegate0Arg(API::getData)
}
But you would need additional functions to handle each unique number of arguments the interface functions have.
private fun <T, R> delegate1Arg(t: T, function: API.(t: T) -> R): R {
for (delegate in delegates) {
try {
return delegate.function(t)
} catch (e: UnsupportedOperationException) {
// continue
}
}
throw UnsupportedOperationException()
}
override fun getData(x: String) = delegate1Arg(x, API::getData)

Kotlin + Arrow + Gson = None?

I have a model in Kotlin of a simple library of Books and Borrowers where a Book is checked out if it has a Borrower. I use Arrow Option to encode the absence/presence of a Borrower:
data class Borrower(val name: Name, val maxBooks: MaxBooks)
data class Book(val title: String, val author: String, val borrower: Option<Borrower> = None)
I am having trouble serializing/deserializing these objects to/from JSON in Gson - specifically the representation of an Option<Borrower> to a JSON null within a Book:
[
{
"title": "Book100",
"author": "Author100",
"borrower": {
"name": "Borrower100",
"maxBooks": 100
}
},
{
"title": "Book200",
"author": "Author200",
"borrower": null
}
]
My deserialize code:
fun jsonStringToBooks(jsonString: String): List<Book> {
val gson = Gson()
return try {
gson.fromJson(jsonString, object : TypeToken<List<Book>>() {}.type)
} catch (e: Exception) {
emptyList()
}
}
I get an empty list. The nearly identical jsonStringToBorrowers works fine.
Can someone point me in the right direction?
Would using a different JSON library like kotlinx.serialization or Klaxon be a better idea and how do they do the null <-> None thing?
Thank you!
The issue is a bit hidden by the fact that you don't log the exception before returning an empty list. If you logged that exception you would have gotten this:
java.lang.RuntimeException: Failed to invoke private arrow.core.Option() with no args
This means that Gson doesn't know how to create an Option class because it has no public empty constructor. Indeed, Option is a sealed class (hence abstract) having 2 concrete children classes: Some and None. In order to get an instance of Option you should use one of the factory methods, like Option.just(xxx) or Option.empty() among the others.
Now, in order to fix your code you need to tell Gson how to deserialize an Option class. To do that, you need to register a type adapter to your gson object.
A possible implementation is the following:
class OptionTypeAdapter<E>(private val adapter: TypeAdapter<E>) : TypeAdapter<Option<E>>() {
#Throws(IOException::class)
override fun write(out: JsonWriter, value: Option<E>) {
when (value) {
is Some -> adapter.write(out, value.t)
is None -> out.nullValue()
}
}
#Throws(IOException::class)
override fun read(input: JsonReader): Option<E> {
val peek = input.peek()
return if (peek != JsonToken.NULL) {
Option.just(adapter.read(input))
} else {
input.nextNull()
Option.empty()
}
}
companion object {
fun getFactory() = object : TypeAdapterFactory {
override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
val rawType = type.rawType as Class<*>
if (rawType != Option::class.java) {
return null
}
val parameterizedType = type.type as ParameterizedType
val actualType = parameterizedType.actualTypeArguments[0]
val adapter = gson.getAdapter(TypeToken.get(actualType))
return OptionTypeAdapter(adapter) as TypeAdapter<T>
}
}
}
}
You can use it in the following way:
fun main(args: Array<String>) {
val gson = GsonBuilder()
.registerTypeAdapterFactory(OptionTypeAdapter.getFactory())
.create()
val result: List<Book> = try {
gson.fromJson(json, TypeToken.getParameterized(List::class.java, Book::class.java).type)
} catch (e: Exception) {
e.printStackTrace()
emptyList()
}
println(result)
}
That code outputs:
[Book(title=Book100, author=Author100, borrower=Some(Borrower(name=Borrower100, maxBooks=100))), Book(title=Book200, author=Author200, borrower=None)]

Typesafe map assignment and retrieval

I am trying to proxy calls for Observables and LiveData (similar to the Mediator pattern), but I could not find a typesafe solution. This is the problem:
class Proxy {
private val backupMap = HashMap<LiveData<Any>, Observer<Any>>()
fun <T> add(liveData : LiveData<T>, observer : Observer<T>) {
// !This is the issue LiveData<Any> is expected
backupMap.put(liveData, observer)
}
fun attach() {
backupMap.forEach { (key, value) ->
key.observeForever(value)
}
}
}
fun addSome() {
Proxy().apply {
add(MutableLiveData<String>(), Observer { })
}
}
I could cast backupMap.put to backupMap.put(liveData as LiveData<Any>, observer as Observer<Any>) but this causes an Unchecked Cast.
Solution I found is to use an intermediate object to hold the typesafe binding:
private val backupMap: MutableMap<LiveData<*>, Attacher<*>>
private class Attacher<A>(private val lifeData: LiveData<A>, private val observer : Observer<A>) {
fun attach() {
lifeData.observeForever(observer)
}
fun detach() {
lifeData.removeObserver(observer)
}
}