Skip to content
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

Document Resteasy Reactive Links #25217

Merged
merged 1 commit into from
May 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions docs/src/main/asciidoc/resteasy-reactive.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,99 @@ Importing this module will allow HTTP message bodies to be read from XML
and serialised to XML, for <<resource-types,all the types not already registered with a more specific
serialisation>>.

=== Web Links support

[[links]]

To enable Web Links support, add the `quarkus-resteasy-reactive-links` extension to your project.

.Table Context object
|===
|GAV|Usage

|`io.quarkus:quarkus-resteasy-reactive-links`
|https://www.w3.org/wiki/LinkHeader[Web Links support]

|===

Importing this module will allow injecting web links into the response HTTP headers by just annotating your endpoint resources with the `@InjectRestLinks` annotation. To declare the web links that will be returned, you need to use the `@RestLink` annotation in the linked methods. An example of this could look like:

[source,java]
----
@Path("/records")
public class RecordsResource {

@GET
@RestLink(rel = "list")
@InjectRestLinks
public List<Record> getAll() {
// ...
}

@GET
@Path("/{id}")
@RestLink(rel = "self")
@InjectRestLinks(RestLinkType.INSTANCE)
public TestRecord get(@PathParam("id") int id) {
// ...
}

@PUT
@Path("/{id}")
@RestLink
@InjectRestLinks(RestLinkType.INSTANCE)
public TestRecord update(@PathParam("id") int id) {
// ...
}

@DELETE
@Path("/{id}")
@RestLink
public TestRecord delete(@PathParam("id") int id) {
// ...
}
}
----

When calling the endpoint `/records` which is defined by the method `getAll` within the above resource using curl, you would get the web links header:

[source,bash]
----
& curl -i localhost:8080/records
Link: <http://localhost:8080/records>; rel="list"
----

As this resource does not return a single instance of type `Record`, the links for the methods `get`, `update`, and `delete` are not injected. Now, when calling the endpoint `/records/1`, you would get the following web links:

[source,bash]
----
& curl -i localhost:8080/records/1
Link: <http://localhost:8080/records>; rel="list"
Link: <http://localhost:8080/records/1>; rel="self"
Link: <http://localhost:8080/records/1>; rel="update"
Link: <http://localhost:8080/records/1>; rel="delete"
----

Finally, when calling the delete resource, you should not see any web links as the method `delete` is not annotated with the `@InjectRestLinks` annotation.

==== Programmatically access to the web links registry

You can programmatically have access to the web links registry just by injecting the `RestLinksProvider` bean:

[source,java]
----
@Path("/records")
public class RecordsResource {

@Inject
RestLinksProvider linksProvider;

// ...
}
----

Using this injected bean of type `RestLinksProvider`, you can get the links by type using the method `RestLinksProvider.getTypeLinks` or get the links by a concrete instance using the method `RestLinksProvider.getInstanceLinks`.

== CORS filter

link:https://en.wikipedia.org/wiki/Cross-origin_resource_sharing[Cross-origin resource sharing] (CORS) is a mechanism that
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.resteasy.reactive.links.deployment;

import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OBJECT_NAME;

