Your Friend Ken
Your Friend Ken

Reputation: 8872

How to return @RepositoryRestResource style responses from a @RestController

Using the @RepositoryRestResource generates paths and injects all the necessary HATEOAS links for a REST API, but when I return the same results from the repository using a controller the JSON structure is different and there are no HATEOAS links.

How would I return get the same JSON structure from a controller as the RepositoryRestResource generated paths?

// /accounts (RepositoryRestResource JSON structure)
{
    _embedded: {
        accounts: []
    },
    _links: {
        first: {},
        self: {},
        next: {},
        last: {},
        profile: {},
        search: {}
    },
    page: {
    size: 20,
    totalElements: 35,
    totalPages: 2,
    number: 0
    }
}

// /my-accounts (RestController JSON structure)
{
    content: [ ... ], // accounts
    pageable: {},
    totalPages: 1,
    totalElements: 2,
    last: true,
    size: 20,
    number: 0,
    sort: {},
    numberOfElements: 2,
    first: true
}

REST Repository:

@RepositoryRestResource(collectionResourceRel = "accounts", path = "accounts", itemResourceRel = "account")
public interface AccountRepository extends PagingAndSortingRepository<Account, Long> {

    @RestResource(path = "owner", rel = "owner")
    Page<Account> findByOwner(@Param("username") String owner,Pageable p);
}

REST Controller:

@RestController
public class AccountController {

    private AccountRepository repo;

    @Autowired
    public AccountController(AccountRepository repo) {
        this.repo = repo;
    }

    @RequestMapping(
        path = "/my-accounts",
        method = RequestMethod.GET,
        produces = "application/hal+json")
    public ResponseEntity<Page<Account>> getStatus(
        @RequestParam(value = "page", defaultValue = "0", required = false) int page,
        @RequestParam(value = "size", defaultValue = "20", required = false) int size,
        Authentication authentication) {

        String username =  authentication.getName();

        Page<Account> accounts = repo.findByOwner(username, PageRequest.of(page, size));

        return ResponseEntity.ok(accounts);
    }
}

Upvotes: 2

Views: 1093

Answers (2)

Danila Kiver
Danila Kiver

Reputation: 3758

Basically, Spring Data REST is just a default implementation of a boilerplate code (like controllers) which people usually write exposing Spring Data repositories via REST and using Spring HATEOAS, i.e. trying to reproduce exactly the same effect with your hand-written controller means just writing the whole Spring Data REST on your own, so, it is a bad idea. Luckily, some parts are easy to reproduce though.

If you speak only about adding paging links to your controller's output (and not implementing other things like search controller, to which a link is present in your sample Spring Data REST controller output), you may take a look at how Spring Data REST does it. It uses Spring Data's PagedResourcesAssembler, which accepts a Page and creates a HATEOAS resource with the required navigation links.

So, to add the paging links, you must inject a PagedResourcesAssembler instance in your controller and use it:

public ResponseEntity<PagedResources> getStatus(
    @RequestParam(value = "page", defaultValue = "0", required = false) int page,
    @RequestParam(value = "size", defaultValue = "20", required = false) int size,
    Authentication authentication,
    PagedResourcesAssembler assembler) {

    String username =  authentication.getName();

    Page<Account> accounts = repo.findByOwner(username, PageRequest.of(page, size));

    return ResponseEntity.ok(assembler.toResource(accounts));
}

Upvotes: 1

Sebastiaan van den Broek
Sebastiaan van den Broek

Reputation: 6331

Not sure, but this might do the trick:

return ResponseEntity.ok(new org.springframework.hateoas.Resource<>(accounts));

If not then you could wrap the accounts in a class that extends ResourceSupport. So just create some class AccountSupport extends ResourceSupport and add the required links in there. It has a lot of utility methods like

add(linkTo(AccountController.class).withSelfRel());

or for links to individual Accounts:

add(linkTo(AccountController.class).slash(idOfYourAccountInstance).withSelfRel())

Upvotes: 0

Related Questions