Override standart Spring Data REST API - java

I have one Entity User in my app. Spring Data REST provides me standard endpoints:
`GET` /user
`GET` /user/<id>
`POST` /user
`PUT` /user
`PATCH` /user
`DELETE` /user/<id>
I need to override default behaviour of DELETE endpoint not changing endpoint url /user. If I add following to my controller:
#Controller
#RequestMapping("/user")
public class User {
#DeleteMapping("/{id}")
#CrossOrigin
public ResponseEntity<?> delete(#PathVariable("id") final String id) {
userService.delete(id); // in service I remove user with other
return ResponseEntity.ok().build();
}
// other custom endpoints
}
I found that other standard REST endpoints do not work - I always receive 405 error. So, my question is - how to customize this endpoint and not affect other endpoints? (I know how to do this in #RepositoryEventHandler - but I should avoid this in my case)

Did you read this: Overriding Spring Data REST Response Handlers?
#RepositoryRestController
#RequestMapping("/users") // or 'user'? - check this...
public class UserController {
#Autoware
private UserRepo userRepo;
#Transactional
#DeleteMapping("/{id}")
public ResponseEntity<?> delete(#PathVariable("id") String id) { // or Long id?..
// custom logic
return ResponseEntity.noContent().build();
}
}
But if you want to add extra business logic to delete process you even don't need to implement a custom controller, you can use a custom event handler:
#Component
#RepositoryEventHandler(User.class)
public class UserEventHandler {
#Autoware
private UserRepo userRepo;
#BeforeDeleteEvent
public void beforeDelete(User u) {
//...
if (/* smth. wrong */) throw new MyException(...);
}
}

Related

spring rest #RequestBody does not validate with #Valid

I'm learning java and spring boot and I am trying to validate a controller parameter which was bound from json.
I've got simple Entity:
#Getter
#Setter
class Resource {
#NotBlank
String uri;
}
Which I want to persist through the following controller:
#BasePathAwareController
public class JavaResourcePostController {
private final ResourceRepository repository;
public JavaResourcePostController(ResourceRepository repository) {
this.repository = repository;
}
#RequestMapping(value = "/resources", method = RequestMethod.POST)
ResponseEntity<Resource> create(
#Valid #RequestBody Resource resource
) {
repository.save(resource);
return ResponseEntity.ok(resource);
}
}
My understanding is that the resource argument should be valid when entering the method. But posting an empty uri field does not trigger the validation of the method. it does however get triggered in the hibernate validation in repository.save()
Why does the #Valid annotation on the argument not ensure I get a validated entity?
You need to add #Validated to your controller class.

How can I override a Spring Data REST method without disabling default implementations

I finally found a way to override methods of Spring Data REST with a custom implementation. Unfortunately this disables the default handling.
My Repository should contain findAll and findById exposed over the GET: /games and GET: /games/{id} respectively and save should not be exported because it is overriden by the controller.
#RepositoryRestResource(path = "games", exported = true)
public interface GameRepository extends Repository<Game, UUID> {
Collection<Game> findAll();
Game findById(UUID id);
#RestResource(exported = false)
Game save(Game game);
}
My controller should handle POST: /games, generate the game on the server and return the saved Game.
#RepositoryRestController
#ExposesResourceFor(Game.class)
#RequestMapping("games")
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces = "application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
However when I try to GET: /games it returns 405: Method Not Allowed but POST: /games works as intended. When I change the value of the generateNewGame mapping to "new" all three requests work. But POST: /games/new is no RESTful URL Layout and I would rather avoid it. I don't understand why I get this behaviour and how I may solve it. Does anybody have a clue?
Use #BasePathAwareControllerannotation above your controller to preserve default spring data rest paths and add new custom path base on your need. Although overwrite default spring data rest path.
#BasePathAwareController
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces =
"application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO
createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
Maybe you can do something we usually do in Linux. Set a fake path and link to it.
POST /games ==> [filter] request.uri.euqal("/games") && request.method==POST
==> Redirect /new/games
What you see also is /games.
Don't use /games/new, it may be conflict with things inner Spring.

Spring rest vs standard Rest?

In spring doc, I can get following explanations for the difference of the spring mvc and spring rest.
Spring REST architecture is also based on Spring MVC, slightly making the difference on the View part. Traditional Spring MVC relies on the View technology to render the model data, the Spring REST architecture also does the same, except that the model object is set directly into the HTTP response, which the #ResponseBody converts into JSON/XML automatically. The output of a RESTful web service has to be a JSON or an XML, a standard format that could be easily handled across different consumer application platforms.
But in https://en.wikipedia.org/wiki/Representational_state_transfer.
It has a couple of feactures except for the json response like the rest will use the HTTP PUT/DELETE/POST method to manipulate resource.
I was wondering if below spring controller can be treated as a restful service. I have used #RestController to return json response, but did not use any other rest features.
#RestController
#RequestMapping(value = "/employee")
public class EmployeeController {
#RequestMapping(value = RequestAction.LOADLIST, method = RequestMethod.POST)
public List<Employee> list(#RequestBody Employee bo) {
System.out.println(bo);
return employeeList;
}
#RequestMapping(value = RequestAction.LOAD, method = RequestMethod.POST)
public Employee getEmployee(
#RequestBody Employee input) {
for (Employee employee : employeeList) {
if (employee.getId().equals(input.getId())) {
return employee;
}
}
return input;
}
#RequestMapping(value = RequestAction.ADD, method = RequestMethod.POST)
public Employee addEmployee(#RequestBody Employee bo) {
System.out.println(bo);
return bo;
}
#RequestMapping(value = RequestAction.UPDATE, method = RequestMethod.POST)
public Employee updateEmployee(#RequestBody Employee bo) {
System.out.println(bo);
for (Employee employee : employeeList) {
if (employee.getId().equals(bo.getId())) {
employee.setName(bo.getName());
return employee;
}
}
return bo;
}
}
Your example script is not REST because it change the url for each task, and use always POST verb. Spring REST use different HTTP verbs (GET, POST, DELETE) to differentiate the action. A few times sharing the same url.
Example:
#RestController
#RequestMapping("/users")
public class UsersController {
#GetMapping
public List<User> index() {...}
#GetMapping("{id}")
public User show(...) {...}
#PostMapping
public User create(...) {...}
#PutMapping("{id}")
public User update(...) {...}
#DeleteMapping("{id}")
public void delete(...) {...}
}
Your example is not following the conventionals of a REST API (e.g. GET for retrieval, POST for create, PUT for full update, PATCH for partial update, etc.), but it does not mean, that you can't. As others stated above, you might just got confused with the term. REST is a protocol and it has lots of conventionals for service usages, which if you follow, you can say that your service is REST or RESTful.
This page is the simple best source of tutoring you in this area:
https://restfulapi.net
More importantly this, when we are considering your example: https://restfulapi.net/http-methods/
I also check it sometimes.

Adding REST Web Service annotation in API class

I am using Spring Boot (1.5.3) to create a Spring REST Web Service. I have added spring-boot-starter-web as the only dependency (as per spring guide). Next I have created UserManagementService interface for my service class.
#RequestMapping("/usermanagement/v1")
public interface UserManagementService {
#RequestMapping(value = "/user/{id}/", method=RequestMethod.GET)
public UserTo getUserById(#PathVariable("id") long id);
#RequestMapping(value = "/users/", method=RequestMethod.GET)
public List<UserTo> getAllUsers();
}
And its implementation UserManagementServiceImpl
#RestController
public class UserManagementServiceImpl implements UserManagementService {
private Map<Integer, UserTo> users;
public UserManagementServiceImpl() {
users = new HashMap<>();
users.put(1, new UserTo(1, "Smantha Barnes"));
users.put(2, new UserTo(2, "Adam Bukowski"));
users.put(3, new UserTo(3, "Meera Nair"));
}
public UserTo getUserById(long id) {
return users.get(id);
}
public List<UserTo> getAllUsers() {
List<UserTo> usersList = new ArrayList<UserTo>(users.values());
return usersList;
}
}
I wanted to created a REST Web Service using Spring Boot with minimum configuration and thought this would work. But on accessing my the Web Service I am getting No Response. What I am missing?
Also, I have seen many projects where annotations are added to the interface rather than the implementation class. Which I think is better than annotating classes. It should work here, right?
As mentioned in the comments, not all annotations are supported on interfaces. The #PathVariable annotation for example won't work, so you'll have to put that on the implementation itself:
public UserTo getUserById(#PathVariable("id") long id) {
return users.get(id);
}
Additionally to that, you have a Map<Integer, UserTo>, but you're retrieving the users using a #PathVariable of type long. This won't work either, so either change the key of users to Long or the id parameter to int:
public UserTo getUserById(#PathVariable("id") int id) {
return users.get(id);
}
The reason for this is that 1L (long) is not the same as 1 (int). So retrieving a map entry wouldn't return any result for a long value.

How should I validate dependent parameters in #requestparam in spring Rest API?

I know there are validators in spring. However, these validators can only be bound to a single object. Say a Pojo in request body. However, I have a scenario where I have a get request and I want to validate a date range: I have a start date and the end date as #requestparams. How should I validate these?
Also there is a validator applied for the same #restcontroller: for post request, say Employeevalidtor. Can I invoke multiple validators for different objects in the same #restcontroller?
You can use separate validators but they have to me manually instantiated by passing the corresponding objects to be validated.
I assume you are talking about request binding validations. The same validations can be obtained with Spring Validators for #RequestParam and #PathVariables as mentioned in this post
Adding the relevant piece here. The controller will look something like this:
#RestController
#Validated
public class RegistrationController {
#RequestMapping(method = RequestMethod.GET,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
#ResponseStatus(HttpStatus.OK)
public Map search(#Email #RequestParam("email") String email) {
return emailMessage(email);
}
}
Note the #Validated method at the class level (which can also be declared at the method level).
Let Spring MVC will map your request parameters to a pojo encapsulating all the related inputs and then add a validator for that.
#RestController
#RequestMapping("/myUrl")
public class MytController {
private final MyIntervalValidator validator;
#InitBinder
public void initBinder(WebDataBinder binder){
binder.setValidator(validator);
}
#GetMapping
public void doSomthing(#Valid #RequestParam MyInterval interval){...}
class MyInterval implements Serializable{
private Date startDate;
private Date endDate;
}
import org.springframework.validation.Validator;
class MyIntervalValidator implements Validator{
#Override
public boolean supports(Class<?> clazz) {
return MyInterval.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
final MyInterval params = (MyInterval) target;
....
}
}

Categories

Resources