5

I have several controllers that are automatically creating REST endpoints.

@RepositoryRestResource(collectionResourceRel = "books", path = "books")
public interface BooksRepository extends CrudRepository<Books, Integer> {
    public Page<Books> findTopByNameOrderByFilenameDesc(String name);
}

When I visit: http://localhost:8080/Books

I get back:

{
    "_embedded": {
        "Books": [{
            "id": ,
            "filename": "Test123",
            "name": "test123",
            "_links": {
                "self": {
                    "href": "http://localhost:8080/books/123"
                },
                "Books": {
                    "href": "http://localhost:8080/books/123"
                }
            }
        }]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/books"
        },
        "profile": {
            "href": "http://localhost:8080/profile/books"
        },
        "search": {
            "href": "http://localhost:8080/books/search"
        },
        "page": {
            "size": 20,
            "totalElements": 81,
            "totalPages": 5,
            "number": 0
        }
    }
}

When I create my own controller:

@Controller
@RequestMapping(value = "/CustomBooks")
public class CustomBooksController {
    @Autowired
    public CustomBookService customBookService;

    @RequestMapping("/search")
    @ResponseBody
    public Page<Book> search(@RequestParam(value = "q", required = false) String query,
                                 @PageableDefault(page = 0, size = 20) Pageable pageable) {
        return customBookService.findAll();
    }
}

I'll get a response back that looks nothing like the automatically generated controller response:

{
    "content": [{
        "filename": "Test123",
        "name" : "test123"
    }],
    "totalPages": 5,
    "totalElements": 81,
    "size": 20,
    "number": 0,
}

What do I need to do to make my response look like the automatically generated response? I want to keep it consistent, so I don't have to rewrite code for a different response. Should I be doing it a different way?


Edit: Found this: Enable HAL serialization in Spring Boot for custom controller method

But I don't understand what I need to change in my REST Controller to enable: PersistentEntityResourceAssembler. I've searched on Google for PersistentEntityResourceAssembler, but it keeps leading me back to similar pages without much of an example (or the example doesn't seem to work for me).

Community
  • 1
  • 1
Kevin Vasko
  • 1,561
  • 3
  • 22
  • 45

1 Answers1

6

As @chrylis suggested you should replace your @Controller annotation with @RepositoryRestController for spring-data-rest to invoke it's ResourceProcessors for customizing the given resource.

For you resource to follow the HATEOAS specification (like your spring-data-rest BooksRepository) your method declaration return type should be like HttpEntity<PagedResources<Resource<Books>>> For converting your Page object to PagedResources:

  • You need to autowire this object.

    @Autowired private PagedResourcesAssembler<Books> bookAssembler;

  • Your return statement should be like

    return new ResponseEntity<>(bookAssembler.toResource(customBookService.findAll()), HttpStatus.OK);

These changes should help you to get a org.springframework.hateoas.Resources compliant response containing the "_embedded" and "_links" attribute.

Alex Ciocan
  • 2,272
  • 14
  • 20
  • For a reference you can use this response[link]http://stackoverflow.com/questions/31758862/enable-hal-serialization-in-spring-boot-for-custom-controller-method?noredirect=1&lq=1 – Alex Ciocan Dec 02 '16 at 11:20
  • 1
    Do you mean the "@RestController"? I don't see "@ResourceRestController" anywhere online. When I add the "@Autowired" private PagedResourcesAssembler intelliJ complains that it can't autowire it. It does compile and springboot does start up. However, when I make a call to my controller I get this error "There was an unexpected error (type=Internal Server Error, status=500). Could not marshal [PagedResource { content: [Resource { content:...." on top of that this error page is in XML and not JSON. It shouldn't matter that I'm pulling this from Solr would it? Books is my model. – Kevin Vasko Dec 02 '16 at 19:55
  • In order to have the above functionality your CustomBooksController should be in the same project as your BooksRepository class. – Alex Ciocan Dec 02 '16 at 20:39
  • com.myproject.repositories has my BooksRepository and CustomBooksController is in the com.myproject.controllers package. – Kevin Vasko Dec 02 '16 at 20:48
  • I corrected a typo in the annotation`@ResourceRestController` should be `@RepositoryRestController`. Let me know if it fixes your problem. – Alex Ciocan Dec 02 '16 at 20:50
  • Ok, that solution works (partially). It was the "@RepositoryRestController" it appears. For some odd reason pageable is always coming back null no matter what now though. "@PageableDefault(page = 0, size = 20) Pageable pageable" in my controller. When it goes to pass that to my findBook(query, pageable) it errors out saying pageable is null (almost as if "@PageableDefault" is working). This appears to have happened after I changed it from "@Controller" to "@RepoistoryRestController" – Kevin Vasko Dec 02 '16 at 21:14
  • 1
    A couple of issues--constructor injection (generally), and then I *believe* that SDR will automatically handle the resource assembly if you just return a `Collection`. Using a `ResponseEntity` is only ever needed if you need to programmatically set response headers or status (instead of using annotations or exceptions). This should make the code significantly simpler. – chrylis -cautiouslyoptimistic- Dec 02 '16 at 21:19
  • 1
    If you want to return a single entity use `PersistentEntityResourceAssembler`. It will be automatically injected if present in the method declaration (@AlexCiocan, nice to see your answers here :). – Adam Bogdan Boczek Feb 18 '17 at 15:19