I want to validate a requestbody with #Valid annotation.
In the method there is a #RequestParam value.
If this #RequestParam value "min" I want that a field, in requestbody, is mandatory. It's possible with annotations?
#RequestMapping(method = RequestMethod.POST, value = ProductionResponse.URL, produces = JWSMessageConverter.JWS_MEDIA_TYPE_VALUE)
#ResponseBody
public ResponseEntity<ProductionResponse> richiestaProduzione(#RequestParam("issuerType") String issuerType,
#RequestParam("issuerCode") String issuerCode, #RequestParam("procedureId") String procedureId,
#Valid #RequestBody ProductionRequestResource requestBody) {
if (LOGGER.isDebugEnabled())
LOGGER.debug("POST " + ProductionResponse.URL);
#Valid ProductionRequestResource fff = requestBody;
requestBody.setProcedureResource(issuerType, issuerCode, procedureId);
try {
ProductionResponse response = produzioneService.richiestaProduzione(requestBody);
return ResponseEntity.ok(response);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
return ResponseEntity.badRequest().body(ProductionResponse.ko(requestBody, e.getMessage(), requestBody.getNun()));
}
}
I want that the field issuerMunicipalityCode in the ProductionRequestResource is mandatory if issuerType is equal to "MIN".
To continue further, here is how you can implement custom validation.
Add a validation to throw an exception when issueType is MIN and municipality code is empty/null.
#RequestMapping(method = RequestMethod.POST, value = ProductionResponse.URL, produces = JWSMessageConverter.JWS_MEDIA_TYPE_VALUE)
#ResponseBody
public ResponseEntity<ProductionResponse> richiestaProduzione(#RequestParam("issuerType") String issuerType,
#RequestParam("issuerCode") String issuerCode, #RequestParam("procedureId") String procedureId,
#RequestBody ProductionRequestResource requestBody) {
if (LOGGER.isDebugEnabled())
LOGGER.debug("POST " + ProductionResponse.URL);
if("MIN".equals(issuerType) && StringUtils.isEmpty(requestBody.getIssuerMunicipalityCode())) {
throw new IllegalArgumentException("IssuerMuncipalityCode can't be null when IssuerType is MIN");
}
try {
ProductionResponse response = produzioneService.richiestaProduzione(requestBody);
return ResponseEntity.ok(response);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
return ResponseEntity.badRequest().body(ProductionResponse.ko(requestBody, e.getMessage(), requestBody.getNun()));
}
}
Implement exception handler to catch the exceptions and build response body based on exception types. InvalidArgumentSuppliedException that was thrown above will be caught by below exception handler. Exception Handler is a best practice to handle exceptions in a separate component.
#ControllerAdvice
public class ScnSchedulerExceptionHandler {
#ExceptionHandler(InvalidArgumentSuppliedException.class)
public final ResponseEntity<Object> handleInvalidArgException(Exception ex) {
ErrorResponseDTO errorDTO = new ErrorResponseDTO();
errorDTO.setMessage(ex.getMessage());
return new ResponseEntity<>(errorDTO, HttpStatus.NOT_ACCEPTABLE);
}
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex) {
ErrorResponseDTO errorDTO = new ErrorResponseDTO();
errorDTO.setMessage("Internal server error occurred.");
return new ResponseEntity<>(errorDTO, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
In the exception handler, wrap the error messages in a pojo object.
public class ErrorResponseDTO {
private String errorMessage;
}
Related
I know how to replace placeholders with actual values in Spring, and things are working for me in my application but my question is specific to below usecase.
Is it even possible to have ${malformed.email.address} replaced by actual value from properties file?
#RequestMapping(value = "/forgot-password", method = RequestMethod.POST)
public ResponseEntity<String> forgotPassword(#RequestBody #Email(message = "${malformed.email.address}") #NotNull String emailAddress, UriComponentsBuilder ucBuilder) {
System.out.println("Forgot password for User : " + emailAddress);
try {
userService.forgotPassword(emailAddress);
return new ResponseEntity<String>("If " + emailAddress + " existed then you should receive an email soon.", HttpStatus.OK);
} catch (ServiceException e) {
throw new ControllerRuntimeException(e);
}
}
I have this configured :
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
and I have messages.properties file in the classpath and spring's Environment object is populated.
This is my Exception Handler :
#ControllerAdvice
public class ConstraintViolationExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleValidationException(ConstraintViolationException e) {
for (ConstraintViolation<?> s : e.getConstraintViolations()) {
return s.getInvalidValue() + ": " + s.getMessage();
}
return "Invalid request supplied";
}
}
This is what I get as a response :
WARN | 2019-02-05 00:21:16,622 |
org.hibernate.validator.internal.engine.messageinterpolation.ElTermResolver | HV000129: EL expression '${malformed.email.address}' references an unknown
property
javax.el.PropertyNotFoundException: ELResolver cannot handle a null base
Object with identifier [malformed]
at org.apache.el.parser.AstIdentifier.getValue(AstIdentifier.java:120)
at org.apache.el.parser.AstValue.getValue(AstValue.java:137)
at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:184)
at org.hibernate.validator.internal.engine.messageinterpolation.ElTermResolver.interpolate(ElTermResolver.java:65)
at org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm.interpolate(InterpolationTerm.java:64)
at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolate(ResourceBundleMessageInterpolator.java:75)
at org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolateExpression(AbstractMessageInterpolator.java:387)
at org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolateMessage(AbstractMessageInterpolator.java:344)
When building a rest api using spring boot what is the best way to handle exceptions from the service level and pass them to the controller, so the client gets a custom json error message.
{
"message": "some error"
}
Endpoint from controller
#PostMapping("/login")
public String login(#RequestBody #Valid LoginDto loginDto) {
return gson.toJson(userService.login(loginDto.getUsername(), loginDto.getPassword()));
}
Service level code
public LoginResponseDto login(String username, String password) {
try {
//performs some checks
...
return new LoginResponseDto(token.get());
} catch (AuthenticationException e){
LOGGER.info("Log in failed for user {}", username);
}
return new LoginResponseDto("login failed");
}
LoginResponseDto class
String token;
String message;
public LoginResponseDto(String message) {
this.message = message;
}
Currently it is obviously returning the correctly message but not the correct status code, it will show status 200 with the error message in json.
You have some options:
1) Returning a message:
If you want to return a message something like this,
{
"message": "some error"
}
What you can do is:
Option 1: Create a custom POJO class for error message and return the reference to the object of that POJO class.
Something like this:
ErrorMessage.java
package org.example;
public class ErrorMessage {
private String message;
public ErrorMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Request Handler Method in Controller:
#GetMapping("/login{?username, password}")
public ErrorMessage isUserAuthenticated(#RequestParam String username, #RequestParam String password) {
if (username.toLowerCase().contentEquals("root") && password.contentEquals("system")) {
return new ErrorMessage("authenticated");
}
return null;
}
Option 2: Create a Map and insert key-value pairs that you want to have in the message.
Like this:
#GetMapping("/login{?username, password}")
public Map<String, String> isUserAuthenticated(#RequestParam String username, #RequestParam String password) {
Map<String, String> message = new HashMap<>();
if (username.toLowerCase().contentEquals("root") && password.contentEquals("system")) {
message.put("message", "authenticated");
}
return message;
}
2) Returning an error status code (highly recommended by me):
You may use ResponseEntity for this purpose.
#GetMapping("/login{?username, password}")
public ResponseEntity<?> isUserAuthenticated(#RequestParam String username, #RequestParam String password) {
if (username.toLowerCase().contentEquals("root") && password.contentEquals("system")) {
return new ResponseEntity<>(HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
When using Spring Data REST and JSR 303 Bean Validation I get a response like the following when there's a constraint violation:
{
"errors": [
{
"entity": "Empresa",
"property": "observacao",
"invalidValue": "",
"message": "O tamanho do campo deve ser entre 1 e 255"
}
]
}
But I'm trying to validate an object manually, and I would like to return the validation errors in the same format used by Spring Data Rest.
#DeleteMapping("/departamento/{id}")
public #ResponseBody
ResponseEntity<?> filtro(#PathVariable Long id){
Optional<Departamento> departamentoOpt = this.departamentoRepository.findById(id);
if (!departamentoOpt.isPresent()) {
return ResponseEntity.notFound().build();
}
Departamento departamento = departamentoOpt.get();
BindingResult errors = new BeanPropertyBindingResult(
departamento, "departamento");
this.validator.validate(departamento, errors, PreDeleteValidation.class);
if (errors.hasErrors()) {
// How to return a response in the same format used by SDR here?
}
return ResponseEntity.ok().build();
}
How can this be accomplished?
You can throw and Exception on validation failure and register a Spring MVC Controller Advice to catch this and transform it to something that meets your needs.
if (errors.hasErrors()) {
throw new org.springframework.web.bind.MethodArgumentNotValidException(
departamento, bindingResult)
}
The advice could look something like the below:
#ControllerAdvice
public class ErrorHandlingAdvice
{
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationError processValidationError(MethodArgumentNotValidException ex)
{
ValidationError error = new ValidationError();
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError fieldError : fieldErrors)
{
error.addError(fieldError.getField(), fieldError.getDefaultMessage());
}
return error;
}
}
ValidationError is just a simple bean:
public class ValidationError
{
private final Map<String, List<String>> errors;
public ValidationError()
{
errors = new TreeMap<>();
}
public void addError(String field, String error)
{
if (!errors.containsKey(field))
{
errors.put(field, new ArrayList<String>());
}
errors.get(field).add(error);
}
public Map<String, List<String>> getErrors()
{
return errors;
}
}
I have a controller as
#Controller
#RequestMapping("/test")
public class TestController {
#RequestMapping("/1")
#ResponseBody
public String test1(){
Object o = null;
o.toString();
return "I ma test one!";
}
#RequestMapping("/2")
public String test2(){
Object o = null;
o.toString();
return "test";
}
}
Is it possible to create ControllerAdvice(s) to handle the controller method as different result without moving these to message to different classes.
I mean:
1. test1 returns a String message: if there is exception, handle it with handleError1 and return a message.
2. test1 returns a view : if there is exception, handle it with handleError2 and return/redirect to a view.
#ControllerAdvice
public class AdviceController {
#ExceptionHandler({ NullPointerException.class })
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public Map handleError1(IllegalStateException ex, HttpServletRequest request) {
Map map = new HashMap();
map.put("code","1000");
map.put("message","NullPointerException of Object");
return map;
}
#ExceptionHandler(NullPointerException.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public String handleError2(MultipartException e, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("message", e.getCause().getMessage());
redirectAttributes.addFlashAttribute("code", "1000");
return "redirect:/error";
}
}
if use
#ControllerAdvice(annotations=RestController.class)
#ControllerAdvice(annotations=Controller.class)
We need to create more controllers.
I know a lot of people have had issues similar to this.Sorry posting it again, but i believe there is something i might not be doing well.
I'm using Spring 3.0.5 with freemarker 2.3.14. Basically i wanted to show a friendly error message to the user.
#Controller("exceptioncontroller")
public class ExceptionController {
private static Logger logger = Logger.getLogger(ExceptionController.class);
#RequestMapping(value = "/site/contentnofoundexception")
public String throwContentFileNotFound(){
boolean exception = true;
if(exception){
throw new ContentFileNotFoundException("content ZZZ123 not found");
}
return "errortest";
}
#ExceptionHandler(value = ContentFileNotFoundException.class)
public String handleFileNotFoundException(ContentFileNotFoundException ex, Model model) {
model.addAttribute("msg",ex.getErrorMessage());//this message is never passed to the error view. msg is always null
return "error";
}
}
//same issue for handleException action which uses ModelAndView
#ExceptionHandler(value = Exception.class)
public ModelAndView handleException(Exception ex){
logger.error(ex);
ModelAndView mv = new ModelAndView();
mv.setViewName("error");
String message = "Something Broke. Please try again later";
mv.addObject("msg", message);
return mv;
}
// Custom Exception class
public class ContentFileNotFoundException extends RuntimeException {
private String errorMessage;
public ContentFileNotFoundException(String message) {
this.setErrorMessage(message);
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
So each case either handleFileNotFoundException or handleException actions are called alright but they can't send any message to the error.ftl view to display to the user. Is there anything i need to configure?
Thanks for helping in advance