-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RESTEasy Reactive Links #14249
RESTEasy Reactive Links #14249
Conversation
Great, thanks a lot @gytis! I'll have a look tomorrow |
.../io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveDeploymentInfoBuildItem.java
Show resolved
Hide resolved
...n/java/io/quarkus/resteasy/reactive/links/deployment/PathParameterValueGettersGenerator.java
Outdated
Show resolved
Hide resolved
...s/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksProcessor.java
Outdated
Show resolved
Hide resolved
...-links/runtime/src/main/java/io/quarkus/resteasy/reactive/links/RestLinksResponseFilter.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some comments and questions. This looks like limited to Link
headers ATM, which is fine.
I'm not too sure about the API though, I think it would help if you added examples which we can use as use-cases to verify what it does.
It looks like you're resolving path parameters directly as fields from the resources, which is probably fine, but for resteasy classic (https://docs.jboss.org/resteasy/docs/2.0.0.GA/userguide/html/LinkHeader.html) I only handled IDs. I guess this is more powerful, but requires that the path parameter name matches the entity property.
} | ||
|
||
@Override | ||
public void visitEnd() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like you're generating getters even if one already exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It did initially.
I wasn't sure how to check if a method exists from a visitor and I couldn't use an index, because it doesn't get updated. So I've added an extra mapping here https://github.com/quarkusio/quarkus/pull/14249/files/a6de4a1aa44bc8a1102de72835ba98a1355d5c8d#diff-938ce5408bf74bba0f73f4922b625306f3a9deced50ca5afcde36c03d431f3abR49 to make sure that only one getter is generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usually collect signatures in visitMethod
.getHeaders() | ||
.getValues("Link"); | ||
assertThat(links).containsOnly( | ||
Link.fromUriBuilder(UriBuilder.fromUri(testWithLinksUrl).path("/1")).rel("getById").build().toString(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO according to https://www.iana.org/assignments/link-relations/link-relations.xhtml this should be self
for the rel
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that because we cannot infer all the possible rel values I would simply default it to the method name and allow it to be overwritten with an annotation (@RestLink(rel="self")
).
.getValues("Link"); | ||
assertThat(links).containsOnly( | ||
Link.fromUriBuilder(UriBuilder.fromUri(testWithLinksUrl).path("/1")).rel("getById").build().toString(), | ||
Link.fromUri(testWithLinksUrl).rel("list").build().toString(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again according to IANA this should probably be collection
.
assertThat(links).containsOnly( | ||
Link.fromUriBuilder(UriBuilder.fromUri(testWithLinksUrl).path("/1")).rel("getById").build().toString(), | ||
Link.fromUri(testWithLinksUrl).rel("list").build().toString(), | ||
Link.fromUri(testWithoutLinksUrl).rel("listWithoutLinks").build().toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then, not sure what to do about this. Need to check if we can have multiple similar relations.
@Path("/with-links/{id}") | ||
@Produces(MediaType.APPLICATION_JSON) | ||
@RestLink | ||
@InjectRestLinks(RestLinkType.INSTANCE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what instance
refers to here: we do have an instance returned, so…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment by default TYPE
links are injected i.e. get all and create.
@GET | ||
@Path("/with-links") | ||
@Produces(MediaType.APPLICATION_JSON) | ||
@RestLink(entityType = TestRecord.class, rel = "list") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be optional, in which case we can infer that the entity type is TestRecord
(from the return type List<TestRecord>
and same for the rel
which should be collection
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have this implemented yet, but inferring an entity type from a generic is in my todo. So for now this is a workaround.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for the rel, this annotation allows to define any rel you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
@Target({ ElementType.TYPE, ElementType.METHOD }) | ||
public @interface InjectRestLinks { | ||
|
||
RestLinkType value() default RestLinkType.TYPE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what purpose this serves. Can you show me use-cases? The example test could deduce that from the method return type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that this would allow user to specify which links does he want to be injected.
E.g. if a DELETE
operation returns a deleted entity, you don't necessarily want to return the links specific for that entity (self, update, delete) because it doesn't exist any more. But you might still want to return the links for getting all and creating new entities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I see, but that feels special-casy.
for (Method method : instance.getClass().getMethods()) { | ||
if (method.getName().equals(pathParameterInfo.getValueGetter())) { | ||
try { | ||
return method.invoke(instance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can get rid of this reflection by generating bytecode that invokes it for a given instance like this:
public class $$Order$Id implements Getter<Order,Long> {
public static Long get(Order order){
return order.$$generated_getter_id();
}
}
And you instantiate those getters at static init and can just invoke them without reflection when you need to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I'll add that.
Thanks for the comments @FroMage.
For now I've added two things. A
This is just a draft to discuss the API. I don't expect it to be final and I'm happy to change it. That's the whole point.
Yes I've added a possibility to resolve any entity field. I haven't yet added an override option to the |
No, I think it's an interesting approach, and more powerful. Let's try it. |
4f58ec1
to
00d7bfb
Compare
@FroMage @geoand, sorry for a slight delay in the update but I think I've covered all the comments. However, I still have a couple of points that need to be cleared up.
|
My comments have been addressed, the rest I can really comment on since I don't have any experience with links |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Just handle collections and special types on a single level, yeah. |
00d7bfb
to
0ce15a3
Compare
I've just squashed the commits and added the extension file. |
5af2ef5
to
bcc5339
Compare
665dd43
to
e212cbc
Compare
Ah damn... We forgot to merge and now there are conflicts :( Can you rebase Gytis so we can get this in? |
e212cbc
to
ff688c4
Compare
@Sgitario would you be interested in writing a guide for RESTEasy Reactive links? |
related to #9052 ? |
Related, but we might want something ever higher level |
I really like the idea. Count on me to do this. |
Thanks a lot! |
@FroMage @geoand this is a a draft PR for RESTEasy Reactive Links extension to discuss the API and see if I'm going in a right direction here.
There are three main user facing pieces:
All JAX-RS methods are scanned in build time when LinksContext is created. In runtime we don't need to do any scans or reflection operations like in RESTEasy Links. It's enough to perform a map lookup to get the link templates and build the actual links.
See TestResource for an example.
I still need to improve this with more testing, EJB security check and a few other bits. But since I have a roughly functioning prototype I'd like to check in now and see what should be adjusted.