Links: Table of Contents | Single HTML

Chapter 13. Declarative Hyperlinking

RESTful APIs must be hypertext-driven. JAX-RS currently offers UriBuilder to simplify URI creation but Jersey adds an additional annotation-based alternative that is described here.

Important

This API is currently under development and experimental so it is subject to change at any time.

13.1. Dependency

To use Declarative Linking you need to add jersey-declarative-linking module to your pom.xml file:

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-declarative-linking</artifactId>
    <version>2.42</version>
</dependency>

Additionally you will need to add the following dependencies, if you are not deploying into a container that is already including them:

<dependency>
    <groupId>jakarta.el</groupId>
    <artifactId>jakarta.el-api</artifactId>
    <version>3.0.3</version>
</dependency>

<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>3.0.4</version>
</dependency>

If you're not using Maven make sure to have all needed dependencies (see jersey-declarative-linking) on the classpath.

13.2. Links in Representations

Links are added to representations using the @InjectLink annotation on entity class fields. The Jersey runtime is responsible for injecting the appropriate URI into the field prior to serialization by a message body writer. E.g. consider the following resource and entity classes:

@Path("widgets")
public class WidgetsResource {
    @GET
    public Widgets get() {
        return new Widgets();
    }
}

public class Widgets {
    @InjectLink(resource=WidgetsResource.class)
    URI u;
}

After a call to WidgetsResource#get, the Jersey runtime will inject the value "/context/widgets" [1] into the returned Widgets instance. If an absolute URI is desired instead of an absolute path then the annotation can be changed to @InjectLink(resource=WidgetsResource.class, style=ABSOLUTE).

The above usage works nicely when there's already a URI template on a class that you want to reuse. If there's no such template available then you can use a literal value instead of a reference. E.g. the following is equivalent to the earlier example: @InjectLink(value="widgets", style=ABSOLUTE).

13.3. List of Link Injection

You can inject multiple links into an array or a List collection type. E.g.:

@InjectLinks({@InjectLink(resource=WidgetsResource.class, rel = "self")})
List<Link> links

The field doesn't need to be initialized. However, if it already contains a collection with manually created links, then it will merge those with the generated links into a new collection which then replaces the field value.

13.4. Links from Resources

As an alternative to defining the links in the entity class, they can also be defined in the resource classes by annotating the resource methods with @ProvideLink. This has the benefit, that the target method is already known and doesn't need to be referenced. Other than that it has the same parameters and behaviors as @InjectLink. The entity classes need to have a field annotated with @InjectLinks, which can be empty.

The @ProvideLink annotation can be repeated to add links to different entities using different options. Entities are defined via the value property. If the entities are similar in structure they can also be declared as an array. @ProvideLink also works with class hierarchies, e.g., contributions defined for a superclass will also be injected into the derived classes (interfaces are not supported).

@ProvideLink(value = Order.class,rel = "self",
     bindings = @Binding(name = "orderId", value = "${instance.id}"))
@ProvideLink(value = PaymentConfirmation.class, rel = "order",
     bindings = @Binding(name = "orderId", value = "${instance.orderId}"))
@GET
@Path("/{orderId}")
public Response get(@PathParam("orderId") String orderId)

13.5. Binding Template Parameters

Referenced or literal templates may contain parameters. Two forms of parameters are supported:

  • URI template parameters, e.g. widgets/{id} where {id} represents a variable part of the URI.

  • EL expressions, e.g. widgets/${instance.id} where ${instance.id} is an EL expression.

Parameter values can be extracted from three implicit beans:

instance

Represents the instance of the class that contains the annotated field.

entity

Represents the entity class instance returned by the resource method.

resource

Represents the resource class instance that returned the entity.

By default URI template parameter values are extracted from the implicit instance bean, i.e. the following two annotations are equivalent:

@InjectLink("widgets/{id}")

@InjectLink("widgets/${instance.id}")

The source for URI template parameter values can be changed using the @Binding annotation, E.g. the following three annotations are equivalent:

