How to extend CollectionModel/PagedModel in Spring Hateoas? - spring-boot

A hypermedia response that needs to be consumed by one of my services looks like this:
{
"_embedded": {
"content": [
{
"createdBy": "...",
"createdDate": "2020-03-07T14:21:27.507Z",
"id": "...",
"name": "item1",
"_links": {
"self": {
"href": ".."
}
}
}
]
},
"_links": {
"self": {
"href": "..."
},
},
"pageNumber": 1,
"totalItems": 20,
"pageSize": 10
}
See how the paging related info is not what's expected by Spring Hateoas PagedModel which should have a single "page" property instead of individual ones for pageNumber, totalItems and pageSize:
"page": {
"size": 2,
"totalElements": 1000,
"totalPages": 500,
"number": 5
}
What I did in the end was to extend CollectionModel by adding those individual properties. This does work correctly deserialising a response shown above. But, all the CollectionModel constructors are now deprecated and the alternative is to use "CollectionModel.of", which however returns just CollectionModel.
What's the right way of customising paging information in using Spring Hateoas?
Many thanks!

Related

Spring Data: Page information missing fron response on using CollectionModel with RepositoryRestController

My repository method is:
public Page<Order> findByStatusIn(List<OrderStatus> orderStatuses, Pageable pageable);
Which is called from Controller (#RepositoryRestController) as :
Pageable paging = PageRequest.of(page, pageSize, Sort.by(Sort.Direction.DESC,"id"));
Page<Order> myOrders = orderRepository.findByStatusIn(orderStatuses, paging);
The Page is converted to CollectionModel:
CollectionModel<Order> resources = CollectionModel.of(myOrders);
resources.add(linkTo(methodOn(OrderController.class).userOrders(page,pageSize,currentUser)).withSelfRel());
return new ResponseEntity<>(resources,HttpStatus.OK);
The output json is like:
{
"_embedded": {
"orders": [
{
"orderId": 10011,
"createdAt": "2022-05-18T16:28:19+05:30",
"lastUpdatedAt": "2022-06-10T16:28:15+05:30",
"createdBy": "User01",
"status": "PENDING"
},
{
"orderId": 10012,
"createdAt": "2017-05-03T14:28:19+05:30",
"lastUpdatedAt": "2022-06-10T16:28:15+05:30",
"createdBy": "User01",
"status": "SHIPPED"
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/orders/userOrders?page=0&pageSize=11"
}
}
}
The data I got is correct but the response is missing the Page information as from the response getting from repository methods in Spring data.
ie, the section like
"page": {
"size": 20,
"totalElements": 20,
"totalPages": 1,
"number": 0
}
is missing.
How the page information can be added to the response when #RepositoryRestController is used?
PagedModel should be used to create representations of pageable collections, instead of CollectionModel. To easily convert Page instances into PagedModel use PagedResourcesAssembler as described here.

Visit HATEOAS links of optional entities [duplicate]

This question already has an answer here:
Spring Data ReST ref link omission when null or empty
(1 answer)
Closed 5 years ago.
I developed a Spring Boot, Spring Data REST, Hibernate application that exposes REST endpoints.
A typical response is something like this:
{
"sid": "f6dddaaa-2713-4b92-844b-6f0d3cefad3f",
"createdBy": "admin",
"createdDate": "2018-01-30T15:56:38.417Z",
"lastModifiedDate": "2018-01-30T15:57:53.963Z",
"lastModifiedBy": "admin",
"status": "Annullato",
"number": "51",
"dailyCode": "VS",
"entryDate": "2018-01-30T15:56:00Z",
"exitDate": "2018-01-31T15:56:00Z",
"totalDays": 1,
"standard": true,
"minibus": false,
"schoolTrip": false,
"price": 400,
"fareRow": "Standard",
"fareColumn": "Euro 0 3",
"extSyncCode": null,
"payments": [],
"passengers": 44,
"agency": null,
"paperBlock": null,
"paperReceipt": null,
"payer": null,
"checkedMedia": false,
"checkedLicensePlate": false,
"_links": {
"self": {
"href": "http://localhost:8080/api/v1/tickets/1"
},
"ticket": {
"href": "http://localhost:8080/api/v1/tickets/1{?projection}",
"templated": true
},
"area": {
"href": "http://localhost:8080/api/v1/tickets/1/area"
},
"fareException": {
"href": "http://localhost:8080/api/v1/tickets/1/fareException"
},
"block": {
"href": "http://localhost:8080/api/v1/tickets/1/block"
},
"customer": {
"href": "http://localhost:8080/api/v1/tickets/1/customer"
},
"transitCertificate": {
"href": "http://localhost:8080/api/v1/tickets/1/transitCertificate"
},
"passengersCountry": {
"href": "http://localhost:8080/api/v1/tickets/1/passengersCountry"
},
"refund": {
"href": "http://localhost:8080/api/v1/tickets/1/refund"
},
"fine": {
"href": "http://localhost:8080/api/v1/tickets/1/fine"
},
"hotel": {
"href": "http://localhost:8080/api/v1/tickets/1/hotel"
},
"workShift": {
"href": "http://localhost:8080/api/v1/tickets/1/workShift"
}
}
}
As you can see this entity has a lot of links. These links represent bound entities. Unfortunately some of these entities are optional.
I created a Angular 5 application that consumes my server side API. When I want to display data (let's say the entity shown in the example) I need to get related entities and I've to visit all links. Because not all related entities are mandatories, some of these links return HTTP 404 and the browser display these calls as errors (see the picture).
Is my approach right? Should I visit all these links and consider the 404 response perfectly fine (I think so) even if the browser consided that as an error? Is there a better approach otherwise?
That’s a lot of work to discover a related entity doesn’t exist. HTTP requests are slow. Admittedly they will run in parallel but it still seems inefficient.
Instead, when you load the parent entity, can you check at that time which children entities will be valid? If the child will not be valid then, instead of a link, return null. Or don’t return the value at all.

Spring pagination - request parameters

My REST contorller:
#GetMapping("/test")
public Page<MyObject> pathParamTest(Pageable pageable) {
return myService.getPage(pageable);
}
I send a request like following:
localhost:8091/endpoint/test?page=0&size=3&sort=id&direction=DESC
It's my response from server:
{
"content": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
],
"last": true,
"totalPages": 1,
"totalElements": 3,
"first": true,
"sort": [
{
"direction": "ASC",
"property": "id",
"ignoreCase": false,
"nullHandling": "NATIVE",
"descending": false,
"ascending": true
}
],
"numberOfElements": 3,
"size": 3,
"number": 0
}
but the request has still direction = ASC.
How can I send to server direction = DESC?
And why response has a field "last" = true, because next page has one element more?
try
localhost:8091/endpoint/test?page=0&size=3&sort=id,DESC
from spring data rest 6.2. Sorting
curl -v
"http://localhost:8080/people/search/nameStartsWith?name=K&sort=name,desc"
sort Properties that should be sorted by in the format
property,property(,ASC|DESC). Default sort direction is ascending. Use
multiple sort parameters if you want to switch directions, e.g.
?sort=firstname&sort=lastname,asc.

How can I output the return value of a Method like totalAmount in an Spring Rest Entity

is it possible to output a return Value totalAmount of an Entity ShoppingCart that is not a Value in the Class but a Method? So for example I have a Class Shoppingcart with a List of Items. and a Method totalAmount. Now when I make a request to the API with the URL http://localhost:8082/carts/1 I want to get a response like the following:
{
"creationDate": "2016-12-07T09:45:38.000+0000",
"items": [
{
"itemName": "Nintendo 2DS",
"description": "Konsole from Nintendo",
"price": 300.5,
"quantity": 3
},
{
"itemName": "Nintendo Classic",
"description": "Classic nintendo Console from the 80th...",
"price": 75,
"quantity": 2
}
],
"totalAmount": "1051,50",
"_links": {
"self": {
"href": "http://localhost:8082/carts/2"
},
"cart": {
"href": "http://localhost:8082/carts/2"
},
"checkout": {
"href": "http://localhost:8083/order"
}
}
}
Currently the response of an API request looks like the following:
{
"creationDate": "2016-12-07T09:45:38.000+0000",
"items": [
{
"itemName": "Nintendo 2DS",
"description": "Konsole from Nintendo",
"price": 300.5,
"quantity": 3
},
{
"itemName": "Nintendo Classic",
"description": "Classic nintendo Console from the 80th...",
"price": 75,
"quantity": 2
}
],
"_links": {
"self": {
"href": "http://localhost:8082/carts/2"
},
"cart": {
"href": "http://localhost:8082/carts/2"
},
"checkout": {
"href": "http://localhost:8083/order"
}
}
}
Is there an Annotation that do this job or something other. I tried to add it in the CartResourceProcessor (org.springframework.hateoas.ResourceProcessor) but there is only the possibility to add additional links. Or do I need to add a Class value totalAmount?
Yes you can achieve that by annotating your method with Jackson #JsonProperty annotation
Code sample
#JsonProperty("totalAmount")
public double computeTotalAmount()
{
// compute totalAmout and return it
}
And to answer the possible next question you get after reading this. How the totalAmount is calculated. Here the snippet
public Class Cart{
// some Class values
#JsonProperty("totalAmount")
public BigDecimal total(){
return items.stream()
.map(Item::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
public class Item{
// some Item values
#JsonProperty("totalAmount")
public BigDecimal total(){
return price.multiply(new BigDecimal(this.quantity));
}
}
Outputs something similar to this:
{
"creationDate": "2016-12-07T09:45:38.000+0000",
"items": [
{
"itemName": "Nintendo 2DS",
"description": "Konsole from Nintendo",
"price": 300.5,
"quantity": 3,
"totalAmount": 901.5
},
{
"itemName": "Nintendo Classic",
"description": "Classic nintendo Console from the 80th...",
"price": 75,
"quantity": 2,
"totalAmount": 150
}
],
"totalAmount": 1051.5,
"_links": {
"self": {
"href": "http://localhost:8082/carts/2"
},
"cart": {
"href": "http://localhost:8082/carts/2"
},
"checkout": {
"href": "http://localhost:8083/order"
}
}
}
Hope it helps you :)

400 Bad Request in Spring Boot Swagger UI for POST

I've created a Spring Boot application with RestController. I enabled Swagger UI and it works fine as I can login to the UI and execute any GET methods. But for POST methods accepting objects in the body, when I fire off the request using Swagger UI, it always returns 400 status code. I can see the request never reached the particular POST method. May I know if any special config I need for Swagger UI?
The particular method in my Spring Boot RestController
#ApiOperation(value = "Query requests by search criteria")
#RequestMapping(value = "/api/query", method = POST)
public PageResult<MyPOJO> find(#RequestBody SearchRequest request) {
//implementation omitted.
}
and I'm using
"io.springfox:springfox-swagger2:2.1.2",
"io.springfox:springfox-swagger-ui:2.1.2"
This one (generated by Swagger UI) for the corresponding api gives me 400 bad request:
{
"listEntrySearchCriteria": {
"summary": {
"createdBy": "string",
"createdOn": "2016-03-21T10:33:05.048Z",
"effectiveEnd": "2016-03-21T10:33:05.048Z",
"effectiveStart": "2016-03-21T10:33:05.048Z",
"id": 0,
"region": "string",
"type": "ETB",
"updatedBy": "string",
"updatedOn": "2016-03-21T10:33:05.048Z",
"version": 0
}
},
"listSummarySearchCriteria": {
"effectiveEnd": "2016-03-21T10:33:05.048Z",
"effectiveStart": "2016-03-21T10:33:05.048Z",
"statuses": [
"string"
],
"types": [
"string"
]
},
"pageRequest": {
"orders": [
{
"direction": "ASC",
"ignoreCase": true,
"nullHandling": "NATIVE",
"property": "string"
}
],
"page": 0,
"size": 0
}
}
But if I supply a random request for the same method, it at least reaches my method:
{
"orders": [
{
"direction": "ASC",
"ignoreCase": true,
"nullHandling": "NATIVE",
"property": "id"
}
],
"page": 0,
"size": 0
}

Resources