Monday 3 June 2013

Exposing and Managing Links with Spring HATEOAS

As a follow up to the previous Spring HATEOAS post, this post will cover how to use the @ExposesResourceFor annotation as an alternative to using the resource's Controller to obtain links.

It'll also cover how to find links based on a particular relation type in a hypermedia enabled representation.

In addition to the classes in the previous post, the SettlementController class is responsible for settling bets and exposes one method, settleBet, in order to do this. To return a link to the settled bet, the SettlementController uses the linkTo method on the ControllerLinkBuilder class:

@RequestMapping(method = RequestMethod.PUT, value = "/{betId}")
ResponseEntity<SettlementResource> settleBet(@PathVariable Long betId) {

    Settlement settlement = this.settlementService.settleBet(betId);  
    SettlementResource resource = 
        settlementResourceAssembler.toResource(settlement);
    resource.add(
        linkTo(BetController.class).slash(betId).withSelfRel());
    return new ResponseEntity<SettlementResource>(resource, HttpStatus.CREATED);
}  

An alternative approach to this is to enable EntityLinks. By adding the @ExposesResourcesFor annotation to the BetController, the SettlementController need only know about the resource and not it's Controller class.

@Controller
@ExposesResourceFor(Bet.class)
@RequestMapping("/bets")
public class BetController {
...
}

The SettlementController by way of the EntityLinks interface can now obtain the link to a single resource or a link to a resource collection. It can either get the Link or a LinkBuilder. With the latter approach, the relation types can be overwritten:


@Controller
@RequestMapping("/settlements")
public class SettlementController {

    private SettlementService settlementService;
    private SettlementResourceAssembler settlementResourceAssembler;
    private EntityLinks entityLinks;

    @Autowired
    public SettlementController(SettlementService settlementService,
        SettlementResourceAssembler settlementResourceAssembler, EntityLinks entityLinks) {
        this.settlementService = settlementService;
        this.settlementResourceAssembler = settlementResourceAssembler;
        this.entityLinks = entityLinks;
    }

    @RequestMapping(method = RequestMethod.PUT, value = "/{betId}")
    ResponseEntity<SettlementResource> settleBet(@PathVariable Long betId) {

        Settlement settlement = this.settlementService.settleBet(betId);
        SettlementResource resource = 
            settlementResourceAssembler.toResource(settlement);
        resource.add(this.entityLinks.linkToSingleResource(Bet.class, betId));
        return new ResponseEntity<SettlementResource>(
            resource, HttpStatus.CREATED);
    } 
 
}

For a collection link, the call would be:

resource.add(this.entityLinks.linkToCollectionResource(Bet.class)); 

To obtain the builders:

LinkBuilder linkFor = this.entityLinks.linkFor(Bet.class);
LinkBuilder linkForSingleResource = 
    this.entityLinks.linkForSingleResource(Bet.class, betId);

To enable all this functionality, the @EnableEntityLinks annotation must be in your Spring configuration.

Another useful feature that comes with Spring HATEOAS, is the ability to discover links. For a given hypermedia enabled representation, the Link Discoverer API can find a link or links for a particular relation type. For example, from the previous post, to find the cancelBet relation type link of a ResourceSupport object would be:

LinkDiscoverer discoverer = new DefaultLinkDiscoverer();
Link link = discoverer.findLinkWithRel("cancelBet", resource.toString());

The DefaultLinkDiscoverer will expect JSON but since 0.5 of Spring HATEOAS, the HAL variant has been supported via the HalLinkDiscoverer class.

Although still evolving, the Spring HATEOAS project provides a good foundation for implementing a HATEOAS compliant REST service. But if all you require is to expose and manage domain objects via a REST web service then the Spring Data REST project which applies the HATEOAS constraint to persisted entities is worth considering and the Restbucks example an ideal tutorial.