Reputation: 21
I am trying to create a very known generic resource for my Panache Entities, however, it does not seem to play well with Java generics. What I wanted to do is avoid repeat myself with common REST operation. I want to change the HTTP meta info (like: auth or not, the resource path, etc), but all the time the actual response building is the same, so I built this generic class:
package org.reproducer;
import java.net.URI;
import java.util.List;
import javax.transaction.Transactional;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
public class BaseResource<T extends PanacheEntity> {
public List<T> listAll() {
return T.listAll();
}
public Response get(long id) {
T entity = T.findById(id);
if (entity != null) {
return Response.ok(entity).build();
} else {
return notFound(id);
}
}
@Transactional
public Response add(T entity) {
T.persist(entity);
URI uri = UriBuilder.fromPath("/")
// "this" reference was not working here
.path(entity.getClass().getName().toLowerCase()).path("{id}").build(entity.id);
return Response.created(uri).build();
}
@Transactional
public Response remove(long id) {
T entity = T.findById(id);
if (entity != null) {
entity.delete();
return Response.noContent().build();
} else {
return notFound(id);
}
}
private Response notFound(long id) {
return Response.status(404).entity("Entity with id " + id + " not found.").build();
}
}
So I could extend and focus on the REST/HTTP part in my resource:
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.reproducer.model.TheEntity;
@Path("/entity2")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ResourceWithBase extends BaseResource<TheEntity>{
@GET
public List<TheEntity> listAll() {
return super.listAll();
}
@GET
@Path("{id}")
public Response get(@PathParam("id") long id) {
return super.get(id);
}
@POST
public Response add(TheEntity entity) {
return super.add(entity);
}
@DELETE
@Path("{id}")
public Response remove(@PathParam("id") long id) {
return super.remove(id);
}
}
However, whatever I try to call any of the methods it will fail with:
However, whatever I try to call any of the methods it will fail with This method is normally automatically overridden in subclasses: did you forget to annotate your entity with @Entity?
(full stack trace below
ERROR: Exception handling request fcc704b2-25f9-4fac-b60e-3dd1f25a7c7c-1 to /entity2
org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalStateException: This method is normally automatically overridden in subclasses: did you forget to annotate your entity with @Entity?
at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:106)
at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:372)
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:209)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:496)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:252)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:153)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
at io.quarkus.resteasy.runtime.ResteasyFilter$ResteasyResponseWrapper.sendError(ResteasyFilter.java:64)
at io.undertow.servlet.handlers.DefaultServlet.doGet(DefaultServlet.java:175)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:686)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:791)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
at io.quarkus.resteasy.runtime.ResteasyFilter.doFilter(ResteasyFilter.java:28)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)
at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$8$1$1.call(UndertowDeploymentRecorder.java:489)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:364)
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1395)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at java.lang.Thread.run(Thread.java:748)
at org.jboss.threads.JBossThread.run(JBossThread.java:479)
Caused by: java.lang.IllegalStateException: This method is normally automatically overridden in subclasses: did you forget to annotate your entity with @Entity?
at io.quarkus.hibernate.orm.panache.runtime.JpaOperations.implementationInjectionMissing(JpaOperations.java:351)
at io.quarkus.hibernate.orm.panache.PanacheEntityBase.listAll(PanacheEntityBase.java:355)
at org.reproducer.BaseResource.listAll(BaseResource.java:15)
at org.reproducer.ResourceWithBase.listAll(ResourceWithBase.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:151)
at org.jboss.resteasy.core.MethodInjectorImpl.lambda$invoke$3(MethodInjectorImpl.java:122)
at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602)
at java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:614)
at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:1983)
at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:110)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:122)
at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:580)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:454)
at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:408)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:410)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:379)
at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invoke$1(ResourceMethodInvoker.java:353)
at java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:981)
at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2124)
at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:110)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:353)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:477)
... 49 more
I tried different approaches, like using repository, an interface to define the methods and others. Always fails in a different way, it seems that Panache does not like generics or the panache extension does not implement a way to generate code for T.{some panache static method}!
Is this expected? Do you suggest any workaround to make it work?
The code for a reproducer can be found here -> https://github.com/jesuino/reproducers/tree/master/quarkus-panache-generics
I hate having to repeat code for this!
Upvotes: 2
Views: 6978
Reputation: 5562
I copy/paste here the answer I made on the Github Issue
What you want to do cannot works, it's not a limitation on Panache but on the way Java Generics works.
On your reproducer I saw this:
public class BaseResource<T extends PanacheEntity> {
public List<T> listAll() {
return T.listAll();
}
}
public class ResourceWithBase extends BaseResource<TheEntity>{
@GET
public List<TheEntity> listAll() {
return super.listAll();
} }
Even if T
seems to be of type TheEntity
, at runtime it isn't, as T extends PancheEntity, the runtime class is PanacheEntity
so you try to call the listAll()
methods on PanacheEntity
and it can not works.
This is the way static methods works on generic types in Java.
Saying it in another way: you call listAll() on T not on TheEntity as listAll() is a static method so it is linked to the type not the instance.
Upvotes: 2