Quarkus: Kotlin based PanacheEntity throws runtime exception

Created on 5 Oct 2019  路  46Comments  路  Source: quarkusio/quarkus

Described at stackoverflow , as well as on zulipchat

arekotlin arepanache kinbug triagout-of-date

Most helpful comment

@geoand I'd agree with you. If we go this way - we need to clearly state that in the docs.
On top - I'd volunteer to create kotlin based guides, alongside Java based ones.

Would be nice to have some organisationally distinguished Java vs Kotlin guides, IMHO

All 46 comments

The problem is essentially that the Kotlin compiler when generating an invokeStatic bytecode instruction is not using the entity as the target, but the PanacheEntity.

It looks something like:

```
13: invokestatic #xy // Method io/quarkus/hibernate/orm/panache/PanacheEntity.find:(Ljava/lang/String;[Ljava/lang/Object;)Lio/quarkus/hibernate/orm/panache/PanacheQuery;


whereas similar examples in Java have bytecode like this:

5: invokestatic #xy // Method io/quarkus/hibernate/orm/panache/Fruit.find:(Ljava/lang/String;[Ljava/lang/Object;)Lio/quarkus/hibernate/orm/panache/PanacheQuery;
```

I also should mention that this happens when trying to call a static of method of the Entity from inside a companion object of the entity.

@geoand
ordinacija-api.zip contains code and readme.md in the projet root describing the issue.

@FroMage @emmanuelbernard FYI

Well that's very bad luck. Does this also happen on static calls outside of the entity class or only in the companion object?

It seems like one can't even do Fruit.findAll (where Fruit extends PanacheEntity) from Kotlin code... I wonder if that is by design or not...

You mean it doesn't compile (different Kotlin syntax for static calls), or it doesn't run (due to invalid bytecode being produced by the Kotlin compiler)?

Doesn't compile :(

You can in Kotlin of course call Java statics, however it doesn't seem to like "inherited" statics

Hah. Well then we're screwed. Probably an alternative solution/API has to be made especially for Kotlin. I don't know the language enough to think of something, though.

For now I think that using the repository approach is pretty much the only option for people wanting to stick to pure Kotlin.

@geoand I'd agree with you. If we go this way - we need to clearly state that in the docs.
On top - I'd volunteer to create kotlin based guides, alongside Java based ones.

Would be nice to have some organisationally distinguished Java vs Kotlin guides, IMHO

For now yes, but I just tried this and it seems a viable approach:

open class PanacheEntity {

}

interface Functions<out T: PanacheEntity> {
  fun findById1(id: Any): T? = null;
  fun findById2(id: Any): T? = null;
}

class MyEntity : PanacheEntity() {
    // require this
    companion object : Functions<MyEntity> {
        // generate this
        override fun findById2(id: Any): MyEntity = MyEntity()
    }
}

fun main(args: Array<String>) {
    println(MyEntity.findById1(2))
    println(MyEntity.findById2(2))
}

So basically we have to make a custom entity type for Kotlin, and require that users add a companion object : Functions<CurrentClass> {} to their classes. This works, except you can't then have subclasses, but I guess that's an acceptable limitation.

@dodalovic That would be fantastic IMHO!

@emmanuelbernard WDYT about having Kotlin code in the docs as well?

@FroMage seems acceptable from my POV

It does, but I'm not sure where this extension would live (not in the current extension because it depends on Kotlin) and who would write it.

Perhaps a new extension called quarkus-hibernate-panache-kotlin or something of that sort?

I could most likely work on it post 1.0 if we think we should have it.
Obviously if anyone wants to take it up as exercise that would be even better :)

Yeah, quarkus-hibernate-orm-panache-kotlin.

I don't know how we could do both Java and Kotlin examples. I'm not sure two different guides each time would scale.

vs having two examples, one for J and one of K

Of course it looks like Panache is a counter example as things are relatively different due to the Kotlin way of doing things

@emmanuelbernard I agree with you that we should have only one guide
, in Java. On top - the same sample written in both languages, and the kotlin one mentioned in Java guide.

My bigger concern is that we currently do not mention limitations related to Kotlin support clearly in the documentation.

What I could do from my side is to prepare sample app in kotlin demonstrating extensions that fully work in Kotlin?

@dodalovic I think that would be great to have quickstarts for Kotin for the key extensions indeed to make sure we don't regress too.

This issue/pullrequest has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

This also occurs inside the MongoDB with Panache extension for it's entity based DAO.

@FroMage @geoand @emmanuelbernard Maybe we can provides something using Kotlin extensions: https://kotlinlang.org/docs/reference/extensions.html

We should be able to provides, inside Hibernate with Panache, extension fonctions for Kotlin.

For exemple the following construct compiles:

@Entity
class Bookmark : PanacheEntity() {
    var title: String? = null
    var url: String? = null
    var description: String? = null
}

