Im new to Spring Boot and got a problem were i need to consume 2 remote Rest services and merge the results. Would need some insight on the right approach.
I got something like this:
{"subInventories":[
{"OrganizationId": 0,
"OrganizationCode":"",
"SecondaryInventoryName":"",
"Description":""},...{}...],
{"organizations":[
{"OrganizationId":0,
"OrganizationCode":"",
"OrganizationName":"",
"ManagementBusinessUnitId":,
"ManagementBusinessUnitName":""}, ...{}...]}
and need to make it into something like this:
{"items":[
{"OrganizationId":0,
"OrganizationCode":"",
"OrganizationName":"",
"ManagementBusinessUnitId":0,
"ManagementBusinessUnitName":"",
"SecondaryInventoryName":"",
"Description":""},...{}...]
got 2 #Entitys to represent each item, Organizations and Inventories with the attributtes like the JSON fields.
EDIT
Currently trying to get matches with Java8 stream()
#GetMapping("/manipulate")
public List<Organization> getManipulate() {
List<Organization> organization = (List<Organization>)(Object) organizationController.getOrganization();
List<SubInventories> subInventories = (List<SubInventories>)(Object) getSuvInventories();
List<Organization> intersect = organization.stream().filter(o -> subInventories.stream().anyMatch(s -> s.getOrganizationId()==o.getOrganizationId()))
.collect(Collectors.toList());
return intersect;
}
found this searching but i got many classes and I don't know if it would be better to just for each organization get the subinventories and put them in a list of maps like
List<Map<String,Object> myList = new ArrayList<>();
//Loops here
Map<String,Object> a = new HashMap<>();
a.put("OrganizationID", 1231242415)...
myList.add(a)
Quite lost in what the right approach is.
EDIT2
Here the classes I'm using.
Organizations
#Entity
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Organization implements Serializable{
//#JsonObject("OrganizationId")
#Id
private Long OrganizationId;
private Long ManagementBusinessUnitId;
private String OrganizationCode,OrganizationName,ManagementBusinessUnitName;
public Organization() {
}
//getters setters
}
SubInventories
#Entity
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubInventories implements Serializable{
#Id
private Long OrganizationId;
private String OrganizationCode,SecondaryInventoryName,Description;
public SubInventories() {
}
//getters and setters
}
Wrapper to unwrapp consume
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Wrapper {
//#JsonProperty("items")
private List<Object> items;
public Wrapper() {
}
public List<Object> getOrganization() {
return items;
}
public void setOrganization(List<Object> organization) {
this.items = organization;
}
}
OrganizationController
#RestController
public class OrganizationController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#GetMapping("/organizations")
public List<Object> getOrganization() {
return getOrganizationInfo();
}
private List<Object> getOrganizationInfo() {
String url = env.getProperty("web.INVENTORY_ORGANIZATIONS");
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
builder.queryParam("fields", "OrganizationId,OrganizationCode,OrganizationName,ManagementBusinessUnitId,ManagementBusinessUnitName");
builder.queryParam("onlyData", "true");
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(env.getProperty("authentication.name"),env.getProperty("authentication.password"));
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Wrapper> temp = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference<Wrapper>() {});
List<Object> data = temp.getBody().getOrganization();
return data;
}
}
SubInventoryController
#RestController
public class SubInventoryController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#GetMapping("/sub")
public List<Object> getSuvInventories() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("this is private :(");
builder.queryParam("onlyData", "true");
builder.queryParam("expand", "subinventoriesDFF");
builder.queryParam("limit", "999999");
builder.queryParam("fields", "OrganizationId,OrganizationCode,SecondaryInventoryName,Description");
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(env.getProperty("authentication.name"),env.getProperty("authentication.password"));
headers.set("REST-Framework-Version", "2");
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Wrapper> subInventories = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference<Wrapper>() {});
List<Object> data = subInventories.getBody().getOrganization();
return data;
}
}
where I'm right now
#RestController
public class MainController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#Autowired
private OrganizationController organizationController;
#Autowired
private SubInventoryController subInventoryController;
#GetMapping("/manipulate")
public Map<Organization, List<SubInventories>> getManipulate() {
List<Organization> organizations = (List<Organization>)(Object) organizationController.getOrganization();
List<SubInventories> subInventories = (List<SubInventories>)(Object) subInventoryController.getSuvInventories();
Map<Organization,List<SubInventories>> result = new HashMap<Organization,List<SubInventories>>();
for(Organization organization : organizations) {
List<SubInventories> subInventoryMatched = (List<SubInventories>) subInventories.stream().filter( s -> s.getOrganizationId()== organization.getOrganizationId()).collect(Collectors.toList());
result.put(organizations.get(0), subInventoryMatched);
}
return result;
}
}
From what I understand I need to make a wrapper class for each POJO cause the response looks like this
/organizations
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "adasd",
"OrganizationName": "Hotel Bahía Príncipe Sunlight Costa Adeje",
"ManagementBusinessUnitId": 131231,
"ManagementBusinessUnitName": "asdasfdas"
},
{
"OrganizationId": 2,
"OrganizationCode": "adadas",
"OrganizationName": "Hadasd",
"ManagementBusinessUnitId": 1231,
"ManagementBusinessUnitName": "aewfrqaew"
}]}
and /subInventories
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "asada",
"SecondaryInventoryName": "adfasdfasdgf",
"Description": "pub"
},
{
"OrganizationId": 2,
"OrganizationCode": "asgfrgtsdh",
"SecondaryInventoryName": "B LOB",
"Description": "pub2"
}
]}
If used the generic one with Object I get a java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.demo.model.Organization in the stream().filter and for the merge of the fields another class to get the desired
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "asdas",
"OrganizationName": "adsadasd",
"ManagementBusinessUnitId": 1,
"ManagementBusinessUnitName": "asdasdf",
"SecondaryInventoryName": "sfsdfsfa",
"Description": "pub1"
}]}
Tons of classes if i get lots of POJO
I assume the following from the information you provide:
You have two Datatypes (Java classes). They should be merged together to one Java class
You have to load this data from different sources
Non of the classes are leading
I can provide you some example code. The code is based on the previos adoptions. This will give you an idea. It's not a simple copy and paste solution.
At first create a class with all fields you want to include in the result:
public class Matched {
private Object fieldA;
private Object fieldB;
// Some getter and Setter
}
The Basic idea is that you load your data. Than find the two corresponding objects. After that do your matching for each field.
public List<Matched> matchYourData() {
// load your data
List<DataA> dataAList = loadYourDataA();
List<DataB> dataBList = loadYourDataB();
List<Matched> resultList = new ArryList<>();
for (dataA: DataA) {
DataB dataB = dataBList.stream()
.filter(data -> data.getId() == dataA.getId())
.findFirst().orElseThrow();
// Now you have your data. Let's match them.
Matched matched = new Matched();
matched.setFieldA(dataB.getFieldA() == dataA.getFieldA() ? doSomething() : doSomethingElse());
// Set all your fields. Decide for everyone the matching strategy
resultList.add(matched);
}
return resultList;
}
This is a quite simple solution. Of course you can use Tools like Mapstruct for mapping purpose. But this depends on your environment.
Related
I am using spring boot application with frontend (spring boot application using thymeleaf) and backend (spring boot REST application ) are separated using REST api. The frontend uses HttpClient to send request to backend. Whenever I try to update an object the HttpClient creates an error for json parsing. The request is not accepted by the backend (ProcessDTORequest object ) with error as follows.
The exception is as follows:
{"message":"JSON parse error: Cannot construct instance of `com.app.dataaccess.entity.Process` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('68d22e4d-7116-4130-aa06-9ba120aadc66'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.app.dataaccess.entity.Process` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('68d22e4d-7116-4130-aa06-9ba120aadc66')\n at [Source: (PushbackInputStream); line: 1, column: 10310] (through reference chain: com.app.ui.dto.request.ProcessDTORequest[\"answeredQuestionnaires\"]->java.util.HashSet[0]->com.app.dataaccess.entity.AnsweredQuestionnaire[\"process\"])","httpStatus":"INTERNAL_SERVER_ERROR","timeStamp":"2022-11-04T08:44:35.9108286Z"}
HttpClient method for post request is as follows:
public String executePost(
final String url, final Object payLoad, final Map<String, String> headers,
final Map<String, String> params) throws Exception {
final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// Add query strings to URL
URIBuilder builder = new URIBuilder(url);
for (final Map.Entry<String, String> elm : params.entrySet()) {
builder = builder.setParameter(elm.getKey(), elm.getValue());
}
// can change for HttpPut, HttpPost, HttpPatch
final HttpPost request = new HttpPost(builder.build());
// Add headers from input map
for (final Map.Entry<String, String> elm : headers.entrySet()) {
request.addHeader(elm.getKey(), elm.getValue());
}
request.setHeader("Accept", "application/json");
request.setHeader("Content-type", "application/json");
// Send Json String as body, can also send UrlEncodedFormEntity
final StringEntity entity = new StringEntity(objectMapper.writeValueAsString(payLoad));
request.setEntity(entity);
try {
final CloseableHttpResponse response = httpClient.execute(request);
System.out.println("Return response status code: "+response.getStatusLine().getStatusCode());
System.out.println("Return response status code: "+response.getStatusLine());
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// Read response string using EntityUtils class of Apache http client library
// Serialize json string into map or any other object
return EntityUtils.toString(response.getEntity());
} else {
throw new Exception(EntityUtils.toString(response.getEntity()));
// throw new Exception(String.format("Response status code was and response was ",
// response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity())));
}
} catch (final ClientProtocolException e) {
throw new Exception("Client protocol Exception occurred while executing request", e);
} catch (final Exception e) {
System.out.println(e);
throw new Exception(e);
}
}
I used the configuration for object mapper as follows:
#Configuration
public class AppConfig {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
objectMapper.registerModule(new JavaTimeModule());
return objectMapper; }
}
Process.java (this is used for serializing/deserializing)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
public class Process {
private UUID processId;
private List<User> users = new ArrayList<>();
private List<UnitType> units = new ArrayList<>();
private String furtherComment;
private List<AnsweredQuestionnaire> answeredQuestionnaires = new ArrayList<>()
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Process)) return false;
Process process = (Process) o;
return getProcessId().equals(process.getProcessId());
}
#Override
public int hashCode() {
return Objects.hash(getProcessId());
}
}
The json from the server is like the following
{
"#id": "bba35e58-5d4b-44ce-9a5a-486f55f79af7",
"processId": "21ef7f9d-4fcc-417c-96e8-4327206d2592",
"users": [
{
"#id": "69d2f392-8213-4f34-9cb5-f0c403170787",
"userId": "5a17ec5f-c20a-4873-93af-bf69fad4eb26",
"roles": [
{
"roleId": "f6ad33a7-9d03-4260-81c2-a4a4c791e30a",
"users": []
}
],
"processes": []
}
],
"units": [
{
"unitTypeId": "c784d197-1dc7-446e-b3e5-6468a7954878",
"unit": {
"unitId": "aba76d05-e2ea-4b5a-828b-349966595258"
},
"isResponsibleUnit": true
}
],
"furtherComment": "",
"answeredQuestionnaires": [
{
"#id": "7ca1af09-eefd-4c56-9587-581858fbbc57"
}
]
}
The relation between the entities Process, AnsweredQuestionnaire and User is as follows:
Between Process and AnsweredQuestionnaire (One-to-many) respectively.
Between Process and User (many-to-many).
Between Process and UnitType (one-to-many) respectively.
AnsweredQuestionnaire.java
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class AnsweredQuestionnaire {
private UUID answeredQuestionnaireId;
private Questionnaire questionnaire;
private Process process;
public void addProcessToAnsweredQuestionnaire(Process process){
//remove old association
if(this.process != null){
this.process.getAnsweredQuestionnaires().remove(this);
}
this.process = process;
//add new association
if(process != null){
this.process.getAnsweredQuestionnaires().add(this);
}
}
}
User.java
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class User {
private UUID userId;
private String firstName;
private String lastName;
private String phoneNumber;
private String email;
private List<Role> roles = new ArrayList<>();
private List<Process> processes = new ArrayList<>();
public void addProcessToUser(Process process){
this.processes.add(process);
process.getUsers().add(this);
}
public void removeProcessFromUser(Process process){
this.processes.remove(process);
process.getUsers().remove(this);
}
}
ProcessDTORequest.java (this class is on the backend accepting the request from the frontend)
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class ProcessDTORequest {
private UUID processId;
private Set<User> users = new HashSet<>();
private Set<AnsweredQuestionnaire> answeredQuestionnaires = new HashSet<>();
private Set<UnitType> units = new HashSet<>();
}
UnitType.java
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class UnitType {
private UUID unitTypeId;
private Unit unit;
private Boolean isResponsibleUnit = false;
}
Using Spring Boot I'd like to implement a mock service for an external API. As this mock is only used for testing, I'd like to keep things as simple as possible. The external API returns a JSON similar to this one:
{
"customer": {
"email": "foo#bar.com"
},
"result": {
"status_code": 100
},
"transaction": {
"amount": 100,
"currency": "EUR",
"order_no": "123456"
}
}
And the controller:
#Value("classpath:/sample.json")
private Resource sampleResource;
#GetMapping(value = "/api")
public String mockMethod() throws IOException {
final InputStream inputStream = sampleResource.getInputStream();
final String sampleResourceString = StreamUtils.copyToString(sampleResource, StandardCharsets.UTF_8);
return sampleResourceString;
}
So basically, the application loads a JSON string from a file and returns it in the response. Now I'd like to replace the amount and the order_no in the JSON with a dynamic value, like this:
{
"customer": {
"email": "foo#bar.com"
},
"result": {
"status_code": 100
},
"transaction": {
"amount": ${amount},
"currency": "EUR",
"order_no": "${orderNumber}"
}
}
My first idea was to use Thymeleaf for this, so I created the following configuration:
#Configuration
#RequiredArgsConstructor
public class TemplateConfiguration {
private final ApplicationContext applicationContext;
#Bean
public SpringResourceTemplateResolver templateResolver() {
final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/templates");
templateResolver.setSuffix(".json");
templateResolver.setTemplateMode(TemplateMode.TEXT);
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
}
But I'm stuck how I can actually "run" the templating so that the sampleResourceString is replaced with the dynamic values.
Or is Thymeleaf maybe actually some kind of "overkill" for this?
Maybe just use String.replace() ?
#GetMapping(value = "/api")
public String mockMethod() throws IOException {
final InputStream inputStream = sampleResource.getInputStream();
final String sampleResourceString = StreamUtils.copyToString(sampleResource, StandardCharsets.UTF_8);
String replacedString = sampleResourceString.replace("${amount}", ...)
.replace("${orderNumber}", ...);
return replacedString;
}
Considering that the JSON has quite a simple structure, I would map them out to classes and use Jackson. For example:
public class DummyResponse {
private DummyResponseCustomer customer;
private DummyResponseTransaction transaction;
private DummyResponseResult result;
// TODO: Getters + Setters + ...
}
public class DummyResponseTransaction {
private BigDecimal amount;
private String currency;
#JsonProperty("order_no")
private String orderNumber;
// TODO: Getters + Setters + ...
}
And then you could write a controller like this:
#ResponseBody // You need the #ResponseBody annotation or annotate the controller with #RestController
#GetMapping(value = "/api")
public DummyResponse mockMethod() {
return new DummyResponse(
new DummyResponseCustomer("foo#bar.com"),
new DummyResponseResult(100),
new DummyResponseTransaction(new BigDecimal("100"), "EUR", "123456")
);
}
This makes it fairly easy to come up with dynamic values and without having to use a templating engine (like Thymeleaf) or having to handle I/O. Besides that, you probably have these classes somewhere already to consume the external API.
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();
}
}
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"
}
}
}
I want to add an extra link to a entity such as:
"_links": {
"self": {
"href": "http://localhost:8080/api/organizaciones"
},
"profile": {
"href": "http://localhost:8080/api/profile/organizaciones"
},
"search": {
"href": "http://localhost:8080/api/organizaciones/search"
},
"disable": {
"href": "http://localhost:8080/api/organizaciones/disable"
}
}
The idea behind this scenario is that I need to expose a soft delete via its own link within Organizacion entity... right now I'm only able to do:
http://localhost:8080/api/organizaciones/search/disable?id=100
in order to perform the soft delete. How can I achieve this the right way? Or is it my only alternative creating a controller?
You just need to add a class extending the ResourceProcessor interface and add it to the spring-context(http://docs.spring.io/spring-data/rest/docs/current/reference/html/#_the_resourceprocessor_interface)
For example
#Bean
public ResourceProcessor<Resource<Person>> personProcessor() {
return new ResourceProcessor<Resource<Person>>() {
#Override
public Resource<Person> process(Resource<Person> resource) {
resource.add(new Link("http://localhost:8080/people", "added-link"));
return resource;
}
};
}
Where the Person entity can be replaced with your Organizacion entity.
I finally figured it out, I did what was mentioned by Alex in a comment.
I have to give credit to the father of spring-data-rest to #olivergieke I checked one of his examples, more precise: restbucks
First created the following component
#Component
#RequiredArgsConstructor
public class OrganizacionResourceProcessor implements ResourceProcessor<Resource<Organizacion>>{
private static final String DISABLE_REL = "deshabilitar";
private static final String ENABLE_REL = "habilitar";
private final #NonNull EntityLinks entityLinks;
#Override
public Resource<Organizacion> process(Resource<Organizacion> resource) {
Organizacion organizacion = resource.getContent();
if(organizacion.isEnabled()){
resource.add(entityLinks.linkForSingleResource(Organizacion.class, organizacion.getId()).slash(DISABLE_REL).withRel(DISABLE_REL));
}
if(organizacion.isDisabled()){
resource.add(entityLinks.linkForSingleResource(Organizacion.class, organizacion.getId()).slash(ENABLE_REL).withRel(ENABLE_REL));
}
return resource;
}
}
Then created the controller to support those two operations...
#RepositoryRestController
#RequestMapping("/organizaciones")
#ExposesResourceFor(Organizacion.class)
#RequiredArgsConstructor
#Slf4j
#Transactional
public class OrganizacionController {
private final #NonNull OrganizacionRepository organizacionRepository;
private final #NonNull EntityLinks entityLinks;
#GetMapping(value="/{id}/habilitar")
public ResponseEntity<?> desactivarOrganizacion(#PathVariable("id") Long id) {
Preconditions.checkNotNull(id);
Organizacion organizacion = organizacionRepository.findOne(id);
if(organizacion == null){
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
}
organizacion.setEstado(Estado.DESHABILITADO);
Organizacion pOrg = this.organizacionRepository.save(organizacion);
HttpHeaders header = new HttpHeaders();
header.setLocation(this.entityLinks.linkForSingleResource(Organizacion.class, pOrg.getId()).toUri());//construimos el URL
return new ResponseEntity<Void>(header,HttpStatus.CREATED);
}
#GetMapping(value="/{id}/deshabilitar")
public ResponseEntity<?> activarOrganizacion(#PathVariable("id") Long id){
Preconditions.checkNotNull(id);
Organizacion organizacion = organizacionRepository.findOne(id);
if(organizacion == null){
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
}
organizacion.setEstado(Estado.ACTIVO);
Organizacion pOrg = this.organizacionRepository.save(organizacion);
HttpHeaders header = new HttpHeaders();
header.setLocation(this.entityLinks.linkForSingleResource(Organizacion.class, pOrg.getId()).toUri());//construimos el URL
return new ResponseEntity<Void>(header,HttpStatus.CREATED);
}
}
and that was it.
This was originally added to Revision 3 of the question.