Spring Boot Data REST, HATEOAS not working with a custom Controller - java

I use MongoRepository in Spring boot data rest and it is working just fine without implementing my own controller. But I want to put "Register Date" in my newly created objects and default implementation is not supporting that. I need to implement my own custom controller to put extra fields in every new objects. The problem is that HATEOAS stop working when I implement my own controller.
Repository class:
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends MongoRepository<User, String> {
}
Controller class:
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
UserRepository repository;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<User>> getAll() {
List<User> list = repository.findAll();
return new ResponseEntity<>(list, HttpStatus.OK);
}
Payload with this custom controller looks like this:
[
{
"id": "571de80ebdabf25dd6cdfb73",
"username": "mark",
"password": "mark123",
"email": "mark#gmail.com",
"createdAt": "2016-04-25 11:49"
},
{
...
Payload without my custom controller looks like this:
{
"_embedded": {
"users": [
{
"username": "mark",
"password": "mark123",
"email": "mark#gmail.com",
"createdAt": "2016-04-25 11:49",
"_links": {
"self": {
"href": "http://localhost:8080/users/571de80ebdabf25dd6cdfb73"
},
"user": {
"href": "http://localhost:8080/users/571de80ebdabf25dd6cdfb73"
}
}
},
{
.....
I tried to use #RepositoryRestController instead of #RestController but it didn't help. I wonder if there is another way to put "register date" in newly created objects without implementing own custom controller? If not, what can I do HATEOAS to work again?

I solved my problem thanks to comments which gave me a perspective :)
1 - Extended User class with ResourceSupport. (Note: Do not use just id for userId because ResourceSupport needs getId() method.)
public class User extends ResourceSupport {
#Id
private String userId;
2 - Updated my controller class as followed
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<User>> getAll() {
List<User> list = repository.findAll();
for(User user : list) {
Link self = linkTo(UserController.class).slash(user.getUserId()).withSelfRel();
Link users = linkTo(UserController.class).slash(user.getId()).withRel("users");
user.add(self);
user.add(users);
}
return new ResponseEntity<>(list, HttpStatus.OK);
}
Now, my payloads look like this:
[
{
"userId": "571e44ecbdab7b1ffc668f02",
"username": "newton",
"password": "gravity",
"email": "nevton#gmail.com",
"createdAt": "2016-04-25 18:
"links": [
{
"rel": "self",
"href": "http://localhost:8080/users/571e44ecbdab7b1ffc668f02"
},
{
"rel": "users",
"href": "http://localhost:8080/users"
}
]
},
{
....

Related

Swagger/OpenAPI 3.0 generation - adding a new content-type hides previous schema information

I am using Java annotations to build our swagger documentation. In one case, we want to provide output in either JSON or csv depending on the "Accepts" header. I created the rest interface like this:
#RestController
#EnableAutoConfiguration
#RequestMapping(path="/api/v2/swaggerTest")
#Api
public class SwaggerDocResource {
private static class ItemDto {
String name;
Integer age;
}
#ApiOperation(value = "Get the requested items in json")
#GetMapping(produces = "application/json")
public ResponseEntity<ItemDto> getItems() {
return null;
}
#ApiOperation(value = "Get the requested items in csv")
#GetMapping(produces = "text/csv")
public ResponseEntity<String> exportItems() {
return null;
}
}
If I comment out the csv method, the generated Swagger doc generates a schema for my DTO class and references it:
...
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemDto"
}
}
}
},
...
However, if I do include the csv method, the schema for my DTO class is no longer generated and both types of response are given the same schema:
...
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "string"
}
},
"text/csv": {
"schema": {
"type": "string"
}
}
}
},
...
Is it possible to assign a different schema to these different content types? I have been unable to figure out how.

How to modify and bind response to DTO