@InjectLink(value="widgets/{id}", bindings={
    @Binding(name="id" value="${resource.id}"}
)

@InjectLink(value="widgets/{value}", bindings={
    @Binding("${resource.id}")})
@InjectLink("widgets/${resource.id}")

13.6. Conditional Link Injection

Link value injection can be made conditional by use of the condition property. The value of this property is a boolean EL expression and a link will only be injected if the condition expression evaluates to true. E.g.:

@InjectLink(value="widgets/${instance.id}/offers",
    condition="${instance.offers}")
URI offers;

In the above, a URI will only be injected into the offers field if the offers property of the instance is true.

13.7. Link Headers

HTTP Link headers can also be added to responses using annotations. Instead of annotating the fields of an entity class with @InjectLink, you instead annotate the entity class itself with @InjectLinks. E.g.:

@InjectLinks(
    @InjectLink(value="widgets/${resource.nextId}", rel="next")
)

The above would insert a HTTP Link header into any response whose entity was thus annotated. The @InjectLink annotation contains properties that map to the parameters of the HTTP Link header. The above specifies the link relation as next. All properties of the @InjectLink annotation may be used as described above.

Multiple link headers can be added by use of the @InjectLinks annotation which can contain multiple @InjectLink annotations.

Resource links via @ProvideLink are currently not supported for link headers.

13.8. Prevent Recursive Injection

By default, Jersey will try to recursively find all @InjectionLink annotations in the members of your object unless this member is annotated with @XmlTransient. But in some cases, you might want to control which member will be introspected regardless of the @XmlTransient annotation. You can prevent Jersey to look into an object by adding @InjectLinkNoFollow to a field.

@InjectLinkNoFollow
Context context;

13.9. Meta-annotation support

The @ProvideLink annotation can be used as a meta-annotation, i.e., annotating your own annotation. This enables you to create custom annotations to reuse @ProvideLink configurations instead of copy pasting them on each method. There is a special marker class ProvideLink.InheritFromAnnotation that can be used in place of the actual entity class, this indicates that the Class<?> value() from the custom annotation should be used instead. Repeated annotations are currently unsupported for this feature. Also the Class<?> value() method must return a single class and not an array of classes.

Here is an example (getter/setter omitted for brevity) of how a meta annotation can be used. The example app uses a Page class as a base class for all entities that contain paged data.

public class Page {
    private int number;

    private int size;

    private boolean isPreviousPageAvailable;

    private boolean isNextPageAvailable;

    @InjectLinks
    private List<Link> links;
}

Instead of duplicating the @ProvideLink annotations for the next and previous links on every method, we create the following @PageLinks annotation.

@ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "next",
    bindings = {
        @Binding(name = "page", value = "${instance.number + 1}"),
        @Binding(name = "size", value = "${instance.size}"),
    }, condition = "${instance.nextPageAvailable}")
@ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "prev",
    bindings = {
        @Binding(name = "page", value = "${instance.number - 1}"),
        @Binding(name = "size", value = "${instance.size}"),
    }, condition = "${instance.previousPageAvailable}")
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PageLinks {
  Class<?> value();
}

The annotation can the then be used on the resource methods with the actual entity class as value.

@PageLinks(OrderPage.class)
@GET
public Response list(@QueryParam("page") @DefaultValue("0") int page,
                     @QueryParam("size") @DefaultValue("20") int size)

The entity just extends from Page and declares its content. It is necessary to use distinct classes instead of just a generic page to have a unique target for @ProvideLink, otherwise every method annotated with @ProvideLink(value=Page.class) would contribute to the entity.

public class OrderPage extends Page {
    private List<Order> orders;
}

13.10. Configure and register

In order to add the Declarative Linking feature register DeclarativeLinkingFeature

Example 13.1. Creating JAX-RS application with Declarative Linking feature enabled.

// Create JAX-RS application.
final Application application = new ResourceConfig()
        .packages("org.glassfish.jersey.examples.linking")
        .register(DeclarativeLinkingFeature.class);




[1] Where /context is the application deployment context.