Skip to content

Commit

Permalink
Document Resteasy Reactive Links
Browse files Browse the repository at this point in the history
Added documentation for resteasy reactive links.
  • Loading branch information
Sgitario committed Apr 28, 2022
1 parent 127c868 commit 3e58d2d
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 6 deletions.
75 changes: 75 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,81 @@ 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.

== 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);
}

0 comments on commit 3e58d2d

Please sign in to comment.