Spring Hateoas issues rendering HAL content - java

I've been checking similar issues and haven't found any answer addressing what I am observing.
The issue is that I've easily managed to get Hypermedia in HAL format in my REST API when I retrieve 1 resource, but when I hit the controller methods retrieving a list of entities, then the hypermedia is NOT the same.
Here are the ouputs:
single resource returned
"_links": {
"self": {
"href": "http://localhost:8080/celsvs/api/books/123567891099"
},
"books": {
"href": "http://localhost:8080/celsvs/api/books"
}
}
List of resources
"links": [
{
"rel": "self",
"href": "http://localhost:8080/celsvs/api/books/123567891099"
},
{
"rel": "books",
"href": "http://localhost:8080/celsvs/api/books"
}
]
I started with Spring hateoas 0.25, but as I had to uplift anyway Spring boot and I saw that the Hateoas API had changed, I am now on Spring hateoas 1.0... And even after adapting my code to the new API I am still getting the same result.
I am using the RepresentationModelAssemblerSupport class to keep my controllers clean from code to generate hateoas content. So this is how it looks like:
#Component
public class BookModelAssembler extends RepresentationModelAssemblerSupport<BookDto, BookDto> {
public BookModelAssembler() {
super(BooksController.class, BookDto.class);
}
#Override
public BookDto toModel(BookDto entity) {
return entity.add(linkTo(methodOn(BooksController.class).getResource(entity.getIsbn())).withSelfRel())
.add(linkTo(methodOn(BooksController.class).getAllResources()).withRel("books"));
}
}
And in the Controller, the endpoints to retrieve one or all resources:
#Override
#GetMapping(value = {QueryConstants.BOOKS_ISBN_PATH, QueryConstants.BOOKS_SIG_PATH})
public BookDto getResource(#PathVariable("isbn") final String isbn) {
return this.modelAssembler.toModel(this.findOneResource(isbn));
}
// GET - retrieve all resources
#Override
#GetMapping (produces = { "application/hal+json" })
public List<BookDto> getAllResources() {
return (findAllResources().stream().map(this.modelAssembler::toModel).collect(Collectors.toList()));
}
As you can see, the Hypermedia rendered is different even when all the entities in the list returned have been mapped using the same method toModel() used in the method getResource().
The only way I've managed to see in the case of all resources the proper HAL format returned is when I've changed the implementation of the controller to return a collection Model:
//#GetMapping
public CollectionModel<BookDto> getAll() {
return this.modelAssembler.toCollectionModel(findAllResources());
}
but then all the entities are bundled inside an _embedded element, which is NOT what I want when I return the collection of entities.
Spring Hateoas documentation states that HAL is the default, so I have not thought about configuring anything for now...
So, I only see:
I am missing some configuration so that when I can get a collection of entities rendered (no under the _embedded element)... But I haven't seen anything suitable in HalConfiguration bean.
I am assuming that the proper way of returning the collection of the type of resources requested is NOT inside the _embedded property... but maybe I am wrong. So far my understanding was that when you request, say, resource Person and you want to return it's contacts, those being also directly reachable via their own endpoint, then you embed those in the content returned along with the rest of Person properties... I haven't found anything stating that collections are expected to be rendered inside the _embedded property.
Does anyone have any advice? I am running out of time and ideas and the team implementing the client side is waiting to consume the Hypermedia content.
Thanks!

The HAL specification says that the property _embedded is used to store an array of resource object.
Edit:
To answer Alberto's question in his own answer
Still, if someone can tell me why in the previous implementation the attached links did not follow the HAL format, I would appreciate. Thanks
Spring HATEOAS customizes JSON serialization of RepresentationModel which is the parent class of CollectionModel.
// org.springframework.hateoas.mediatype.hal.RepresentationModelMixin
public abstract class RepresentationModelMixin extends RepresentationModel<RepresentationModelMixin> {
#Override
#JsonProperty("_links")
#JsonInclude(Include.NON_EMPTY)
#JsonSerialize(using = Jackson2HalModule.HalLinkListSerializer.class)
#JsonDeserialize(using = Jackson2HalModule.HalLinkListDeserializer.class)
public abstract Links getLinks();
}
#JsonProperty("_links") defines the JSON property name to be _links. #JsonSerialize defines the serializer to be used. Look into method org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize for serialization logic.

