how to mapping join type by using spring data elasticSearch - elasticsearch

i reindex data from es 2.4 to 5.6.
data in es 2.4 have 2 types,and the 2 type is parent-child relation.
when reindex it to es 5.6,the index only contains single type,the parent-child relation by using join type to resolving.
the data above works ok.
the mapping example like this, it contains a join type:
"mappings": {
"doc": {
"properties": {
"my_join_field": {
"eager_global_ordinals": true,
"type": "join",
"relations": {
"question": "answer"
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
}
}
}
}
how to mapping join type by using spring data elasticSearch:
in old version code es 2.4,i can mapping it like this:
#Document(indexName = ParentEntity.INDEX, type = ParentEntity.PARENT_TYPE, shards = 1, replicas = 0, refreshInterval = "-1")
public class ParentEntity {
public static final String INDEX = "parent-child";
public static final String PARENT_TYPE = "parent-entity";
public static final String CHILD_TYPE = "child-entity";
#Id
private String id;
#Field(type = FieldType.Text, store = true)
private String name;
public ParentEntity() {
}
public ParentEntity(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
#Override
public String toString() {
return new ToStringCreator(this).append("id", id).append("name", name).toString();
}
#Document(indexName = INDEX, type = CHILD_TYPE, shards = 1, replicas = 0, refreshInterval = "-1")
public static class ChildEntity {
#Id
private String id;
#Field(type = FieldType.Text, store = true)
#Parent(type = PARENT_TYPE)
private String parentId;
#Field(type = FieldType.Text, store = true)
private String name;
public ChildEntity() {
}
public ChildEntity(String id, String parentId, String name) {
this.id = id;
this.parentId = parentId;
this.name = name;
}
public String getId() {
return id;
}
public String getParentId() {
return parentId;
}
public String getName() {
return name;
}
#Override
public String toString() {
return new ToStringCreator(this).append("id", id).append("parentId", parentId).append("name", name).toString();
}
}
}
how can i Mapping join type by using spring data elasticSearch v3.0.10?
Today, i tried the entity below to working at spring data elasticSearch 3.0.10:
#Document(indexName = "join_index", type = "join_mapping")
#Data
public class JoinEntity {
#Id
private String id;
#Mapping(mappingPath = "/mappings/join_type.json")
private Map<String,String> relationType;
#Field(type = FieldType.Keyword)
private String name;
//#Parent(type = "question")
#Field(type = FieldType.Keyword)
private String parentId;
}
join_type.json below:
{
"type": "join",
"relations": {
"question": "answer"
}
}
it create index and put mapping work ok:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:elasticsearch-template-test.xml")
public class ElasticsearchTemplateJoinTests {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
#Before
public void before() {
clean();
elasticsearchTemplate.deleteIndex(JoinEntity.class);
elasticsearchTemplate.createIndex(JoinEntity.class);
elasticsearchTemplate.putMapping(JoinEntity.class);
elasticsearchTemplate.refresh(JoinEntity.class);
}
#Test
public void shouldCreateIndexAndMappingSuccess(){
Map mapping = elasticsearchTemplate.getMapping(JoinEntity.class);
assertThat(mapping, is(notNullValue()));
Map properties = (Map) mapping.get("properties");
assertThat(properties, is(notNullValue()));
assertThat(properties.containsKey("name"), is(true));
Map file = (Map) properties.get("relationType");
assertThat(file, is(notNullValue()));
assertThat(((String) file.get("type")), is("join"));
}
}
when index parent work ok too,but index child it throws exception:
#Test
public void shouldIndexParentAndChildSuccess(){
JoinEntity parenEntity = new JoinEntity();
parenEntity.setName("parent_name");
parenEntity.setRelationType(Collections.singletonMap("name","question"));
IndexQuery parentQuery = new IndexQueryBuilder().withId("11").withObject(parenEntity).build();
final String id = elasticsearchTemplate.index(parentQuery);
assertThat("11",is(id));
JoinEntity childEntity = new JoinEntity();
childEntity.setName("child_name");
Map<String,String> joinRelation = new HashMap<>(2);
joinRelation.put("name","answer");
joinRelation.put("parent", "11");
childEntity.setRelationType(joinRelation);
childEntity.setParentId("11");
IndexQuery childQuery = new IndexQueryBuilder().withId("22").withObject(childEntity).build();
elasticsearchTemplate.index(childQuery);
}
exception:
MapperParsingException[failed to parse
]; nested: IllegalArgumentException[[routing] is missing for join field [relationType]];
at org.elasticsearch.index.mapper.DocumentParser.wrapInMapperParsingException(DocumentParser.java:171)
how can i resolve this problem or Mapping the new version Parent-child relation correctly?thks!!

Elasticsearch needs the parent document routing parameter when you index child document check this
This is because both parent and child documents must be indexed in same shard to join to work.
However I couldn't figure out a way to solve this using Spring data elasticsearch. The only way that worked was using RestHighLevelClient
The recent version of Spring Data ElasticSearch had added support for this doc
Your child indexing would be something like,
IndexRequest indexRequest = new IndexRequest();
indexRequest.source(objectMapper.writeValueAsString(childEntity),XContentType.JSON);
indexRequest.id("22"); //child doc id
indexRequest.index(INDEX_NAME);
indexRequest.type(INDEX_TYPE);
indexRequest.routing("11"); //parent doc id
restHighLevelClient.index(indexRequest);

Finally, i gived up the parent-child relation, i split them into two separate indexs. some advance feature should be used less if not neccessary.

Related

Spring Boot, query Elasticsearch specific fields from already indexed data created by Elastic Stack

The target is to query specific fields from an index via a spring boot app.
Questions in the end.
The data in elasticsearch are created from Elastic Stack with Beats and Logstash etc. There is some inconsistency, eg some fields may be missing on some hits.
The spring app does not add the data and has no control on the fields and indexes
The query I need, with _source brings
GET index-2022.07.27/_search
{
"from": 0,
"size": 100,
"_source": ["#timestamp","message", "agent.id"],
"query": {
"match_all": {}
}
}
brings the hits as
{
"_index": "index-2022.07.27",
"_id": "C1zzPoIBgxar5OgxR-cs",
"_score": 1,
"_ignored": [
"event.original.keyword"
],
"_source": {
"agent": {
"id": "ddece977-9fbb-4f63-896c-d3cf5708f846"
},
"#timestamp": "2022-07-27T09:18:27.465Z",
"message": """a message"""
}
},
and with fields instead of _source is
{
"_index": "index-2022.07.27",
"_id": "C1zzPoIBgxar5OgxR-cs",
"_score": 1,
"_ignored": [
"event.original.keyword"
],
"fields": {
"#timestamp": [
"2022-07-27T09:18:27.465Z"
],
"agent.id": [
"ddece977-9fbb-4f63-896c-d3cf5708f846"
],
"message": [
"""a message"""
]
}
},
How can I get this query with Spring Boot ?
I lean on StringQuery with the RestHighLevelClient as below but cant get it to work
Query searchQuery = new StringQuery("{\"_source\":[\"#timestamp\",\"message\",\"agent.id\"],\"query\":{\"match_all\":{}}}");
SearchHits<Items> productHits = elasticsearchOperations.search(
searchQuery,
Items.class,
IndexCoordinates.of(CURRENT_INDEX));
What form must Items.class have? What fields?
I just need timestamp, message, agent.id. The later is optional, it may not exist.
How will the mapping work?
versions:
Elastic: 8.3.2
Spring boot: 2.6.6
elastic (mvn): 7.15.2
spring-data-elasticsearch (mvn): 4.3.3
official documentation states that with RestHighLevelClient the versions should be supported
Support for upcoming versions of Elasticsearch is being tracked and
general compatibility should be given assuming the usage of the
high-level REST client.
You can define an entity class for the data you want to read (note I have a nested class for the agent):
#Document(indexName = "index-so", createIndex = false)
public class SO {
#Id
private String id;
#Field(name = "#timestamp", type = FieldType.Date, format = DateFormat.date_time)
private Instant timestamp;
#Field(type = FieldType.Object)
private Agent agent;
#Field(type = FieldType.Text)
private String message;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Instant getTimestamp() {
return timestamp;
}
public void setTimestamp(Instant timestamp) {
this.timestamp = timestamp;
}
public Agent getAgent() {
return agent;
}
public void setAgent(Agent agent) {
this.agent = agent;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
class Agent {
#Field(name = "id", type = FieldType.Keyword)
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
}
The query then would be:
var query = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withSourceFilter(new FetchSourceFilter(
new String[]{"#timestamp", "message", "agent.id"},
new String[]{}))
.build();
var searchHits = operations.search(query, SO.class);

Updating table is not happening through Setters in Spring JPA

I am new to Spring boot and Spring Data JPA . So here i am trying to implement a sample project where a employee has list of workers , while adding a new worker has employee details also to indicate that he works for particular employee. I am able to update the worker table and also fetch the details perfectly . Am trying to update Employee table as well so that while fetching a particular employee i want the list of workers associated with him also to be fetched . But that is not happening , i haven't used any query so far as it seems simple updation and i thought just save and setters would help to do so .
Employee.Java
#Entity
public class Employee {
#Id
private int empId;
private String empName;
private String location;
#OneToMany
private List<Worker> workers;
public Employee(){
}
public Employee(int empId, String empName, String location) {
super();
this.empId = empId;
this.empName = empName;
this.location = location;
}
public List<Worker> getWorkers() {
return workers;
}
public void setWorkers(List<Worker> workers) {
this.workers = workers;
}
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public void setWorkers(Worker worker) {
this.workers.add(worker);
}
#Override
public String toString() {
return "Employee [empId=" + empId + ", empName=" + empName + ", location=" + location + ", workers=" + workers
+ "]";
}
/*#Override
public String toString() {
return "Employee [empId=" + empId + ", empName=" + empName + ", location=" + location + "]";
}*/
Worker.Java
#Entity
public class Worker {
#Id
private int id;
private String name;
#ManyToOne
#JoinColumn(name="empId")
private Employee employee;
public Worker(int id, String name , int empId) {
super();
this.id = id;
this.name = name;
this.employee = new Employee(empId,"","");
}
public Worker() {
// TODO Auto-generated constructor stub
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
WorkerController.Java
#RestController
public class WorkerController {
#Autowired
WorkerRepository workerService;
#Autowired
EmployeeRepository employeeService;
#GetMapping("/employees/{id}/workers")
public List<Worker> getAllWorker(#PathVariable("id") int empId){
return workerService.findByEmployeeEmpId(empId);
}
#PostMapping("/employees/{id}/workers")
public String addNewEmployee(#RequestParam("name") String name ,
#RequestParam("workerId") int id , #PathVariable("id") int empId){
Worker worker = new Worker();
List<Worker> workers = new ArrayList<Worker>();
worker.setId(id);
worker.setName(name);
worker.setEmployee(new Employee(empId,"",""));
workerService.save(worker);
workers.add(worker);
employeeService.findById(empId).get().setWorkers(workers);
Employee emp = new Employee();
emp = employeeService.findById(empId).get();
return "Successfully added";
}
}
After adding worker , i retrieve the following as output
[
{
"id": 108,
"name": "vijay",
"employee": {
"empId": 99,
"empName": "darsha",
"location": "mumbai",
"workers": []
}
},
{
"id": 110,
"name": "suraj",
"employee": {
"empId": 99,
"empName": "darsha",
"location": "mumbai",
"workers": []
}
}
]
but while retrieving i could see the employee table is not updated. can someone guide me .
{
"empId": 99,
"empName": "darsha",
"location": "mumbai",
"workers": []
}
You says:
"...while fetching a particular employee i want the list of workers
associated with him also to be fetched"
then you have to write a OneToMany Relationship on employee's side. What you do is you tries to fetch for each Worker one Employee which is of course working and is there in your JSON result.
Here is a OneToMany Example on Emplyees class side:
#OneToMany(mappedBy="employee", fetch=FetchType.EAGER, cascade=CascadeType.ALL)
private List<Worker> worker = new ArrayList<>();
Note: mappedBy have to refer to the variable Emplyee employee in your Worker Class.
Please try different fetch types also.
Why you are not getting your #OneToMany attributes is due fetch type is lazy by default for all #OneToMany associations like list, set.
To overcome this problem. Change the default lazy fetch to eager fetch.
Modify your entity mapping to this.
#OneToMany(mappedBy="employee", fetch=FetchType.EAGER, cascade=CascadeType.ALL) private List<Worker> worker = new ArrayList<>();
while fetching you may get recursive mapping. To avoid this just add #JsonIgnoreProperties to your employee field in worker class.
Example:
#JsonIgnoreProperties("employee")
#ManyToOne
#JoinColumn(name="empId")
private Employee employee;

Query MongoDb based on Map Key Spring Repository

I need help to query nested documents. Using Spring Boot with MongoDB.
Structure:
public class Holiday {
#Id
private String id;
private Integer year;
private Map<String, List<HolidayElement>> holidays = new HashMap<>();
}
public class HolidayElement {
private String name;
#JsonFormat(pattern="yyyy-MM-dd")
private Date date;
private String note;
}
After saving everything the Json looks like:
[
{
"id": "5a153331b3cb1f0001e1edeb",
"year": 2017,
"holidays": {
"BB": [
{
"name": "Neujahrstag",
"date": "2017-01-01",
"note": ""
},
...
],
"HH": [
{ ... }
]
}
]
Now how can I get for instance: List of "HolidayElement" where the State is "BB"?
Assuming you have a repository like HolidayRepository, you need to create a custom implementation since you want to use MongoTemplate. So your HolidayRepository will look like
#Repository
public interface HolidayRepository extends MongoRepository<Holiday, String>, HolidayRepositoryCustom {
}
And declare two new files HolidayRepositoryCustom and HolidayRepositoryImpl in the same directory(very important) as HolidayRepository
public interface HolidayRepositoryCustom {
List<HolidayElement> findByMapId(final String mapId);
}
And the Impl class will look like this
public class HolidayRepositoryImpl implements HolidayRepositoryCustom {
private final MongoTemplate mongoTemplate;
#Autowired
public HolidayRepositoryImpl(final MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Override
public List<HolidayElement> findByMapId(String mapId) {
final QueryBuilder queryBuilder = QueryBuilder.start();
queryBuilder
.and("holidays."+mapId).exists(true);
final DBObject projection = new BasicDBObject();
projection.put("holidays."+mapId, 1);
String collectionName = "Holiday";//Change to your collection name
try( final DBCursor dbCursor = mongoTemplate.getCollection(collectionName).find(queryBuilder.get(), projection)){
if(dbCursor.hasNext()){
DBObject next = dbCursor.next();
Map<String, List<HolidayElement>> holidayElements =
(Map<String, List<HolidayElement>>) next.get("holidays");
return holidayElements.get(mapId);
}
}
return Lists.newArrayList();
}
}

Nested Group with Spring MongoDB

I need to generate a result with the number of alerts of each level for each user.
A structure similar to the following:
{
"identitity": "59e3b9dc5a3254691f327b67",
"alerts": [
{
"level": "INFO",
"count": "3"
},
{
"level": "ERROR",
"count": "10"
}
]
}
The alert entitity has the following structure:
#Document(collection = AlertEntity.COLLECTION_NAME)
public class AlertEntity {
public final static String COLLECTION_NAME = "alerts";
#Id
private ObjectId id;
#Field
private AlertLevelEnum level = AlertLevelEnum.INFO;
#Field("title")
private String title;
#Field("payload")
private String payload;
#Field("create_at")
private Date createAt = new Date();
#Field("delivered_at")
private Date deliveredAt;
#Field("delivery_mode")
private AlertDeliveryModeEnum deliveryMode =
AlertDeliveryModeEnum.PUSH_NOTIFICATION;
#Field("parent")
#DBRef
private ParentEntity parent;
#Field("son")
#DBRef
private SonEntity son;
private Boolean delivered = Boolean.FALSE;
}
I have implemented the following method tried to project the result in a nested way. But the "Identity" field is always null and the "alerts" field is a empty collection.
#Override
public List<AlertsBySonDTO> getAlertsBySon(List<String> sonIds) {
TypedAggregation<AlertEntity> alertsAggregation =
Aggregation.newAggregation(AlertEntity.class,
Aggregation.group("son.id", "level").count().as("count"),
Aggregation.project().and("son.id").as("id")
.and("alerts").nested(
bind("level", "level").and("count")));
// Aggregation.match(Criteria.where("_id").in(sonIds)
AggregationResults<AlertsBySonDTO> results = mongoTemplate.
aggregate(alertsAggregation, AlertsBySonDTO.class);
List<AlertsBySonDTO> alertsBySonResultsList = results.getMappedResults();
return alertsBySonResultsList;
}
The result I get is the following:
{
"response_code_name": "ALERTS_BY_SON",
"response_status": "SUCCESS",
"response_http_status": "OK",
"response_info_url": "http://yourAppUrlToDocumentedApiCodes.com/api/support/710",
"response_data": [
{
"identity": null,
"alerts": []
},
{
"identity": null,
"alerts": []
}
],
"response_code": 710
}
The result DTO is as follows:
public final class AlertsBySonDTO implements Serializable {
private static final long serialVersionUID = 1L;
#JsonProperty("identity")
private String id;
#JsonProperty("alerts")
private ArrayList<Map<String, String>> alerts;
public AlertsBySonDTO() {
super();
}
public AlertsBySonDTO(String id, ArrayList<Map<String, String>> alerts) {
super();
this.id = id;
this.alerts = alerts;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public ArrayList<Map<String, String>> getAlerts() {
return alerts;
}
public void setAlerts(ArrayList<Map<String, String>> alerts) {
this.alerts = alerts;
}
}
What needs to be done to project the result in a nested way?
Thanks in advance
In aggregation framework there is an $unwind operator which will basically transform your one element collection with nested array of two elements to two separate documents with one element from this array. So you'll get:
{
"identitity": "59e3b9dc5a3254691f327b67",
"alerts": {
"level": "INFO",
"count": "3"
}
}
{
"identitity": "59e3b9dc5a3254691f327b67",
"alerts": {
"level": "ERROR",
"count": "10"
}
}
And this is where you can start your group by with count. Should be working fine.

Expose enums with Spring Data REST

I'm using Spring Boot 1.5.3, Spring Data REST, HATEOAS.
I've a simple entity model:
#Entity
public class User extends AbstractEntity implements UserDetails {
private static final long serialVersionUID = 5745401123028683585L;
public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
#NotNull(message = "The name of the user cannot be blank")
#Column(nullable = false)
private String name;
/** CONTACT INFORMATION **/
private String landlinePhone;
private String mobilePhone;
#NotNull(message = "The username cannot be blank")
#Column(nullable = false, unique = true)
private String username;
#Email(message = "The email address is not valid")
private String email;
#JsonIgnore
private String password;
#Column(nullable = false)
private String timeZone = "Europe/Rome";
#JsonIgnore
private LocalDateTime lastPasswordResetDate;
#Column(nullable = false, columnDefinition = "BOOLEAN default true")
private boolean enabled = true;
#Type(type = "json")
#Column(columnDefinition = "json")
private Roles[] roles = new Roles[] {};
and my enum Roles is:
public enum Roles {
ROLE_ADMIN, ROLE_USER, ROLE_MANAGER, ROLE_TECH;
#JsonCreator
public static Roles create(String value) {
if (value == null) {
throw new IllegalArgumentException();
}
for (Roles v : values()) {
if (value.equals(v.toString())) {
return v;
}
}
throw new IllegalArgumentException();
}
}
I'm creating a client in Angular 4. Spring Data REST is great and expose repository easily return my model HATEOAS compliant:
{
"_embedded": {
"users": [
{
"name": "Administrator",
"username": "admin",
"roles": [
"Amministratore"
],
"activeWorkSession": "",
"_links": {
"self": {
"href": "http://localhost:8080/api/v1/users/1"
},
"user": {
"href": "http://localhost:8080/api/v1/users/1{?projection}",
"templated": true
}
}
},
Like you can see I'm also translating via rest-messages.properties the value of my enums. Great!
My Angular page now needs the complete lists of roles (enums). I've some question:
understand the better way for the server to return the list of roles
how to return this list
My first attemp was to create a RepositoryRestController in order to take advantage of what Spring Data REST offers.
#RepositoryRestController
#RequestMapping(path = "/api/v1")
public class UserController {
#Autowired
private EntityLinks entityLinks;
#RequestMapping(method = RequestMethod.GET, path = "/users/roles", produces = "application/json")
public Resource<Roles> findRoles() {
Resource<Roles> resource = new Resource<>(Roles.ROLE_ADMIN);
return resource;
}
Unfortunately, for some reason, the call to this methods return a 404 error. I debugged and the resource is created correctly, so I guess the problem is somewhere in the JSON conversion.
how to return this list?
#RepositoryRestController
#RequestMapping("/roles")
public class RoleController {
#GetMapping
public ResponseEntity<?> getAllRoles() {
List<Resource<Roles>> content = new ArrayList<>();
content.addAll(Arrays.asList(
new Resource<>(Roles.ROLE1 /*, Optional Links */),
new Resource<>(Roles.ROLE2 /*, Optional Links */)));
return ResponseEntity.ok(new Resources<>(content /*, Optional Links */));
}
}
I was playing around with this and have found a couple of ways to do it.
Assume you have a front end form that wants to display a combo box containing priorities for a single Todo such as High, Medium, Low. The form needs to know the primary key or id which is the enum value in this instance and the value should be the readable formatted value the combo box should display.
If you wish to customize the json response in 1 place only such as a single endpoint then I found this useful. The secret sauce is using the value object PriorityValue to allow you to rename the json field through #Relation.
public enum Priority {
HIGH("High"),
NORMAL("Normal"),
LOW("Low");
private final String description;
Priority(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static List<Priority> orderedValues = new ArrayList<>();
static {
orderedValues.addAll(Arrays.asList(Priority.values()));
}
}
#RepositoryRestController
#RequestMapping(value="/")
public class PriorityController {
#Relation(collectionRelation = "priorities")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
private class PriorityValue {
private String id;
private String value;
public PriorityValue(String id,
String value) {
this.id = id;
this.value = value;
}
}
#GetMapping(value = "/api/priorities", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Resources<PriorityValue>> getPriorities() {
List<PriorityValue> priorities = Priority.orderedValues.stream()
.map(p -> new PriorityValue(p.name(), p.getDescription()))
.collect(Collectors.toList());
Resources<PriorityValue> resources = new Resources<>(priorities);
resources.add(linkTo(methodOn(PriorityController.class).getPriorities()).withSelfRel());
return ResponseEntity.ok(resources);
}
}
Another approach is to use a custom JsonSerializer. The only issue using this is everywhere a Priority enum is serialized you will end up using this format which may not be what you want.
#JsonSerialize(using = PrioritySerializer.class)
#Relation(collectionRelation = "priorities")
public enum Priority {
HIGH("High"),
NORMAL("Normal"),
LOW("Low");
private final String description;
Priority(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static List<Priority> orderedValues = new ArrayList<>();
static {
orderedValues.addAll(Arrays.asList(Priority.values()));
}
}
#RepositoryRestController
#RequestMapping(value="/api")
public class PriorityController {
#GetMapping(value = "/priorities", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Resources<Priority>> getPriorities() {
Resources<Priority> resources = new Resources<>(Priority.orderedValues);
resources.add(linkTo(methodOn(PriorityController.class).getPriorities()).withSelfRel());
return ResponseEntity.ok(resources);
}
}
public class PrioritySerializer extends JsonSerializer<Priority> {
#Override
public void serialize(Priority priority,
JsonGenerator generator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("id");
generator.writeString(priority.name());
generator.writeFieldName("value");
generator.writeString(priority.getDescription());
generator.writeEndObject();
}
}
The final json response from http://localhost:8080/api/priorities
{
"_embedded": {
"priorities": [
{
"id": "HIGH",
"value": "High"
},
{
"id": "NORMAL",
"value": "Normal"
},
{
"id": "LOW",
"value": "Low"
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/priorities"
}
}
}

Resources