I am very new to spring boot development.
Currently, I am using #FeignClient to call external API and I want to bind it to my DTO.
My DTO looks like
public class TestDTO {
private UUID uuid;
private String name;
}
My #FeignClient,
#FeignClient(name = "testClient", url = "http://extenalApi/getRules")
public interface DataClient {
#RequestMapping(method = RequestMethod.GET)
List<TestDTO> getRules();
}
It throws an error because external API response is a bit different
{
"data": [
{
"customRule": {
"name": "SAMPLE 1",
"id": "0AB58A47D3A64B56A6B74DA0E66935DD"
}
},
{
"customRule": {
"name": "SAMPLE 2",
"id": "0AE6C846EAF648D1926E665E63633A94"
}
}
}
how can I parse this JSON and make it to
[
{
"name": "SAMPLE 2",
"id": "0AE6C846EAF648D1926E665E63633A94"
},
{ ...
}
]
as my DTO demands.
Seems like you have different structure of API reponse.
Create a new container to hold API response of rules api:
public class RulesDTO {
private List<DataDto> data;
//getter setter
class DataDto{
TestDTO customRule;
//getter setter
}
}
change method to getRules:
#RequestMapping(method = RequestMethod.GET)
RulesDTO getRules();
Now parse it to List of TestDTO for your response:
List<TestDTO> yourData = data.stream().map(DataDto::getCustomRule).collect(Collectors.toList());
Note- It is not working code. Just to give you an idea.

How construct list of objects from json string coming from api response?

Similar question might be asked before on here, but I had no luck and I was wondering how to extract specific objects like user in from below json string and then construct an ArrayList. However, there is one twist, one of the property directly under Users is a random number, which can be anything!!!
Here is how my json string looks like:
<code>{
"_links": {
},
"count": {
},
"users": {
"123321": { //*Is a random number which can be any number
"_links": {
},
"user": {
"id": "123321",
"name": "...",
"age": "...",
"address": ""
..
}
},
"456654": {
"_links": {
},
"user": {
"id": "456654",
"name": "...",
"age": "...",
"address": ""
...
}
}
...
},
"page": {
}
}
</code>
The java object I would like to transform it to is:
#JsonIgnoreProperties(ignoreUnknown = true) // Ignore any properties not bound here
public class User {
private String id;
private String name;
//setter:getter
}
Note: The transformation should only consider those two fields (id,name), and ignore the rest of the fields from the json response user:{} object.
Ideally, I would like to end up with a list like this:
List<User> users = resulted json transformation should return a list of users!!
Any idea how can I do this please ideally with Jackson JSON Parser/ or maybe GSON?
Since the user keys are random, you obviously can't map them to a named Java field. Instead, you can parse the top-level object as a map and the manually pull out the user objects.
public class UserWrapper {
private User user;
public User getUser() { return user; }
}
public class Root {
private Map<String, UserWrapper> users;
public List<User> getUsers() {
List<User> usersList = new ArrayList();
for (String key : map.keySet()) {
UserWrapper wrapper = map.get(key);
usersList.add(wrapper.getUser());
}
return userList;
}
}
Root root = parseJson();
List<User> users = root.getUsers()
Hope that helps!
jolt transformer is your friend. Use shift with wildcard * to capture arbitrary node value and then standard mappers (Jackson /gson) .

Spring Data REST - Exclude Subtypes

Lets say I have the following Hibernate Entities (fields ommitted)
#Entity
#DiscriminatorColumn(name = "T")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class SuperClass {
}
#Entity
#DiscriminatorValue(name = "subClassA")
public SubClassA extends SuperClass {
}
#Entity
#DiscriminatorValue(name = "subClassB")
public SubClassB extends SuperClass {
}
With Spring Data REST I would get the following JSON representation:
{
"_links": {
},
"_embedded": {
"subclassA": [
{
"field1": "",
"field2": ""
}
],
"subclassB": [
{
"field1": "",
"field2": "",
"field3": ""
}
]
}
}
Again ommiting the _links attributes. Is there some sort of configuration I can use so the Serializer can ignore the subclasses and do a representation like this:
{
"_links": {
},
"_embedded": {
"superClass": [
{
"field1": "",
"field2": ""
},
{
"field1": "",
"field2": "",
"field3": ""
}
]
}
}
One way to solve the problem would be the implementation of a RelProvider. All you need to do is to implement it and add it to spring container (this could be done but i.e. annotating the implementation with #Component).
Considering that you would be able to get the response you are expecting simply by adding the following implementation (considering it will be scanned by spring):
#Component
public class MessageRelProvider implements RelProvider {
public boolean supports(Class<?> arg0) {
return SuperClass.class.isAssignableFrom(arg0);
}
public String getItemResourceRelFor(Class<?> type) {
return "superClass";
}
public String getCollectionResourceRelFor(Class<?> type) {
return "superClasses";
}
}

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