After some more reading I've made my mind and I have concluded that the proper thing to send back in the response is a collection inside the _embedded element.
I've followed several references:
Section 6 of JSON HAL rfc draft (this is the latest version I've found), there is an example of a document returned as a result of a request of a list of resources, where the list of them is embedded in an array inside the _embedded element.
In a section dedicated to Collections by other writers, the interpretation is the same and the elements of a collection are returned inside an _embedded property.
So in line with this, if I change my controller to return CollectionModel, then I get a proper content formatted in HAL.
The code being:
#GetMapping
public CollectionModel<BookDto> getAll() {
return this.modelAssembler.toCollectionModel(findAllResources()).add(linkTo(methodOn(BooksController.class).getAll()).withSelfRel());
}
And the result being:
{
"_embedded": {
"bookDtoList": [
{
"isbn": "123567891099",
"signature": "AA-23-EEE",
"title": "Electromagnetismo",
"subtitle": "Introducción a las aplicaciones del electromagnetismo",
"authors": [
"Idoia Mendieta",
"Bonifacio Pérez"
],
"available": false,
"numOfCopies": 0,
"library": null,
"detailedInfo": null,
"_links": {
"self": {
"href": "http://localhost:8080/celsvs/api/books/123567891099"
},
"books": {
"href": "http://localhost:8080/celsvs/api/books"
}
}
},
{
"isbn": "123567891012",
"signature": "AA-23-EFD",
"title": "Electromagnetismo",
"subtitle": "Introducción a las aplicaciones del electromagnetismo",
"authors": [
"Idoia Mendieta",
"Bonifacio Pérez"
],
"available": false,
"numOfCopies": 0,
"library": null,
"detailedInfo": null,
"_links": {
"self": {
"href": "http://localhost:8080/celsvs/api/books/123567891012"
},
"books": {
"href": "http://localhost:8080/celsvs/api/books"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/celsvs/api/books"
}
}
}
Still, if someone can tell me why in the previous implementation the attached links did not follow the HAL format, I would appreciate. Thanks

Related

How to create a simple json template to send body data with restassured?

So i am trying to build a json to send data to the body of my restassured request, like this structure here:
{
"id": 1,
"category": {
"id": 1,
"name": "duch"
},
"name": "benny",
"photoUrls": [
"string"
],
"tags": [
{
"id": 0,
"name": "string"
}
],
"status": "available"
}
So it is as simple as to copy this as string to the body of the request and i am done, i don't want that at all.
Is there a framework of sorts to give this structure and to change the data dynamically somehow?
I don't want this: (for example)
given().body("{\r\n\"city\": \"Hod Hasharon\",\r\n\"description\": \"Automation Hotel\",\r\n\"name\":\"Nir Great hotel\",\r\n\"rating\":5\r\n}")
.when().post("http://localhost:8090/example/v1/hotels").then().statusCode(201);
I want to be more flexible here, to reference some kind of object (A template with the option to change the data in some places?) that handles this stuff, is there something like that?
I think what you need is using POJO and Jackson to serialize it to json.
public class Payload {
private int id;
private String name;
private List<Tag> tags; //Tag is another class you need to create the same way
//getters, setters
}
And then using objects as payload in your request:
Payload payload = new Payload();
payload.setId(123);
payload.setName("John");
given().contentType("application/json").body(payload).when().post("http://example.com");
Also don't forget to add jackson-databind dependency to your project.
There's more about that in official documentation here: https://github.com/rest-assured/rest-assured/wiki/Usage#object-mapping

Change PagedResources<T>'s name in Json representation

I am using spring boot to develop an small rest api that returns informations about an object named Chapter in HAL representation and using pagination.
A normal HAL representation would require the following controller
#RestController
#RequestMapping("/chapters")
public class ChapterController {
#Autowired ChapterRepository chapterRepo;
#RequestMapping(value="/slice", produces="application/hal+json")
public PagedResources<Chapter> slice(Pageable p, PagedResourcesAssembler assembler){
Page<Chapter> page = chapterRepo.findAll(p);
return assembler.toResource(page);
}
}
wich will return the following
{
"_embedded":{
"chapterList":[
//list of chapters here
]
},
"_links":{
"self":{
"href":"http://localhost:8090/chapters/slice?page=0&size=20"
}
},
"page":{
"size":20,
"totalElements":4,
"totalPages":1,
"number":0
}
}
But there is one thing i want to change which is the "chapterList" nomination.
Is there a way to do it

How to handle List<String> in Realm?

I have tried add the JSON response into the Realm database. I handled the response through GSON and then tried to convert to realm. I have already extended RealmObject for my response model class. I am also using RealmString class for handling List by using RealmList. But when I tried to GSON to Realm object I get errors. I am looking for an example of this kind if anyone has one. All support are appreciated. Below is my JSON response.
{
"transactionType": 12,
"location": {
"type": "Point",
"coordinates": [
77.7,
12.9
]
},
"rooms": {
"bedrooms": {
"total": 2,
"metadata": [
{
"name": "bedroom 2",
"images": [
"Eshant",
"Abhijeet"
]
}
]
}
}
}
I answered a very similar question here https://stackoverflow.com/a/39993141/1666063
Here is short walkthrough how to to JSON -> GSON -> Realm:
Use http://www.jsonschema2pojo.org/ to generate a POJO with getters and setters for GSON
for the classes and subclasses you want to store in Realm add extends RealmObject to them
for all your classes that extends RealmObject make sure to put #PrimaryKey on of the fields (like an ID)
replace any usage of List<Foo> with RealmList<Foo>
Foo MUST extends RealmObject as well (even if it is a String)
Add a TypeAdapter to GSON that can handle RealmList(here is one I wrote that takes a generic T)

Swagger Spring mvc reponsebody

Is there a way to present the request body of a complex object in swagger with each field having it's input?
In simple words if one of my apis expects a Person (suppose it has just a firstname/lastname) as #RequestBody then the only way to provide this Person with swagger would be to give the entire json of Person. Is there a way to enable each separate field to have it's separate input for firstname/lastname for example?
If you annotate your Operation using #ModelAttributeit should do exactly what you're looking for
For e.g. instead of
public void updatePersonName(#RequestBody Person person) { ... }
Use this
public void updatePersonName(#ModelAttribute Person person) { ... }
The #ModelAttribute will expand the primitive properties and provide fields for entering in the first name and last name in the swagger ui. The equivalent of that operation is
public void updatePersonName(#RequestParam String firstName,
#RequestParam String lastName) { ... }
I'm also using swagger with the default swagger UI and I don't think it's possible unless you change the swagger UI code. But a quite convenient solution is to define the type of your parameter and then provide the model for that type. For instance
"parameters": [
{
"in": "body",
"name": "body",
"description": "A Person",
"required": true,
"type": "Person"
}
]
And then
"definitions": {
"Person": {
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
}
}
}
This way the model appears in the UI and you can easily copy it to the request body textbox and fill it with content manually.

Spring HATEOAS, embed linked object in WS response

I am using Spring Boot and Spring HATEOAS to build a REST API.
I have 2 simple objects. Let's say:
// Model
#Entity
public class Person {
private String name;
private Address address;
// ... usual methods omitted for brievity
}
#Entity
public class Address {
private String street;
private String city;
// ...
}
// Repository. It exposes directly a REST service
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {}
// Application entry point
#ComponentScan
#EnableAutoConfiguration
#EnableJpaRepositories
#Import(RepositoryRestMvcConfiguration.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This simple project creates output like the following:
{
"_links": {
"self": {
"href": "http://localhost:8080/persons{?page,size,sort}",
"templated": true
}
},
"_embedded": {
"persons": [
{
"name": "Some name",
"_links": {
"self": {
"href": "http://localhost:8080/persons/1"
},
"address": {
"href": "http://localhost:8080/persons/1/address"
}
}
}
]
}
}
Fine, but I would like the application to send the Address object directly in the response. In order not to have to query the URL of the address.
Something like:
...
"persons": [
{
"name": "Some name",
"address": {
"street": "Some street name"
"city": "Some city name"
}
"_links": {
"self": {
"href": "http://localhost:8080/persons/1"
},
"address": {
"href": "http://localhost:8080/persons/1/address"
}
}
}
]
...
Are there any configuration to do that? I could not find any configuration about it in Spring HATEOAS docs. And this is the default behavior when using only regular Spring controllers.
The latest release of Spring Data REST's docs illustrate excerpt projections. This provides an alternative default view of a collection of data.
Your use case is the exact angle it was originally designed for.
Delete AddressRepository interface, and object Address will be embedded in json in Person class. But it will not be possible to get Address by ID

Categories

Resources