How to return #RepositoryRestResource style responses from a #RestController - spring

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);
}
}

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));
}

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())

Related

How to write #ApiResponse which may return a Class or a List of that class using OpenAPI 3 Swagger in Spring Boot

As written in documentation we can use anyOf with #Schema if we want to define multiple responses.
#ApiResponse(responseCode = "201", description = "OK",
content = #Content(schema = #Schema(anyOf = {Product.class, Activity.class})))
My controller returns either a Product or a List<Product>. I would like to specify this in my OpenAPI 3 documentation.
I would like to know if it's possible.
If Yes, then how?
If No, then is there any workaround?
I don't only want to specify List.class. I want to specify List<Product>.
P.S.:- Searching on Google didn't get me any results that I can use.
Ok, thats a tough one.
Basically if you really want to return a List of Objects or one Object, then you can create a basic interface like this
public interface Response {
}
And then you can create your Object, which implements the response
public class Hello implements Response {
private String message;
public Hello(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
}
}
Finally we can create the List of our Object. For that we need to create a class, which extends the ArrayList and implements our Interface
public class HelloList extends ArrayList<Hello> implements Response {
}
After that we can just set our schema as implementation
#ApiResponse(responseCode = "200", description = "hello world", content = #Content(mediaType = "application/json", schema = #Schema(implementation = Response.class)))
On the Clientside you need to check the instance of the Response-Object, so that you can parse either the Object or the List
Response response = someCall();
if (response instanceof Hello) {
System.out.println(processHello((Hello) response);
}
if (response instanceof HelloList) {
System.out.println(processHelloList((HelloList) response);
}
This example works, but its very very complex und confusing. A better way to design your api, would be to return just the list. I don't see the benefit to seperate one object or the list of it.

Does Springboot #RequestParam support List<Object> params in get request

Springboot #RequestParam annotation can pass basic list parameters, just like:
#GetMapping("param")
public String requestParamDemo(#RequestParam("list")List<Long> list) {
System.out.println(list.toString());
return list.toString();
}
and in postman, GET request localhost:8998/param?list=1,3,100 is works, "1,3,100" can be converted to List, but how or if Springboot #RequestParam support custom Generics such as below:
#GetMapping("objlist")
public String paramWithObjList(#RequestParam("objList")List<AaParam> objList) {
System.out.println("objList = " + objList);
return objList.toString();
}
import lombok.Data;
#Data
public class AaParam {
private int id;
private String name;
}
postman request: GET url: localhost:8998/objlist?objlist=[{id: 1, name: "aa"},{id: 2, name: "bb"}]
I tested in local and it didn't work.
Want to know if #RequestParam can do that or any alternative way to implement it.
Thanks!
Hope this suggestion holds good for your requirement.
I would suggest going with #RequestBody code will look like this
#PostMapping(path = "/objlist", consumes = "application/json", produces = "application/json")
public String paramWithObjList(#RequestBody List<AaParam> objList) {
System.out.println("objList = " + objList);
return objList.toString();
}
Note: please add some ObjectPaser in your actual logic (for example Jackson )
Postman request would be like this

How to design a REST service to response with different levels of information?

I would like to have a single service that can respond with different levels of information:
Level 1:
{
"field_1": "value_1",
"field_2": "value_2"
}
Level 2:
{
"field_1": "value_1",
"field_2": "value_2",
"field_3": "value_3"
}
Level 3:
{
"field_1": "value_1",
"field_2": "value_2",
"field_3": "value_3",
"field_4": "value_4"
}
My first approach is using a parameter in the request such like this:
#RestController
public <ResponseObject> getInfo(..., #RequestParam levelInfo) {
service.getInfo(..., levelInfo);
}
#Service
public <ResponseObject> getInfo(..., levelInfo) {
if (levelInfo == 1)
return setupResponseLevel1();
if (levelInfo == 2)
return setupResponseLevel1();
if (levelInfo == 3)
return setupResponseLevel1();
}
private <ResponseObject> setupResponseLevel1() {
responseObject.setField_1(repository.getField1());
responseObject.setField_2(repository.getField2());
return responseObject;
}
private <ResponseObject> setupResponseLevel2() {
responseObject = this.setupResponseLevel1();
responseObject.setField_3(repository.getField3());
return responseObject;
}
private <ResponseObject> setupResponseLevel3() {
responseObject = this.setupResponseLevel2();
responseObject.setField_4(repository.getField4());
return responseObject;
}
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class ResponseObject {
private String field_1;
private String field_2;
private String field_3;
private String field_4;
// Getters & setters...
}
My API will be very large and I need to find a pattern that I can reuse in many services.
Do you know any cleaner way to do it?
EDIT: I'm sorry, I did not explain with the properly precision.
I like the ideas of the answers but I have added more code to the #Service so that you understand that the problem is not only the presentation of the response (JSON) but also the saving of the cost of obtaining the information (queries to BBDD).
You could use #JsonView annotation for that. A simple example would look like this
public class Views {
public static class LevelOne {
}
public static class LevelTwo extends LevelOne {
}
}
public class ResponseObject {
#JsonView(Views.LevelOne.class)
private String field_1;
#JsonView(Views.LevelOne.class)
private String field_2;
#JsonView(Views.LevelTwo.class)
private String field_3;
}
and method for serializing
public String toJson(Class<?> view) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writerWithView(view).writeValueAsString(this);
}
if the toJson method is called with Views.LevelOne.class as an argument, only the field_1 and field_2 will be serialized. If it is called with Views.LevelTwo.class, all three fields would be serialized.
You can choose which view to use based on parameter as you suggested.
What about a URL scheme along the lines of:
https://your.api/info/2
then
#RequestMapping("/info/{levelInfo}")
public <ResponseObject> getInfo(..., #PathVariable String levelInfo) {
where 2 is the level. As the level is part of your domain, i.e. it has relevance to your clients, you could capture that in the database with a level column for each field, 1,2,3,4 etc then use the repository to:
repository.findByLevelLessThanEqual(levelInfo)
which could return a package of information containing all the required fields. So if you ask for level 1, you only get level 1 fields. If you ask for level 4, you get all fields up to and including level 4
Spring JPA LessThanEqual documentation

Spring Data Rest, SpringFox and JpaRepository custom finders

NB: using Spring Boot 1.4.2 + SpringFox 2.6.0
Hi, I'm having an issue with Swagger 2 forms on my API documentation over a #RepositoryRestResource. The code below works fine (REST access OK):
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends JpaRepository<Person, Long> {
Person findByLastName(#Param("name") String name);
}
And the HATEOAS links are right too: calling URL /api/people/search
ends up with this (notice parameter "name"):
{
"_links": {
"findByLastName": {
"href": "http://localhost:8080/api/people/search/findByLastName{?name}",
"templated": true
},
"self": {
"href": "http://localhost:8080/api/people/search"
}
}
}
The REST API is ok: URL /api/people/search/findByLastName?name=foobar returns data when executed with a browser
BUT in Swagger the GET parameter type is interpreted as "body" instead of "query" and the form submission (curl ... -d 'foobar'...) fails in 404, attempting to submit "name" as request body.
So I tried to set Swagger explicitly, like this:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends JpaRepository<Person, Long> {
#ApiOperation("Find somebody by it's last name")
#ApiImplicitParams({
#ApiImplicitParam(name = "name", paramType = "query")
})
Person findByLastName(#Param("name") #ApiParam(name = "name") String name);
}
without any success, despite the fact that "name" is well retained in the form as the parameter name in this example :-(
body parameter type on GET query
Does anyone know what could be done to make that Swagger form to work? Thx for your help
This is it : #Param configures Spring Data REST, while #RequestParam fits Swagger
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends JpaRepository<Person, Long> {
// #Param Spring Data REST : Use #Param or compile with -parameters on JDK 8
// #RequestParam Swagger : paramType=query cf. $Api*Param
Person findByLastName(#Param("name") #RequestParam("name") String name);
}
Me happy!

How to correctly use PagedResourcesAssembler from Spring Data?

I'm using Spring 4.0.0.RELEASE, Spring Data Commons 1.7.0.M1, Spring Hateoas 0.8.0.RELEASE
My resource is a simple POJO:
public class UserResource extends ResourceSupport { ... }
My resource assembler converts User objects to UserResource objects:
#Component
public class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> {
public UserResourceAssembler() {
super(UserController.class, UserResource.class);
}
#Override
public UserResource toResource(User entity) {
// map User to UserResource
}
}
Inside my UserController I want to retrieve Page<User> from my service and then convert it to PagedResources<UserResource> using PagedResourcesAssembler, like displayed here: https://stackoverflow.com/a/16794740/1321564
#RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(#PageableDefault Pageable p, PagedResourcesAssembler assembler) {
Page<User> u = service.get(p)
return assembler.toResource(u);
}
This doesn't call UserResourceAssembler and simply the contents of User are returned instead of my custom UserResource.
Returning a single resource works:
#Autowired
UserResourceAssembler assembler;
#RequestMapping(value="{id}", method=RequestMethod.GET)
UserResource getById(#PathVariable ObjectId id) throws NotFoundException {
return assembler.toResource(service.getById(id));
}
The PagedResourcesAssembler wants some generic argument, but then I can't use T toResource(T), because I don't want to convert my Page<User> to PagedResources<User>, especially because User is a POJO and no Resource.
So the question is: How does it work?
EDIT:
My WebMvcConfigurationSupport:
#Configuration
#ComponentScan
#EnableHypermediaSupport
public class WebMvcConfig extends WebMvcConfigurationSupport {
#Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(pageableResolver());
argumentResolvers.add(sortResolver());
argumentResolvers.add(pagedResourcesAssemblerArgumentResolver());
}
#Bean
public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
return new HateoasPageableHandlerMethodArgumentResolver(sortResolver());
}
#Bean
public HateoasSortHandlerMethodArgumentResolver sortResolver() {
return new HateoasSortHandlerMethodArgumentResolver();
}
#Bean
public PagedResourcesAssembler<?> pagedResourcesAssembler() {
return new PagedResourcesAssembler<Object>(pageableResolver(), null);
}
#Bean
public PagedResourcesAssemblerArgumentResolver pagedResourcesAssemblerArgumentResolver() {
return new PagedResourcesAssemblerArgumentResolver(pageableResolver(), null);
}
/* ... */
}
SOLUTION:
#Autowired
UserResourceAssembler assembler;
#RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(#PageableDefault Pageable p, PagedResourcesAssembler pagedAssembler) {
Page<User> u = service.get(p)
return pagedAssembler.toResource(u, assembler);
}
You seem to have already found out about the proper way to use but I'd like to go into some of the details here a bit for others to find as well. I went into similar detail about PagedResourceAssembler in this answer.
Representation models
Spring HATEOAS ships with a variety of base classes for representation models that make it easy to create representations equipped with links. There are three types of classes provided out of the box:
Resource - an item resource. Effectively to wrap around some DTO or entity that captures a single item and enriches it with links.
Resources - a collection resource, that can be a collection of somethings but usually are a collection of Resource instances.
PagedResources - an extension of Resources that captures additional pagination information like the number of total pages etc.
All of these classes derive from ResourceSupport, which is a basic container for Link instances.
Resource assemblers
A ResourceAssembler is now the mitigating component to convert your domain objects or DTOs into such resource instances. The important part here is, that it turns one source object into one target object.
So the PagedResourcesAssembler will take a Spring Data Page instance and transform it into a PagedResources instance by evaluating the Page and creating the necessary PageMetadata as well as the prev and next links to navigate the pages. By default - and this is probably the interesting part here - it will use a plain SimplePagedResourceAssembler (an inner class of PRA) to transform the individual elements of the page into nested Resource instances.
To allow to customize this, PRA has additional toResource(…) methods that take a delegate ResourceAssembler to process the individual items. So you end up with something like this:
class UserResource extends ResourceSupport { … }
class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> { … }
And the client code now looking something like this:
PagedResourcesAssembler<User> parAssembler = … // obtain via DI
UserResourceAssembler userResourceAssembler = … // obtain via DI
Page<User> users = userRepository.findAll(new PageRequest(0, 10));
// Tell PAR to use the user assembler for individual items.
PagedResources<UserResource> pagedUserResource = parAssembler.toResource(
users, userResourceAssembler);
Outlook
As of the upcoming Spring Data Commons 1.7 RC1 (and Spring HATEOAS 0.9 transitively) the prev and next links will be generated as RFC6540 compliant URI templates to expose the pagination request parameters configured in the HandlerMethodArgumentResolvers for Pageable and Sort.
The configuration you've shown above can be simplified by annotating the config class with #EnableSpringDataWebSupport which would let you get rid off all the explicit bean declarations.
I wanted to convert list of Resources to page. but when giving it PagedResourcesAssembler it was eating up the internal links.
This will get your List paged.
public class JobExecutionInfoResource extends ResourceSupport {
private final JobExecutionInfo jobExecution;
public JobExecutionInfoResource(final JobExecutionInfo jobExecution) {
this.jobExecution = jobExecution;
add(ControllerLinkBuilder.linkTo(methodOn(JobsMonitorController.class).get(jobExecution.getId())).withSelfRel()); // add your own links.
}
public JobExecutionInfo getJobExecution() {
return jobExecution;
}
}
Paged resource Providing ResourceAssembler telling Paged resource to use it, which does nothing simply return's it back as it is already a resource list that is passed.
private final PagedResourcesAssembler<JobExecutionInfoResource> jobExecutionInfoResourcePagedResourcesAssembler;
public static final PageRequest DEFAULT_PAGE_REQUEST = new PageRequest(0, 20);
public static final ResourceAssembler<JobExecutionInfoResource, JobExecutionInfoResource> SIMPLE_ASSEMBLER = entity -> entity;
#GetMapping("/{clientCode}/{propertyCode}/summary")
public PagedResources<JobExecutionInfoResource> getJobsSummary(#PathVariable String clientCode, #PathVariable String propertyCode,
#RequestParam(required = false) String exitStatus,
#RequestParam(required = false) String jobName,
Pageable pageRequest) {
List<JobExecutionInfoResource> listOfResources = // your code to generate the list of resource;
int totalCount = 10// some code to get total count;
Link selfLink = linkTo(methodOn(JobsMonitorController.class).getJobsSummary(clientCode, propertyCode, exitStatus, jobName, DEFAULT_PAGE_REQUEST)).withSelfRel();
Page<JobExecutionInfoResource> page = new PageImpl<>(jobExecutions, pageRequest, totalCount);
return jobExecutionInfoResourcePagedResourcesAssembler.toResource(page, SIMPLE_ASSEMBLER, selfLink);
}
ALTERNATIVE WAY
Another way is use the Range HTTP header (read more in RFC 7233). You can define HTTP header this way:
Range: resources=20-41
That means, you want to get resource from 20 to 41 (including). This way allows consuments of API receive exactly defined resources.
It is just alternative way. Range is often used with another units (like bytes etc.)
RECOMMENDED WAY
If you wanna work with pagination and have really applicable API (hypermedia / HATEOAS included) then I recommend add Page and PageSize to your URL. Example:
http://host.loc/articles?Page=1&PageSize=20
Then, you can read this data in your BaseApiController and create some QueryFilter object in all your requests:
{
var requestHelper = new RequestHelper(Request);
int page = requestHelper.GetValueFromQueryString<int>("page");
int pageSize = requestHelper.GetValueFromQueryString<int>("pagesize");
var filter = new QueryFilter
{
Page = page != 0 ? page : DefaultPageNumber,
PageSize = pageSize != 0 ? pageSize : DefaultPageSize
};
return filter;
}
Your api should returns some special collection with information about number of items.
public class ApiCollection<T>
{
public ApiCollection()
{
Data = new List<T>();
}
public ApiCollection(int? totalItems, int? totalPages)
{
Data = new List<T>();
TotalItems = totalItems;
TotalPages = totalPages;
}
public IEnumerable<T> Data { get; set; }
public int? TotalItems { get; set; }
public int? TotalPages { get; set; }
}
Your model classes can inherit some class with pagination support:
public abstract class ApiEntity
{
public List<ApiLink> Links { get; set; }
}
public class ApiLink
{
public ApiLink(string rel, string href)
{
Rel = rel;
Href = href;
}
public string Href { get; set; }
public string Rel { get; set; }
}

Resources