//define extension method => should resides inside hibernate-panache
fun PanacheEntity.listAll(): List<Bookmark> {
    throw JpaOperations.implementationInjectionMissing();
}

@Path("/bookmarks")
class BookmarkResource {

    @GET
    fun listAll(): List<Bookmark> {
        return Bookmark().listAll()
    }
}

This is not static methods as there is no static methods inside Kotlin but this is more easy to use than a companion object as the user don't need to modify it's entity.

For this to works we need to create a PanacheEntityBaseExtension that will add as extension function all the static methods from PanacheEntityBase and find a way to generate the needed bytecode for them as it is done for PanacheEntityBase. I don't know if it's feasible but sounds interesting to investiguate ...

Frankly I'm not a fan of adding those as instance methods, because it doesn't make sense to instantiate those objects to throw them away.

If we want to keep using static method, as static method only target the host type on Kotlin, we need to provides special method for Kotlin inside a new component in Panache, like a KotlinPanacheEntity with methods like

public <T> List<T> find(Class<? extends T>, String query, String... params)

But having object created that didn't escape the method and dies immediatly doesn't impact performance so I think the aproach with instance methods added via an extension is viable.

What is your alternative @FroMage ?

What I suggested above, that kotlin subclasses include companion object : Functions<MyEntity> {} in their entities. But perhaps we should ask kotlin users for ideas because we may be missing options due to lack of expertise.

@FroMage I don't understand wich part is provided by Quarkus and wich part is provided by the user. On my proposal, Quarkus will provides extension methods for all static methods of Panache and the user will use them.

For the records, Scala didn't have static methods so we also needs to do something for it. And it also supports extension methods and companion objects.

I'm not an expert on Kotlin but as far as I know there is three solutions:

  • extension methods
  • companion objects
  • not using entity but using repository instead

The third option should be recommended as long as we didn't provides a better approach.

https://github.com/quarkusio/quarkus/issues/4394#issuecomment-538916878 explains it.

Users have to include companion object : Functions<MyEntity> {} in their entities, and they can place whatever static methods in there if then want it, and then they get all the static methods for free.

vs having two examples, one for J and one of K

@emmanuelbernard: Sorry, for the off-topic, but regarding the examples for Java and Kotlin, the Google Android Developers pages could be a very nice inspiration. https://developer.android.com/topic/libraries/architecture/livedata#kotlin. You can easily switch betwen the languages for every example.

For the record, I'm discovering quarkus, was pleased to see scala supported, was trying to do the same PanacheEntityBase.listAll from as ORM-101 and got bitten the same exact same way.

I don't know how much it's possible/desirable to have something language-agnostic there.

Scala would likely have different constraints. If you feel like trying it out and see if it works, and if not tell us why, we could try to look for a possible solution.

Hello @FroMage ,

sure, you can find the repro source in this repository, and the compilation output in the README.

This should be fixed now with the panache kotlin extension. Closing it

I thing you are refering to: io.quarkus:quarkus-hibernate-orm-panache-kotlin
But this only solves the problem for hibernate. Is there something similar for quarkus-mongodb-panache?

I am actually in the final stages of adding support there. I hope to have a PR submitted this week.

Hi

I still have the same issue when I create a base class that I want all my repositories to extend from:

open class BaseRepository<T : Any> : PanacheRepository<T> {

  fun create(entity: T): T {
    persistAndFlush(entity)
    return entity
  }

  fun findByUUID(uuid: UUID, lock: Boolean = false): T? {
    val query = find("id", uuid)
    if (lock) {
      query.withLock(LockModeType.OPTIMISTIC)
    }
    return query.firstResult()
  }

}

Error:

java.lang.VerifyError: class org.iam.repository.UserRepository_ClientProxy overrides final method org.util.BaseRepository.create(Ljava/lang/Object;)Ljava/lang/Object;

Any help would be great!

Where does org.util.BaseRepository come in to the picture? There's no create() on the repository interfaces.

its

@ApplicationScoped
class UserRepository : BaseRepository<User>() {
}

and UserRepository is used in the UserResource with

@Inject
lazyinit val userRepository: UserRepository

Oh, it would seem the generator for those client proxies perhaps isn't generating the right bytecode then. I'm not sure what bit of code is responsible for generating that. iirc, that's a general hibernate function.

And if make the functions "open" I get:

java.lang.IllegalStateException: This method is normally automatically overridden in subclasses: did you forget to annotate your entity with @Entity?

any updates?

My apologies. My laptop had an expected and longer than promised trip to the shop so i'm a bit behind schedule. I'm wrapping some changes over the next day or two and will hopefully have something to say then.

And if make the functions "open" I get:

java.lang.IllegalStateException: This method is normally automatically overridden in subclasses: did you forget to annotate your entity with @Entity?

Same issue here

@Ethras do you have code or better yet a repository you can share?

Was this page helpful?
0 / 5 - 0 ratings