Basically i have the same issue like the member who posted this question
When i request a single user in my Application, i get the response in the HAL-format, like i wish
http://localhost:8080/api/v1/users/25 with GET:
{
"userId": "25",
"firstname": "Beytullah",
"lastname": "Güneyli",
"username": "gueneylb",
"_links": {
"self": {
"href": "http://localhost:8080/api/v1/users/25"
},
"roles": [
{
"href": "http://localhost:8080/api/v1/roles/33"
},
{
"href": "http://localhost:8080/api/v1/roles/34"
}
]
}
}
But, when i request all users , i get the response in non-HAL-Format, like this:
http://localhost:8080/api/v1/users with GET:
[...
{
"userId": "25",
"firstname": "Beytullah",
"lastname": "Güneyli",
"username": "gueneylb",
"links": [
{
"rel": "self",
"href": "http://localhost:8080/api/v1/users/25"
},
{
"rel": "roles",
"href": "http://localhost:8080/api/v1/roles/33"
},
{
"rel": "roles",
"href": "http://localhost:8080/api/v1/roles/34"
}
]
},
...]
Here are my methods:
#RequestMapping(value = "users", method = RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
public List<UserResource> list() throws MyException, NotFoundException {
List<User> userList= userRepository.findAll();
List<UserResource> resources = new ArrayList<UserResource>();
for (User user : userList) {
resources.add(getUserResource(user));
}
if(userList == null)
throw new MyException("List is empty");
else
return resources;
}
#RequestMapping(value = "users/{id}", method = RequestMethod.GET)
public UserResource get(#PathVariable Long id) throws NotFoundException {
User findOne = userRepository.findOne(id);
if (findOne == null){
log.error("Unexpected error, User with ID " + id + " not found");
throw new NotFoundException("User with ID " + id + " not found");
}
return getUserResource(findOne);
}
private UserResource getUserResource(User user) throws NotFoundException {
resource.add(linkTo(UserController.class).slash("users").slash(user.getId()).withSelfRel());
for(Role role : user.getRoles()){
resource.add(linkTo(RoleController.class).slash("roles").slash(role.getId()).withRel("roles"));
}
return resource;
}
You can see that both methods invoke thegetUserResource(User user) method.
But when i get all users in my database, the format of the _linksis not like i want. I think it must be something about the List of resources i return. Maybe because of that it has no HAL-Format. I also tried a Set instead of List but it gave me the same response
You should return Resources<UserResource> in your list() method.
return new Resources(resources);
You could also add a self-link to the resources itself to point to the list resource.
Furthermore, I would suggest using a RessourceAssembler to create the resource instance - see http://docs.spring.io/spring-hateoas/docs/0.23.0.RELEASE/reference/html/#fundamentals.resource-assembler.
Additionally, you could add paging to your list resource.
For this you need to:
use the findAll method in your repository that returns a Page<User>.
autowire PagedResourcesAssembler<User> in your controller
return PagedResources<UserResource> in your list method
use the PagedResourcesAssembler to convert the Page into PagedResources
This would result in something like this:
private final PagedResourcesAssembler<User> pagedResourcesAssembler;
#RequestMapping(value = "users", method = RequestMethod.GET)
public ResponseEntity<PagedResources<UserResource>> list(Pageable pageable) {
final Page<User> page = repository.findAll(pageable);
final Link link = ControllerLinkBuilder.linkTo(ControllerLinkBuilder.methodOn(this.getClass()).list(null)).withSelfRel();
PagedResources<UserResource> resources = page.getContent().isEmpty() ?
(PagedResources<UserResource>) pagedResourcesAssembler.toEmptyResource(page, ShippingZoneResource.class, link)
: pagedResourcesAssembler.toResource(page, resourceAssembler, link);
return ResponseEntity.ok(resources);
}
Related
I've created 2 HATEOS #RepositoryRestResource for a Customer that has many Address :
class Customer {
...
Long id;
#OneToMany(
Set<Address> addresses;
...
}
class Address {
...
Long id;
...
}
#RepositoryRestResource(path = "customers")
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long> {
}
#RepositoryRestResource(path = "addresses")
public interface AddressRepository extends PagingAndSortingRepository<Address, Long> {
}
I am able to get an Address entity with the following multi-segment path
Get Address 1 of Customer 1: GET /customers/1/adresses/1
BUT ... I'm not able to PATCH, PUT or DELETE an address using the same multi-segment path
Update Address 1 of Customer 1: PATCH, PUT or DELETE /customers/1/adresses/1
I always get a 404 - Not Found. It only works if I use the address endpoint
Update Address 1 : PATCH, PUT or DELETE /adresses/1
Is there a way to configure the #RepositoryRestResource so that I can update/delete an address (or any entities related to customer via the customer endpoint (/customers/{customerId}/addresses/{addressId}) ?
Regards
The code is available at https://github.com/fdlessard/spring-boot-repository-rest-resource
When you GET /customers/1/addresses/1, the response JSON should be
{
"country": "XYZ",
"_links": {
"self": {
"href": "http://localhost:8888/addresses/1" <-- here
},
"address": {
"href": "http://localhost:8888/addresses/1"
}
}
}
Use the link self, that is, http://localhost:8888/addresses/1 for update.
Actually, when you GET /customers/1/addresses, the array addresses in response JSON already gives you the true URI /addresses/1.
{
"_embedded": {
"addresses": [
{
"country": "XYZ",
"_links": {
"self": {
"href": "http://localhost:8888/addresses/1" <-- here
},
"address": {
"href": "http://localhost:8888/addresses/1"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8888/customers/1/addresses"
}
}
}
Remember, when play with HATEOAS, discover link and follow the href, rather than hardcode the URI. See https://docs.spring.io/spring-hateoas/docs/current/reference/html/#client.link-discoverer
Is there a way to provide a custom error message depending on the given condition?
I'm using https://github.com/networknt/json-schema-validator, version 1.0.43
This is my JSON Schema:
{
"$id": "https://configurations/provider.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Configuration",
"type": "object",
"properties": {
"provider": {
"description": "Name of the provider.",
"enum": [
"Provider #1",
"Provider #2"
]
},
"configuration": {
"$schema": "json-schema/configurations/configuration.json"
}
},
"if": {
"properties": {
"parcsProvider": {
"const": "Provider #1"
}
}
},
"then": {
"required": [
"configuration"
]
},
"else": {
"not": {
"required": [
"configuration"
]
}
}
}
If the value for the provider is "Provider #1" then the configuration object is required, and if it's "Provider #2" and configuration is passed an error will occur. I want to customize that error so that the response is the same as it is now but with a custom message like "Provider 2 can't have a configuration."
Current error message/response:
{
"timestamp": "2020-11-23T12:50:56.20658+01:00",
"message": "invalid.json.input",
"validationErrors": [
{
"field": "$",
"message": "$: should not be valid to the schema \"not\" : {\"required\":[\"configuration\"]}"
}
]
}
I had a similar requirement to implement in one of my projects. For validation, I was using https://github.com/everit-org/json-schema.
Here is what I did
Categorized all kind of errors[there must be some specific keyword] thrown by the validator
Now once you have all the keys, you can easily manipulate the errors and send the custom error/response.
Below are the keys I have collected for different cases, this might help you -
MIN_LENGTH_VIOLATION = "expected minLength"
MAX_LENGTH_VIOLATION = "expected maxLength"
PATTERN_VIOLATION = "does not match pattern"
DATA_TYPE_VIOLATION = "expected type"
DEPENDENCY_VIOLATION = "is required"
FORMAT_VIOLATION_OR_ENUM_VALIDATION_VIOLATION = "is not a valid"
MANDATORY_FIELD_VIOLATION_OR_CONDITIONAL_VIOLATION = "required key"
NUMBER_IS_LESS_THAN_VIOLATION = "is not greater or equal to"
NUMBER_IS_GREATER_THAN_VIOLATION = "is not less or equal"
EXCLUSIVE_NUMBER_IS_GREATER_THAN_VIOLATION = "is not less than"
EXCLUSIVE_NUMBER_IS_LESS_THAN_VIOLATION = "is not greater than"
MULTIPLE_OF_VIOLATION = "is not a multiple"
Sample Code -
private static void validate(JSONObject request) {
try {
// Schema, that might be fetched dynamically from some data source
JSONObject schema = new JSONObject();
Schema loadedSchema = SchemaLoader.load(schema);
loadedSchema.validate(request);
} catch (ValidationException ve) {
List<String> allErrorMessages = ve.getAllMessages();
List<String> mandatoryFields = parseMandatoryField(allErrorMessages);
if (CollectionUtils.isNotEmpty(mandatoryFields)) {
throw new MandFieldMissingExp(mandatoryFields);
} else {
List<String> invalidFields = parseInvalids(allErrorMessages);
throw new InvalidFieldExp(invalidFields);
}
}
}
private static List<String> parseMandatoryField(List<String> validationExceptionMessages) {
Set<String> mandatoryListSet = new HashSet<>();
validationExceptionMessages.forEach(errorMessage -> {
if (StringUtils.containsAny(errorMessage, MANDATORY_FIELD_VIOLATION_OR_CONDITIONAL_VIOLATION, DEPENDENCY_VIOLATION)) {
mandatoryListSet.add(StringUtils.substring(errorMessage, errorMessage.indexOf('[') + 1, errorMessage.indexOf(']')));
}
});
return new ArrayList<>(mandatoryListSet);
}
private static List<String> parseInvalids(List<String> validationExceptionMessages) {
Set<String> invalidParamsSet = new HashSet<>();
validationExceptionMessages.forEach(errorMessage -> {
if (StringUtils.containsAny(errorMessage, MIN_LENGTH_VIOLATION, MAX_LENGTH_VIOLATION, PATTERN_VIOLATION,
FORMAT_VIOLATION_OR_ENUM_VALIDATION_VIOLATION, DATA_TYPE_VIOLATION, NUMBER_IS_LESS_THAN_VIOLATION,
MULTIPLE_OF_VIOLATION, EXCLUSIVE_NUMBER_IS_GREATER_THAN_VIOLATION, NUMBER_IS_GREATER_THAN_VIOLATION
, EXCLUSIVE_NUMBER_IS_LESS_THAN_VIOLATION)) {
invalidParamsSet.add(StringUtils.substring(errorMessage, errorMessage.indexOf('/') + 1, errorMessage.indexOf(':')));
}
});
return new ArrayList<>(invalidParamsSet);
}
Hope it helps
I am looking to generate an api that take different content type.
The problem I am facing is that if I run several time my application I have different output documentation
#RestController
public class MyRestController {
#Operation(summary = "GetMyData", operationId = "gettt",
responses = #ApiResponse(responseCode = "204", content = #Content(mediaType = "application/vnd.something")))
#GetMapping(produces = "application/vnd.something")
public ResponseEntity<Void> getSomethingElse() {
return noContent().build();
}
#GetMapping(produces = TEXT_PLAIN_VALUE)
public String get() {
return "some text";
}
#GetMapping(produces = HAL_JSON_VALUE)
public EntityModel<JsonResponse> getHal() {
return EntityModel.of(new JsonResponse(),
linkTo(MyRestController.class).slash("somelink").withSelfRel()
);
}
#GetMapping(produces = APPLICATION_JSON_VALUE)
public JsonResponse getJson() {
return new JsonResponse();
}
}
It currently generate a wrong api-docs
"operationId": "gettt_1_1_1",
"responses": {
"200": {
"content": {
"application/hal+json": {
"schema": {
"$ref": "#/components/schemas/EntityModelJsonResponse"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/JsonResponse"
}
},
"text/plain": {
"schema": {
"type": "string"
}
}
},
"description": "OK"
},
"204": {
"content": {
"application/hal+json": {
"schema": {
"$ref": "#/components/schemas/EntityModelJsonResponse"
}
},
"application/vnd.something": {},
"text/plain": {
"schema": {
"type": "string"
}
}
},
"description": "No Content"
}
},
If I restart my server without changing the code the following response is generated
"operationId": "gettt_1",
"responses": {
"200": {
"content": {
"application/hal+json": {
"schema": {
"$ref": "#/components/schemas/EntityModelJsonResponse"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/JsonResponse"
}
},
"text/plain": {
"schema": {
"type": "string"
}
}
},
"description": "OK"
},
"204": {
"content": {
"application/vnd.something": {}
},
"description": "No Content"
}
},
I would expect that restarting my server will always generate the same documentation
Have you looked at the documentation?
https://springdoc.github.io/springdoc-openapi-demos/springdoc-properties.html#swagger-ui-properties
You can use the swagger-ui properties, without having to override the standard way of sorting (operationsSorter and tagsSorter).
For example:
springdoc.swagger-ui.operationsSorter=method
springdoc.swagger-ui.tagsSorter=alpha
If you want a an order on the server side, you can use OpenApiCustomiser, to sort the elements
This is a sample code that you can customize using Comparators, depending on the sorting logic you want:
Example, for alphabetical order sorting of schemas:
#Bean
public OpenApiCustomiser sortSchemasAlphabetically() {
return openApi -> {
Map<String, Schema> schemas = openApi.getComponents().getSchemas();
openApi.getComponents().setSchemas(new TreeMap<>(schemas));
};
}
Example for sorting tags, in alphabetical order:
#Bean
public OpenApiCustomiser sortTagsAlphabetically() {
return openApi -> openApi.setTags(openApi.getTags()
.stream()
.sorted(Comparator.comparing(tag -> StringUtils.stripAccents(tag.getName())))
.collect(Collectors.toList()));
}
You can have full control on the elements order, and you can sort them depending on your use case...
one other flag mentioned here:
springdoc:
writer-with-order-by-keys
How to parse a Json with a field "data" which can contain several types of objets, or an object and a simple message (outside the object structure).
An Android app request information to its backend. In most cases, the response has the same structure and if the "data" field only contains one kind of object i can complete the process.
But if the "data" field contains several kinds of objects or one object with a external message, i can´t parse it.
Normal Response structure (I can parse these cases):
{
"success": true,
"code": "OK",
"error": null,
"data": {
"_type": "auth",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImlzcyI6Im9mZnRpbWUiLCJzdWIiOiJkZXZlbG9wZXIifQ==.eyJyb2xlIjpbImFwcCJdLCgfgdfgJhcgdfgdfAiOiJPRkZUSU1FIEFuZHJvaWQiLCJvd25lcdsgsghhjghkukI6Ik9zY2FyIn0=.7PiMEmX8M3ABs7P8KymuXvBpTL1gdgdfLRajFbwxP5IBARM=",
"refresh": null,
"logout": null
}
}
Response with an object "user" and a message (i have problems parsing it):
{
"success": true,
"code": "USER_ALREADY_EXISTS",
"error": null,
"data": {
"user": {
"_type": "user",
"id": 32,
"name": "Pascual22",
"email": "pascual22#juanedc.com",
"picture": null
},
"message": "This email is already registered, you will receive an email with the reset password instructions"
}
}
Response with several kinds of objects (i have problems parsing it):
{
"success": true,
"code": "OK",
"error": null,
"data": {
"auth": {
"_type": "auth",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImlzcyI6Im9mZnRpbWUiLCJzdWIiOiJ1c2VyIiwiZXhwIjoxNTYwNTk2MzUwfQ==.eyJyb2xlIjpbImFwcCIsInVzZXIiXSwib3duZXIiOjI2LCJkZXZpY2UiOiI5In0=.JxbidqCnZNCt8eyRUbLE8HEJKeWgwFejz/9rMJPoKS0=",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImlzcyI6Im9mZnRpbWUiLCJzdWIiOiJ1c2VyIiwiZXhwIjoxNTYwNzY5MTUwfQ==.eyJyb2xlIjpbImFwcCIsInVzZXIiLCJyZWZyZXNoIl0sIm93bmVyIjoyNiwiZGV2aWNlIjoiOSIsInJlZnJlc2giOiJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpJVXpJMU5pSXNJbWx6Y3lJNkltOW1ablJwYldVaUxDSnpkV0lpT2lKMWMyVnlJaXdpWlhod0lqb3hOVFl3TlRrMk16VXdmUT09LmV5SnliMnhsSWpwYkltRndjQ0lzSW5WelpYSWlYU3dpYjNkdVpYSWlPakkyTENKa1pYWnBZMlVpT2lJNUluMD0uSnhiaWRxQ25aTkN0OGV5UlViTEU4SEVKS2VXZ3dGZWp6XC85ck1KUG9LUzA9In0=.JBTIyHAtVS8PgH7LzUzL3TTg8yoe7vVMBH6eauPgc7o=",
"logout": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImlzcyI6Im9mZnRpbWUiLCJzdWIiOiJ1c2VyIiwiZXhwIjoxNTYwNTk2MzUwfQ==.eyJyb2xlIjpbImFwcCIsInVzZXIiLCJsb2dvdXQiXSwib3duZXIiOjI2LCJkZXZpY2UiOiI5IiwibG9nb3V0IjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUlzSW1semN5STZJbTltWm5ScGJXVWlMQ0p6ZFdJaU9pSjFjMlZ5SWl3aVpYaHdJam94TlRZd05UazJNelV3ZlE9PS5leUp5YjJ4bElqcGJJbUZ3Y0NJc0luVnpaWElpWFN3aWIzZHVaWElpT2pJMkxDSmtaWFpwWTJVaU9pSTVJbjA9Lkp4YmlkcUNuWk5DdDhleVJVYkxFOEhFSktlV2d3RmVqelwvOXJNSlBvS1MwPSJ9.syuKoi8XiHp/me/AJAydyXfRbXa63KVWqPSKS7ySGQc="
},
"user": {
"_type": "user",
"id": 26,
"name": "Sin Foto 2",
"email": "sf2#yopmail.com",
"picture": null
},
"device": {
"_type": "device",
"id": "9",
"category": "mobile",
"manufacturer": "Xiaomi",
"model": "Mi A1",
"os": "android",
"os_version": "9",
"sim_support": true,
"name": null,
"added": "2019-06-10 10:59:10"
}
}
}
My class response:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Response {
#JsonProperty("success")
private String success;
#JsonProperty("code")
private String code;
#JsonProperty("error")
private String error;
#JsonProperty("data")
private Object data;
public String getSuccess() {return success;}
public void setSuccess(String success) {this.success = success;}
public String getCode() {return code;}
public void setCode(String code) {this.code = code;}
public String getError() {return error;}
public void setError(String error) {this.error = error;}
public Object getData() {return data;}
public void setData(Object data) {this.data = data;}
}
Initial parsing process (from Json to Response Object, using spring for android framework, ):
responseApi = restTemplate.exchange(uri, HttpMethod.POST, requestEntity, Response.class);
Parsing Process of data field to Objects (Example with a user object in "data"):
try {
User user = mapper.convertValue(responseApi.getBody().getData(), User.class);
}
catch (Exception e){
e.printStackTrace();
}
I tried to make a Data class with a generic object and a message atributes, but, it din´t work. I need to parse the response in all cases, how can i achieve it? Some suggestions about the class structure in order to make a correct automatic parsing process?
I have a simple use case where I would like to consume a resource collection that is represented with json+hal.
I use the spring RestTemplate and have configuired it to use the Jackson2HalModule.
When I debug my code I find that the Response object does contain accurate metadata (e.g. number of pages and resources) and response headers but there is no content or links. I have looked at many articles and guides on the internet over the last day, and I feel that my custom rest template should be working for my use case based on my findings.
If anybody can shed any light on this I would be eternally grateful.
My code for my service is as follows:
#Service
public class EventServiceImpl extends BaseService implements EventService {
private static final String knownEntity = "59d786d642572853721728f6";
private static String SERVICE_URL = "http://EVENTS-SERVER";
private static String EVENTS_PATH = "/events";
#Autowired
#LoadBalanced
protected RestTemplate restTemplate;
#Override
public ResponseEntity<PagedResources<Event>> fetchEventsList() {
// acceptable media type
List<MediaType> acceptableMediaTypes = Arrays.asList(HAL_JSON);
// header
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
HttpEntity<String> entity = new HttpEntity<String>(null, headers);
ResponseEntity<PagedResources<Event>> response = getRestTemplateWithHalMessageConverter()
.exchange(SERVICE_URL + EVENTS_PATH, HttpMethod.GET, entity, new ParameterizedTypeReference<PagedResources<Event>>(){});
return response;
}
public RestTemplate getRestTemplateWithHalMessageConverter() {
List<HttpMessageConverter<?>> existingConverters = restTemplate.getMessageConverters();
List<HttpMessageConverter<?>> newConverters = new ArrayList<>();
newConverters.add(getHalMessageConverter());
newConverters.addAll(existingConverters);
restTemplate.setMessageConverters(newConverters);
return restTemplate;
}
private HttpMessageConverter getHalMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
halConverter.setObjectMapper(objectMapper);
return halConverter;
}
And my simple model is:
public class Event {
private String name;
private String location;
private int capacity;
public String getName() {
return name;
}
public String getLocation() {
return location;
}
public int getCapacity() {
return capacity;
}
}
For completeness, here is a samle of the shape of the hal+json I am attempting to consume:
{
"_embedded": {
"events": [
{
"name": null,
"location": null,
"capacity": 0,
"currentState": "CANCELLED",
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events/59d786d642572853721728f6"
},
"event": {
"href": "http://192.168.1.6:2221/events/59d786d642572853721728f6"
},
"reinstate": {
"href": "http://192.168.1.6:2221/events/59d786d642572853721728f6/reinstate"
},
"reschedule": {
"href": "http://192.168.1.6:2221/events/59d786d642572853721728f6/reschedule"
}
}
},
{
"name": null,
"location": null,
"capacity": 0,
"currentState": "ADVERTISED",
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events/59d7f14342572812ceca7fc6"
},
"event": {
"href": "http://192.168.1.6:2221/events/59d7f14342572812ceca7fc6"
},
"cancel": {
"href": "http://192.168.1.6:2221/events/59d7f14342572812ceca7fc6/cancel"
},
"reschedule": {
"href": "http://192.168.1.6:2221/events/59d7f14342572812ceca7fc6/reschedule"
}
}
},
{
"name": null,
"location": null,
"capacity": 0,
"currentState": "ADVERTISED",
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events/59d7f14742572812ceca7fc7"
},
"event": {
"href": "http://192.168.1.6:2221/events/59d7f14742572812ceca7fc7"
},
"cancel": {
"href": "http://192.168.1.6:2221/events/59d7f14742572812ceca7fc7/cancel"
},
"reschedule": {
"href": "http://192.168.1.6:2221/events/59d7f14742572812ceca7fc7/reschedule"
}
}
},
{
"name": null,
"location": null,
"capacity": 0,
"currentState": "ADVERTISED",
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events/59d7f14c42572812ceca7fc8"
},
"event": {
"href": "http://192.168.1.6:2221/events/59d7f14c42572812ceca7fc8"
},
"cancel": {
"href": "http://192.168.1.6:2221/events/59d7f14c42572812ceca7fc8/cancel"
},
"reschedule": {
"href": "http://192.168.1.6:2221/events/59d7f14c42572812ceca7fc8/reschedule"
}
}
}
]
},
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events{?page,size,sort}",
"templated": true
},
"profile": {
"href": "http://192.168.1.6:2221/profile/events"
}
},
"page": {
"size": 20,
"totalElements": 4,
"totalPages": 1,
"number": 0
}
}
EDIT: I can consume an individual Event with no problems.
I had a similar problem and I ended up using org.springframework.hateoas.Resources
For my example below, this object is located in org.springframework.hateoas:spring-hateoas:jar:0.25.2.RELEASE
which is being pulled in from org.springframework.boot:spring-boot-starter-data-rest:jar:2.1.7.RELEASE, so there's a good chance its already in your classpath assuming you declare the spring-boot-starter-data-rest dependency.
Here's a simple example (using your info):
RestTemplate restTemplate = new RestTemplate();
Resources<Event> resourceEvents = restTemplate.getForObject("http://192.168.1.6:2221/events", Resources.class);
List<Event> events = new ArrayList<>(resourceEvents.getContent());
There are probably some gotchas the make this not so straight forward, but hopefully this provides a start to solving your problem.