Skip to main content
Last updated July 08, 2011 13:56, by spericas
Feedicon  

Declarative Hyperlinking by Example (Jersey)


This section describes declarative hyperlinking as supported in Jersey. Declarative hyperlinking uses annotations to help with the generation of links between resources. Links can be embedded in representations or be part of the headers (i.e., link headers). The annotations @Ref and @Link are used for these purposes.

The example that follows provides a clear separation between model, representation (view) and resource (controller). Hyperlinking will be added to the representation returned by the resource class. Let us start by understanding the model:

public class ItemModel {
    String name;

    public ItemModel(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

public class ItemsModel {
    private List<ItemModel> items;
    private static ItemsModel instance;

    public static synchronized ItemsModel getInstance() { 
        ... 
    }
    private ItemsModel() {
        items = new ArrayList<ItemModel>();
        items.add(new ItemModel("Item 0"));
        items.add(new ItemModel("Item 1"));
        items.add(new ItemModel("Item 2"));
    }
    ...
}

An ItemModel is composed of a single string. ItemsModel is an ordered collection of ItemModel. As such, items have a next (except for last) and a prev (except for first). The representation of an item includes links to prev and next and uses @Ref to generate these:

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="item")
public class ItemRepresentation {
    @XmlElement
    private String name;

    @Ref(resource=ItemResource.class,
        style = Style.ABSOLUTE,
        bindings=@Binding(name="id", value="${resource.id}")
    )
    @XmlElement
    URI self;

    @Ref(resource=ItemResource.class,
        style = Style.ABSOLUTE,
        condition="${resource.next}",
        bindings=@Binding(name="id", value="${resource.nextId}")
    )
    @XmlElement
    URI next;

    @Ref(resource=ItemResource.class,
        style = Style.ABSOLUTE,
        condition="${resource.prev}",
        bindings=@Binding(name="id", value="${resource.prevId}")
    )
    @XmlElement
    URI prev;

    public ItemRepresentation(String name) {
        this.name = name;
    }
}

Let us look at the @Ref for the next item in more detail:

    @Ref(resource=ItemResource.class,
        style = Style.ABSOLUTE,
        condition="${resource.next}",
        bindings=@Binding(name="id", value="${resource.nextId}")
    )
    @XmlElement
    URI next;

This instance of @Ref states that (i) the link should be generated based on the path defined in ItemResource (ii) the link should be absolute (iii) it should only be generated if ItemResource.isNext() returns true and replacing the URI template parameter id by ItemResource.getNextId(). The ItemResource class is shown next:

@Path("{id}")
@Produces(MediaType.APPLICATION_XML)
public class ItemResource {
    private ItemsModel itemsModel;
    private ItemModel itemModel;
    private String id;

    public ItemResource(@PathParam("id") String id) {
        ...
    }
    @GET
    public ItemRepresentation get() {
        return new ItemRepresentation(itemModel.getName());
    }
    public boolean isNext() {
        return itemsModel.hasNext(id);
    }
    public String getNextId() {
        return itemsModel.getNextId(id);
    }
    ...
}

The result of executing a GET /app-context/1 returns:

Content-Type:application/xml
...
<item>
  <name>Item 1</name>
  <self>http://localhost:8080/app-context/1</self>
  <next>http://localhost:8080/app-context/2</next>
  <prev>http://localhost:8080/app-context/0</prev>
</item>

Note how the self, next and prev links have been automatically generated based on the @Ref's.

If link headers are preferred over links in representations, all that is needed it to decorate the LinkReprensentation class instead of its members, and remove the members that we do not want to be part of the representation. Assuming all links are moved into headers, the representation class can would be defined as follows:

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="item")
@Links({
    @Link(
        value=@Ref(
            resource=ItemResource.class,
            style = Style.ABSOLUTE,
            condition="${resource.next}",
            bindings=@Binding(name="id", value="${resource.nextId}")
        ),
        rel="next"
    ),
    @Link(
        value=@Ref(
            resource=ItemResource.class,
            style = Style.ABSOLUTE,
            condition="${resource.prev}",
            bindings=@Binding(name="id", value="${resource.prevId}")
        ),
        rel="prev"
    )
})
public class ItemRepresentation {
    @XmlElement
    private String name;
}

The @Links annotation includes the same information as in the previous version, except for the rel element. This element is used to define the relationship identified by the link (which was previously derived from the variable name). The result of executing a GET /app-context/1 now returns:

Content-Type:application/xml
Link:<http://localhost:8080/jersey-server-linking-sample/2>;rel=next
Link:<http://localhost:8080/jersey-server-linking-sample/0>;rel=prev
...
<item>
  <name>Item 1</name>
</item>

Naturally, the two approaches can be combined to return both links in headers and in representations.

Jersey supports 3 implicit beans that can be used in EL expressions that are part of @Ref and @Link:

  • instance: the instance in which the annotation is used (model instance)
  • entity: the class of the instance in which the annotation is used (model class)
  • resource: the resource class instance that returns the entity (resource class)

For more information see Declarative Hyperlinking in Jersey.

 
 
Close
loading
Please Confirm
Close