Open dixie-tcpl opened 3 months ago
The link https://docs.quarkiverse.io/quarkus-groovy/dev/index.html#_usage has an explanation that only the repository standard is supported.
"All static methods in PanacheEntityBase (such as find, findAll, list, listAll, count…) that depend on bytecode injection have been removed due to a side effect of the static compilation that by-pass the generated methods. As workaround, the methods in the corresponding repository must be used."
@fernando88to Thanks for your immediate response. I appreciate it the most.
Here is with the repo I tried. Still the same exception
@Path("/books")
class BookResource {
@Inject
BookService bookService
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
RestResponse<Book> latestBook(Book book) {
println book.properties
bookService.save(book).onItem().invoke { item -> Log.info("Persisted " + item)
}.onFailure().invoke { throwable ->
Log.errorf("Persisting failed", throwable)
RestResponse.status(RestResponse.Status.BAD_REQUEST, book)
}.subscribe().with { panacheEntityBase ->
Log.info("Persisted")
RestResponse.status(RestResponse.Status.ACCEPTED, book)
}
}
}
@ApplicationScoped
class BookService {
@Inject
BookRepository bookRepository
@WithTransaction
Uni<Book> save(Book book) {
bookRepository.persist(book)
}
}
@ApplicationScoped
class BookRepository implements PanacheRepository<Book>{
}
Http post with json body
{
"name":"Book1",
"author":"Author1",
"available":false
}
println book.properties prints all the props properly.
[id:null, name:Book1, author:Author1, available:false, $$_hibernate_entityEntryHolder:null, $$_hibernate_previousManagedEntity:null, $$_hibernate_nextManagedEntity:null, $$_hibernate_attributeInterceptor:null, $$_hibernate_tracker:org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker@71b78da2, class:class Book, persistent:false]
@fernando88to Any approaches or suggestions?
Thx for the ticket, I will try to find a long-term solution for this problem ASAP.
As a workaround, I encourage you to enable the static compilation by adding @CompileStatic
to your class BookResource
.
Please note that the equivalent of the code of your class BookResource
in Java doesn't compile, I don't think that it is the proper way to use mutiny, you should return a Uni of Book instead of a RestResponse of Book and the code should simply be:
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Uni<Book> latestBook(Book book) {
bookService.save(book)
}
I'm not a mutiny expert but I believe that the code should rather be more or less like this:
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Uni<RestResponse<Book>> latestBook(Book book) {
bookService.save(book).onItem().invoke({ item -> Log.info("Persisted " + item)} as Consumer)
.map { panacheEntityBase -> RestResponse.status(RestResponse.Status.ACCEPTED, panacheEntityBase)}
.onFailure().recoverWithItem {ex -> RestResponse.status(RestResponse.Status.BAD_REQUEST, book)}
}
@essobedo Thanks for your response. I have tried both of your suggestions. Still I get the same error. Also, notice this is not an issue with Mutiny but Panache-Groovy not being able to serialize the Book domain.
If I make Book as a simple POJO it just works.
This seems to be a reflection issue with Panache and this plugin way of handling it.
Some reference - from Groovy transformations, but this seems to be fixed. https://issues.apache.org/jira/browse/GROOVY-10747
Also, to see if this one can be replicated with JDK 11, I tried running this, but there were other compatibility issues. SO I dropped that idea.
@dixie-tcpl did you add @CompileStatic
to your class as proposed?
This code works on my side:
import groovy.transform.CompileStatic
import io.smallrye.mutiny.Uni
import jakarta.inject.Inject
import jakarta.ws.rs.Consumes
import jakarta.ws.rs.POST
import jakarta.ws.rs.Path
import jakarta.ws.rs.Produces
import jakarta.ws.rs.core.MediaType
import org.jboss.resteasy.reactive.RestResponse
import java.util.function.Consumer
@CompileStatic
@Path("/books")
class BookResource {
@Inject
BookService bookService
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Uni<RestResponse<Book>> latestBook(Book book) {
bookService.save(book).onItem().invoke({ item -> println "Persisted " + item} as Consumer)
.map { panacheEntityBase -> RestResponse.status(RestResponse.Status.ACCEPTED, panacheEntityBase)}
.onFailure().recoverWithItem {ex -> RestResponse.status(RestResponse.Status.BAD_REQUEST, book)}
}
}
Regarding the problem itself, it is due to the dynamic type resolution when using the Groovy dynamic compiler with beans with a normal scope like ApplicationScoped
that by specification have to be proxied such that the type seen is no more BookService
as we could expect but its proxy equivalent generated by Quarkus which is BookService_ClientProxy
.
This also means that as second workaround, you can change the scope of the BookService
to the non-normal scope Singleton
and keep the dynamic compiler.
My goal will be as long-term solution to manage this use case directly in the extension
@dixie-tcpl did you add
@CompileStatic
to your class as proposed?This code works on my side:
import groovy.transform.CompileStatic import io.smallrye.mutiny.Uni import jakarta.inject.Inject import jakarta.ws.rs.Consumes import jakarta.ws.rs.POST import jakarta.ws.rs.Path import jakarta.ws.rs.Produces import jakarta.ws.rs.core.MediaType import org.jboss.resteasy.reactive.RestResponse import java.util.function.Consumer @CompileStatic @Path("/books") class BookResource { @Inject BookService bookService @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) Uni<RestResponse<Book>> latestBook(Book book) { bookService.save(book).onItem().invoke({ item -> println "Persisted " + item} as Consumer) .map { panacheEntityBase -> RestResponse.status(RestResponse.Status.ACCEPTED, panacheEntityBase)} .onFailure().recoverWithItem {ex -> RestResponse.status(RestResponse.Status.BAD_REQUEST, book)} } }
This is one works well. Thanks for this. I am kind of relived here.
Regarding the problem itself, it is due to the dynamic type resolution when using the Groovy dynamic compiler with beans with a normal scope like
ApplicationScoped
that by specification have to be proxied such that the type seen is no moreBookService
as we could expect but its proxy equivalent generated by Quarkus which isBookService_ClientProxy
.
Totally understand this. This is a convenience for the framework.
This also means that as second workaround, you can change the scope of the
BookService
to the non-normal scopeSingleton
and keep the dynamic compiler.
I tried this with Singleton annotation earlier before I posted this issue. For some reason, with the code without @CompileStatic worked without any Groovy transformation errors, but the Entity (Book) did not persist.
My goal will be as long-term solution to manage this use case directly in the extension
Awesome. Let me know if you need any testing support from my side to validate this scenario when you implement this.
Greetings. I have been trying to persist a simple entity but facing issues.
Quarkus version: 3.9.1 Exception at bookService.save(book)
Some research shows this was a groovy language transformer issue, but seemed to be fixed in prior versions of groovy. The current groovy version on quarkus extension is 4.0.20.
Any guidance here is much appreciated.