Skip to content

Commit 0d5b7bc

Browse files
Adds PATCH methods for partial updates with Generics + Reflection.
I'm impressed with myself, honestly! Also updates the Insomnia_requests.json, README.md and index.html.
1 parent 3e8ece3 commit 0d5b7bc

File tree

10 files changed

+381
-88
lines changed

10 files changed

+381
-88
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ This project was bootstrapped with Spring Boot, so:
1212

1313
## Testing
1414

15-
There's a [Insomnia_requests.json](src/main/resources/static/Insomnia_requests.json) file. Import it into [Insomnia](https://insomnia.rest/download/) to test all endpoints (already with examples).
15+
There's a [Insomnia_requests.json](src/main/resources/static/Insomnia_requests.json) file. Import it into [Insomnia](https://insomnia.rest/download/) to test all endpoints (already with examples).
16+
17+
You may also just... [![Run in Insomnia}](https://insomnia.rest/images/run.svg)](https://insomnia.rest/run/?label=Drugo%20AlgafoodAPI&uri=https%3A%2F%2Falgafoodapi.herokuapp.com%2FInsomnia_requests.json)
1618

1719
You may use it against [https://algafoodapi.herokuapp.com/](https://algafoodapi.herokuapp.com/). Some endpoints you can try via browser (GET requests):
1820

src/main/java/dev/drugowick/algaworks/algafoodapi/api/controller/CityController.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.drugowick.algaworks.algafoodapi.api.controller;
22

3+
import dev.drugowick.algaworks.algafoodapi.api.controller.utils.ObjectMerger;
34
import dev.drugowick.algaworks.algafoodapi.domain.exception.EntityBeingUsedException;
45
import dev.drugowick.algaworks.algafoodapi.domain.exception.EntityNotFoundException;
56
import dev.drugowick.algaworks.algafoodapi.domain.model.City;
@@ -11,6 +12,7 @@
1112
import org.springframework.web.bind.annotation.*;
1213

1314
import java.util.List;
15+
import java.util.Map;
1416

1517
@RestController
1618
@RequestMapping("cities")
@@ -24,10 +26,12 @@ public class CityController {
2426

2527
private CityRepository cityRepository;
2628
private CityCrudService cityCrudService;
29+
private ObjectMerger<City> objectMerger;
2730

28-
public CityController(CityRepository cityRepository, CityCrudService cityCrudService) {
31+
public CityController(CityRepository cityRepository, CityCrudService cityCrudService, ObjectMerger<City> objectMerger) {
2932
this.cityRepository = cityRepository;
3033
this.cityCrudService = cityCrudService;
34+
this.objectMerger = objectMerger;
3135
}
3236

3337
@GetMapping
@@ -86,6 +90,19 @@ public ResponseEntity<?> update(@PathVariable Long id, @RequestBody City city) {
8690

8791
}
8892

93+
@PatchMapping("/{id}")
94+
public ResponseEntity<?> partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> cityMap) {
95+
City cityToUpdate = cityRepository.get(id);
96+
97+
if (cityToUpdate == null) {
98+
return ResponseEntity.notFound().build();
99+
}
100+
101+
cityToUpdate = objectMerger.mergeRequestBodyToGenericObject(cityMap, cityToUpdate);
102+
103+
return update(id, cityToUpdate);
104+
}
105+
89106
@DeleteMapping("/{id}")
90107
public ResponseEntity<?> delete(@PathVariable Long id) {
91108
try {

src/main/java/dev/drugowick/algaworks/algafoodapi/api/controller/CuisineController.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.drugowick.algaworks.algafoodapi.api.controller;
22

3+
import dev.drugowick.algaworks.algafoodapi.api.controller.utils.ObjectMerger;
34
import dev.drugowick.algaworks.algafoodapi.domain.exception.EntityBeingUsedException;
45
import dev.drugowick.algaworks.algafoodapi.domain.exception.EntityNotFoundException;
56
import dev.drugowick.algaworks.algafoodapi.domain.model.Cuisine;
@@ -11,6 +12,7 @@
1112
import org.springframework.web.bind.annotation.*;
1213

1314
import java.util.List;
15+
import java.util.Map;
1416

1517
@RestController
1618
@RequestMapping(value = "/cuisines")
@@ -24,10 +26,12 @@ public class CuisineController {
2426

2527
private CuisineRepository cuisineRepository;
2628
private CuisineCrudService cuisinesCrudService;
29+
private ObjectMerger<Cuisine> objectMerger;
2730

28-
public CuisineController(CuisineRepository cuisineRepository, CuisineCrudService cuisinesCrudService) {
31+
public CuisineController(CuisineRepository cuisineRepository, CuisineCrudService cuisinesCrudService, ObjectMerger<Cuisine> objectMerger) {
2932
this.cuisineRepository = cuisineRepository;
3033
this.cuisinesCrudService = cuisinesCrudService;
34+
this.objectMerger = objectMerger;
3135
}
3236

3337
@GetMapping
@@ -60,22 +64,35 @@ public ResponseEntity<Cuisine> get(@PathVariable Long id) {
6064
@PutMapping(value = "/{id}")
6165
public ResponseEntity<Cuisine> update(@PathVariable Long id, @RequestBody Cuisine cuisine) {
6266
Cuisine cuisineToUpdate = cuisineRepository.get(id);
63-
67+
6468
if (cuisineToUpdate != null) {
6569
BeanUtils.copyProperties(cuisine, cuisineToUpdate, "id");
6670
cuisineToUpdate = cuisinesCrudService.update(id, cuisineToUpdate);
6771
return ResponseEntity.ok(cuisineToUpdate);
6872
}
69-
73+
7074
return ResponseEntity.notFound().build();
7175
}
72-
76+
77+
@PatchMapping("/{id}")
78+
public ResponseEntity<?> partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> cuisineMap) {
79+
Cuisine cuisineToUpdate = cuisineRepository.get(id);
80+
81+
if (cuisineToUpdate == null) {
82+
return ResponseEntity.notFound().build();
83+
}
84+
85+
cuisineToUpdate = objectMerger.mergeRequestBodyToGenericObject(cuisineMap, cuisineToUpdate);
86+
87+
return update(id, cuisineToUpdate);
88+
}
89+
7390
@DeleteMapping(value = "/{id}")
7491
public ResponseEntity<Cuisine> delete(@PathVariable Long id) {
7592
try {
7693
cuisinesCrudService.delete(id);
7794
return ResponseEntity.noContent().build();
78-
95+
7996
} catch (EntityBeingUsedException e) {
8097
return ResponseEntity.status(HttpStatus.CONFLICT).build();
8198

src/main/java/dev/drugowick/algaworks/algafoodapi/api/controller/ProvinceController.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.drugowick.algaworks.algafoodapi.api.controller;
22

3+
import dev.drugowick.algaworks.algafoodapi.api.controller.utils.ObjectMerger;
34
import dev.drugowick.algaworks.algafoodapi.domain.exception.EntityBeingUsedException;
45
import dev.drugowick.algaworks.algafoodapi.domain.exception.EntityNotFoundException;
56
import dev.drugowick.algaworks.algafoodapi.domain.model.Province;
@@ -11,6 +12,7 @@
1112
import org.springframework.web.bind.annotation.*;
1213

1314
import java.util.List;
15+
import java.util.Map;
1416

1517
@RestController
1618
@RequestMapping(value = "/provinces")
@@ -24,10 +26,12 @@ public class ProvinceController {
2426

2527
private ProvinceRepository provinceRepository;
2628
private ProvinceCrudService provinceCrudService;
29+
private ObjectMerger<Province> objectMerger;
2730

28-
public ProvinceController(ProvinceRepository provinceRepository, ProvinceCrudService provinceCrudService) {
31+
public ProvinceController(ProvinceRepository provinceRepository, ProvinceCrudService provinceCrudService, ObjectMerger<Province> objectMerger) {
2932
this.provinceRepository = provinceRepository;
3033
this.provinceCrudService = provinceCrudService;
34+
this.objectMerger = objectMerger;
3135
}
3236

3337
@GetMapping
@@ -76,6 +80,19 @@ public ResponseEntity<Province> update(@PathVariable Long id, @RequestBody Provi
7680
return ResponseEntity.ok(provinceToUpdate);
7781
}
7882

83+
@PatchMapping("/{id}")
84+
public ResponseEntity<?> partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> provinceMap) {
85+
Province provinceToUpdate = provinceRepository.get(id);
86+
87+
if (provinceToUpdate == null) {
88+
return ResponseEntity.notFound().build();
89+
}
90+
91+
provinceToUpdate = objectMerger.mergeRequestBodyToGenericObject(provinceMap, provinceToUpdate);
92+
93+
return update(id, provinceToUpdate);
94+
}
95+
7996
@DeleteMapping(value = "/{id}")
8097
public ResponseEntity<?> delete(@PathVariable Long id) {
8198
try {

src/main/java/dev/drugowick/algaworks/algafoodapi/api/controller/RestaurantController.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.drugowick.algaworks.algafoodapi.api.controller;
22

3+
import dev.drugowick.algaworks.algafoodapi.api.controller.utils.ObjectMerger;
34
import dev.drugowick.algaworks.algafoodapi.domain.exception.EntityBeingUsedException;
45
import dev.drugowick.algaworks.algafoodapi.domain.exception.EntityNotFoundException;
56
import dev.drugowick.algaworks.algafoodapi.domain.model.Restaurant;
@@ -10,15 +11,18 @@
1011
import org.springframework.web.bind.annotation.*;
1112

1213
import java.util.List;
14+
import java.util.Map;
1315

1416
@RestController
1517
@RequestMapping("/restaurants")
1618
public class RestaurantController {
17-
19+
1820
private RestaurantCrudService restaurantCrudService;
21+
private ObjectMerger<Restaurant> objectMerger;
1922

20-
public RestaurantController(RestaurantCrudService restaurantCrudService) {
23+
public RestaurantController(RestaurantCrudService restaurantCrudService, ObjectMerger<Restaurant> objectMerger) {
2124
this.restaurantCrudService = restaurantCrudService;
25+
this.objectMerger = objectMerger;
2226
}
2327

2428
@GetMapping
@@ -73,6 +77,19 @@ public ResponseEntity<?> update(@PathVariable Long id, @RequestBody Restaurant r
7377
}
7478
}
7579

80+
@PatchMapping("/{id}")
81+
public ResponseEntity<?> partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> restaurantMap) {
82+
Restaurant restaurantToUpdate = restaurantCrudService.read(id);
83+
84+
if (restaurantToUpdate == null) {
85+
return ResponseEntity.notFound().build();
86+
}
87+
88+
restaurantToUpdate = objectMerger.mergeRequestBodyToGenericObject(restaurantMap, restaurantToUpdate);
89+
90+
return update(id, restaurantToUpdate);
91+
}
92+
7693
@DeleteMapping("/{id}")
7794
public ResponseEntity<?> delete(@PathVariable Long id) {
7895
try {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package dev.drugowick.algaworks.algafoodapi.api.controller.utils;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import org.springframework.util.ReflectionUtils;
5+
6+
import java.lang.reflect.Field;
7+
import java.util.Map;
8+
9+
/**
10+
* Helper method to merge the request body (a Map<String, Object>) to an Object to be updated via a Patch HTTP request.
11+
* <p>
12+
* Uses ReflectionUtils from Spring Framework to set values to the Object.
13+
*
14+
* @param <T>
15+
*/
16+
public class ObjectMerger<T> {
17+
18+
private ObjectMapper objectMapper;
19+
private Class<T> type;
20+
21+
public ObjectMerger(Class<T> type) {
22+
this.objectMapper = new ObjectMapper();
23+
this.type = type;
24+
}
25+
26+
public T mergeRequestBodyToGenericObject(Map<String, Object> objectMap, T objectToUpdate) {
27+
T newObject = objectMapper.convertValue(objectMap, type);
28+
29+
objectMap.forEach((fieldProp, valueProp) -> {
30+
Field field = ReflectionUtils.findField(type, fieldProp);
31+
field.setAccessible(true);
32+
33+
Object newValue = ReflectionUtils.getField(field, newObject);
34+
35+
ReflectionUtils.setField(field, objectToUpdate, newValue);
36+
});
37+
38+
return objectToUpdate;
39+
}
40+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dev.drugowick.algaworks.algafoodapi.config;
2+
3+
import dev.drugowick.algaworks.algafoodapi.api.controller.utils.ObjectMerger;
4+
import dev.drugowick.algaworks.algafoodapi.domain.model.City;
5+
import dev.drugowick.algaworks.algafoodapi.domain.model.Cuisine;
6+
import dev.drugowick.algaworks.algafoodapi.domain.model.Province;
7+
import dev.drugowick.algaworks.algafoodapi.domain.model.Restaurant;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
11+
/**
12+
* Confiures the ObjectMerger objects to instantiate as Beans.
13+
* <p>
14+
* These Beans will be used on Controllers for object transformation from Map<String, Object> to <T> object.
15+
* This is especially useful for PATCH HTTP requests.
16+
*/
17+
@Configuration
18+
public class ObjectMergerConfig {
19+
20+
@Bean
21+
public ObjectMerger<Restaurant> restaurantObjectMerger() {
22+
return new ObjectMerger<>(Restaurant.class);
23+
}
24+
25+
@Bean
26+
public ObjectMerger<Cuisine> cuisineObjectMerger() {
27+
return new ObjectMerger<>(Cuisine.class);
28+
}
29+
30+
@Bean
31+
public ObjectMerger<City> cityObjectMerger() {
32+
return new ObjectMerger<>(City.class);
33+
}
34+
35+
@Bean
36+
public ObjectMerger<Province> provinceObjectMerger() {
37+
return new ObjectMerger<>(Province.class);
38+
}
39+
}

0 commit comments

Comments
 (0)