Hello,
I'm having a problem with this scenario. Tipically we are using interfaces for REST endpoints where the annotations are placed to the interfaces (JAX-RS and OpenAPI annotations), we like to make our implementations super clear.
I don't know if it is a Quarkus based practice or just not working but see the following code:
Project created with this line of codes:
mvn io.quarkus:quarkus-maven-plugin:0.14.0:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=rest-json \
-DclassName="org.acme.rest.json.FruitResource" \
-Dpath="/fruits" \
-Dextensions="resteasy-jsonb"
We have an action, just keep it super simple:
package org.acme.rest.json;
import javax.enterprise.inject.Model;
@Model
public class UserAction {
public String getUser() {
return "adminUser";
}
}
and then we have a REST endpoint:
package org.acme.rest.json;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Model
@Path("/users")
public class UserResource {
@Inject
UserAction userAction;
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response getUser() {
return Response.ok(userAction.getUser()).build();
}
}
When we call the /users URI with GET, everything works, and it returns the adminUser string.
My problem comes when I'm trying to use an interface for my implementation.
package org.acme.rest.json;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/users")
public interface IUserResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
Response getUser();
}
and then implement it:
package org.acme.rest.json;
import javax.enterprise.inject.Model;
import javax.inject.Inject;
import javax.ws.rs.core.Response;
@Model
public class UserResource implements IUserResource {
@Inject
UserAction userAction;
@Override
public Response getUser() {
return Response.ok(userAction.getUser()).build();
}
}
When we call the /users URI withj GET, we are getting an NPE for the userAction, which is NULL...
ERROR [io.und.request] (executor-thread-1) UT005023: Exception handling request to /users: org.jboss.resteasy.spi.UnhandledException: java.lang.NullPointerException
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:362)
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:234)
at io.quarkus.resteasy.runtime.ResteasyFilter$ResteasyResponseWrapper.sendError(ResteasyFilter.java:72)
at io.undertow.servlet.handlers.DefaultServlet.doGet(DefaultServlet.java:175)
....
Caused by: java.lang.NullPointerException
at org.acme.rest.json.UserResource.getUser(UserResource.java:15)
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.acme.rest.json.UserResource.getUser(UserResource.java:15)
which is this line: return Response.ok(userAction.getUser()).build();
When the endpoint is called, before the NPE drops there is another LOG message:
[io.qua.arc.run.ArcDeploymentTemplate] (executor-thread-1) Bean matching class org.acme.rest.json.UserResource was marked as unused and removed during build.
Extensions can eliminate false positives using:
- a custom UnremovableBeanBuildItem
- AdditionalBeanBuildItem(false, beanClazz)
What am I missing? It should work, shouln't it?
Did I miss something in the docs?
It's possible that Arc doesn't support the use of @Model.
Can you try adding @RequestScoped to UserResource and see if that makes it work?
@kenfinnigan
Yes, I have already tried @RequestScoped and @ApplicationScoped on UserResource and on the UserAction.
If you guys need I can push this little thing to a repository, but can be easily copy it.
And yes! I'm using DEV mode, so I'm not using the native option, I'm stuck on Windows yet :)
Have you checked if the error occurs when executing the -runner.jar?
It might be a DEV mode only problem
Using the -runner.jar still gives the error. :(
Might be a pain, but at least it's consistent.
If you can push a simple repository that would be great.
Of course, will link it in a few minutes.
Here it is @kenfinnigan https://github.com/Holi60k/quarkus-injection
Thanks!
@mkouba could take a look when you have a chance? Likely Arc related, but not entirely sure
Yes, it's related. When interface is used for JAX-RS annotations the UserResource bean is removed because we only exclude resource classes annotated with @Path (which is as a bean defining annotation defined here).
I will add a new UnremovableBeanBuildItem that will also exclude classes that implement an interface annotated with @Path. It should be a simple fix.
Thank you @mkouba .
So does it mean, it will be fixed in the new version? :)
Yes ;-)
Thank you! :)
Actually, I'm just looking at the JAX-RS spec and it seems that only method-level annotations are inherited, "3.6 Annotation Inheritance": _"JAX-RS annotations may be used on the methods and method parameters of a super-class or an implemented interface. ... Note that inheritance of class or interface annotations is not supported."_
In other words, at least the @Path annotation must be declared on the UserResource. @gsmet @asoldano @kenfinnigan Am I right?
Just a little note: This scenario works with Thorntail based applications.
@Holi60k Ok, if RESTEasy does support this and no one has any objections we can make it work. However, I'd like to know what the expected behavior is.
@mkouba I don't know what exactly caused adding that line about the class/interface annotation inheritrance, but AFAIK the JAX-RS implementations have ignored this line :-), not 100% sure about Jersey only
Any information about the proof that is truly supported by JAX-RS ? :)
@Holi60k As I've mentioned earlier, it is up to the concrete implementation whether to support the Path inheritance at the class level or not, the spec says no, but in practice it works in some implementations. TCK does not enforce that such inheritance does not work AFAIK
@sberyozkin Thank you !
Ok, then I'm going to send a PR today.
Most helpful comment
Yes ;-)