import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -97,7 +98,7 @@ private RuntimeValue<GetterAccessorsContainer> implementPathParameterValueGetter
String entityType = linkInfo.getEntityType();
for (String parameterName : linkInfo.getPathParameters()) {
// We implement a getter inside a class that has the required field.
// We later map that getter's accessor with a entity type.
// We later map that getter's accessor with an entity type.
// If a field is inside a parent class, the getter accessor will be mapped to each subclass which
// has REST links that need access to that field.
FieldInfo fieldInfo = getFieldInfo(index, DotName.createSimple(entityType), parameterName);
Expand Down Expand Up @@ -140,7 +141,7 @@ private FieldInfo getFieldInfo(IndexView index, DotName className, String fieldN
if (fieldInfo != null) {
return fieldInfo;
}
if (classInfo.superName() != null) {
if (classInfo.superName() != null && !classInfo.superName().equals(OBJECT_NAME)) {
return getFieldInfo(index, classInfo.superName(), fieldName);
}
throw new RuntimeException(String.format("Class '%s' field '%s' was not found", className, fieldName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Inject web links into the response HTTP headers with the "Link" header field.
* Only the response of the REST methods annotated with {@link RestLink} will include the "Link" headers.
* <p>
* The InjectRestLinks annotation can be used at either class or method levels.
* <p>
*
* @see <a href="https://www.rfc-editor.org/info/rfc5988">RFC 5988 Web Linking Standard</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface InjectRestLinks {

/**
* Find all the types available in {@link RestLinkType}.
*
* @return what types of links will be injected.
*/
RestLinkType value() default RestLinkType.TYPE;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,31 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Represents a Web link to be incorporated into the HTTP response.
* Only the response of methods or classes annotated with {@link InjectRestLinks} will include the "Link" headers.
* <p>
* The RestLink annotation can be used at method level.
* <p>
*
* @see <a href="https://www.rfc-editor.org/info/rfc5988">RFC 5988 Web Linking Standard</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RestLink {

/**
* If not set, it will default to the method name.
*
* @return the link relation.
*/
String rel() default "";

/**
* Declares a link for the given type of resources.
* If not set, it will default to the returning type of the annotated method.
*
* @return the type of returning method.
*/
Class<?> entityType() default Object.class;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,81 @@
package io.quarkus.resteasy.reactive.links;

/**
* Manage the link types to be injected in the Web links.
*/
public enum RestLinkType {
/**
* It will inject the links that return the link type {@link RestLink#entityType()} without filtering or searching.
* For example:
*
* <pre>
* &#64;GET
* &#64;Path("/records")
* &#64;RestLink(rel = "list")
* &#64;InjectRestLinks(RestLinkType.TYPE)
* public List<Record> getAll() { // ... }
*
* &#64;GET
* &#64;Path("/records/valid")
* &#64;RestLink
* public List<Record> getValidRecords() { // ... }
*
* &#64;GET
* &#64;Path("/records/{id}")
* &#64;RestLink(rel = "self")
* public TestRecord getById(@PathParam("id") int id) { // ... }
* </pre>
* <p>
* Note that the method `getAll` is annotated with `@InjectRestLinks(RestLinkType.TYPE)`, so when calling to the endpoint
* `/records`, it will inject the following links:
*
* <pre>
* Link: <http://localhost:8080/records>; rel="list"
* Link: <http://localhost:8080/records/valid>; rel="getValidRecords"
* </pre>
* <p>
* The method `getById` is not injected because it's instance based (it depends on the field `id`).
*/
TYPE,

/**
* It will inject all the links that return the link type {@link RestLink#entityType()}.
* For example:
*
* <pre>
*
* &#64;GET
* &#64;RestLink(rel = "list")
* public List<TestRecord> getAll() { // ... }
*
* &#64;GET
* &#64;Path("/records/{id}")
* &#64;RestLink(rel = "self")
* &#64;InjectRestLinks(RestLinkType.INSTANCE)
* public TestRecord getById(@PathParam("id") int id) { // ... }
*
* &#64;GET
* &#64;Path("/records/{slug}")
* &#64;RestLink
* public TestRecord getBySlug(@PathParam("slug") String slug) { // ... }
*
* &#64;DELETE
* &#64;Path("/records/{id}")
* &#64;RestLink
* public TestRecord delete(@PathParam("slug") String slug) { // ... }
* </pre>
* <p>
* Note that the method `getById` is annotated with `@InjectRestLinks(RestLinkType.INSTANCE)`, so when calling to the
* endpoint `/records/1`, it will inject the following links:
*
* <pre>
* Link: <http://localhost:8080/records>; rel="list"
* Link: <http://localhost:8080/records/1>; rel="self"
* Link: <http://localhost:8080/records/theSlugValueInRecord1>; rel="getBySlug"
* Link: <http://localhost:8080/records/1>; rel="delete"
* </pre>
* <p>
* Now, all the links have been injected.
*/
INSTANCE
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ public void handle(ResteasyReactiveRequestContext context) {
}

private Collection<Link> getLinks(Response response) {
RestLinksProvider provider = getRestLinksProvider();

if ((restLinkData.getRestLinkType() == RestLinkType.INSTANCE) && response.hasEntity()) {
return getTestLinksProvider().getInstanceLinks(response.getEntity());
return provider.getInstanceLinks(response.getEntity());
}
return getTestLinksProvider()
.getTypeLinks(restLinkData.getEntityType() != null ? entityTypeClass() : response.getEntity().getClass());
return provider.getTypeLinks(
restLinkData.getEntityType() != null ? entityTypeClass() : response.getEntity().getClass());
}

private Class<?> entityTypeClass() {
Expand All @@ -46,7 +48,7 @@ private Class<?> entityTypeClass() {
}
}

private RestLinksProvider getTestLinksProvider() {
private RestLinksProvider getRestLinksProvider() {
return Arc.container().instance(RestLinksProvider.class).get();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,21 @@

import javax.ws.rs.core.Link;

/**
* An injectable bean that contains methods to get the web links at class and instance levels.
*/
public interface RestLinksProvider {

/**
* @param elementType The resource type.
* @return the web links associated with the element type.
*/
Collection<Link> getTypeLinks(Class<?> elementType);

/**
* @param instance the resource instance.
* @param <T> the resource generic type.
* @return the web links associated with the instance.
*/
<T> Collection<Link> getInstanceLinks(T instance);
}