While Doing Request Validation in Springboot, Response is not as expected - java

I am trying to validate the json-resquest using hibernate-validator, it is working as expected but response is not there in postman.
Customer.java
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "cin", "firstName"})
public class Customer {
#JsonProperty("cin")
private String cin;
#JsonProperty("firstName")
#NotEmpty(message = "First Name must have some values")
#Size(min = 2, message = "First Name must greater or equal to 2 characters")
private String firstName;
//getters and setters
}
and Errors class - to wrap error in one object.
public class Errors {
private Integer status;
private String message;
private List<String> details;
public Errors(Integer status, String message, List<String> details) {
super();
this.status = status;
this.message = message;
this.details = details;
}
// Getters and Setters
}
ControllerAdvice class
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.ecommerce.ms.customer.model.Errors;
#ControllerAdvice
#ResponseBody
public class CustomerExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value=ConstraintViolationException.class)
public final ResponseEntity<Errors> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
List<String> details = ex.getConstraintViolations().parallelStream().map(e -> e.getMessage())
.collect(Collectors.toList());
Errors error = new Errors(HttpStatus.BAD_REQUEST.value(), "Request Validation Error", details);
return ResponseEntity.badRequest().body(error);
}
}
CustomerController.java
*
*/
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
import javax.ws.rs.core.MediaType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ecommerce.ms.customer.api.service.CustomerService;
import com.ecommerce.ms.customer.model.Customer;
#RestController
#RequestMapping("/api/customers")
public class CustomerController {
#Autowired
private CustomerService customerService;
#GetMapping("/status")
public String getStatus() {
return "ok";
}
#PostMapping(consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public ResponseEntity<Customer> addCustomer(#Valid #RequestBody Customer customer) {
return ResponseEntity.accepted().body(customerService.addCustomer(customer));
}
}
Hibernate-validator is already added pom.xml and I am expecting the below reason.
{
"status":400,
"message": "Request Validation Error",
"details":["First Name must greater or equal to 2 characters"]
}
I am trying to to get proper response body but I couldn't find it in postman.

Looking at the ResponseEntityExceptionHandler there is no such method that handles ConstraintValidationExceptions, and therefore the custom method you have created is not being called.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.html
As well:
You cannot catch ConstraintViolationException.class because it's not
propagated to that layer of your code, it's caught by the lower
layers, wrapped and rethrown under another type. So that the exception
that hits your web layer is not a ConstraintViolationException.
Ref: SpringBoot doesn't handle org.hibernate.exception.ConstraintViolationException
An example of proper usage is to use the method handleMethodArgumentNotValid and return the Errors as body:
#RestControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler{
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, Object> responseBody = new LinkedHashMap<>();
List<String> allErrors = new ArrayList<>();
ex.getBindingResult().getAllErrors().forEach(error -> allErrors.add(error.getDefaultMessage()));
responseBody.put("Errors:", allErrors);
return new ResponseEntity<>(responseBody, headers, status);
}
}

Related

Can't use #Valid annotation on #RequestParam from multipart form after converting string to JSON

Basically, I have implemented this converter, to allow me to send JSON data as a "data" field alongside a file upload.
import javax.validation.Valid;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import ****.DatasetUploadDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.validation.ObjectError;
import lombok.SneakyThrows;
#Component
public class StringToDatasetUploadDtoConverter implements Converter<String,
DatasetUploadDTO> {
#Autowired
private ObjectMapper objectMapper;
#Override
#SneakyThrows
#JsonIgnoreProperties(ignoreUnknown=true)
#Valid
public DatasetUploadDTO convert(String source) {
return objectMapper.readValue(source, DatasetUploadDTO.class);
}
this is my dto class:
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;
import org.springframework.web.multipart.MultipartFile;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import ****.models.ExtraMetaData;
#Data
#RequiredArgsConstructor
public class DatasetUploadDTO {
#Length(max = 0, message = "Id modification not permitted in this context.")
private String id;
#NotBlank(message = "Description is mandatory") #Length(min=3)
private String description;
private String authorId;
#NotNull
#NotBlank(message = "ExperimentId is mandatory") #Length(min=3)
private String experimentId;
private String type;
#NotNull
private Boolean isPublic;
#NotNull
private Boolean isMetaData;
#NotNull
private List<String> userPermission;
}
In my controller, I can successfully use this converter, and save to DB etc. with no problems. However, if I then add the #Valid annotation to attempt to validate according to this schema, it doesn't actually do anything:
#RequestMapping(path = "/dataset", method = RequestMethod.POST, consumes = {"multipart/form-data"})
ResponseEntity<?> postExperiment(#AuthenticationPrincipal UserDetailsImpl jwt , #RequestParam("data") /* Here nothing happens -> */ #Valid DatasetUploadDTO uploadDTO, #RequestParam("file") MultipartFile file) {
....
}
What can I do to achieve validation of this JSON field? If all else fails, is there some way I can implement validation within the body of the function?

Spring boot with Mongo db rest Api

I have created a crud application Using spring boot initializer.
Dependencies:
Lombok
Spring Web
Spring Mongo
This app calls from a database/cluster that I have set up on atlas. but I want it to call the correct collection and just do a simple get all api call in postman
but I get a server 500 error
Service Java file:
package com.fullstack.app.Service;
import com.fullstack.app.exception.EntityNotFoundException;
import com.fullstack.app.Model.*;
import com.fullstack.app.Model.Request.WCCreationRequest;
import com.fullstack.app.Repository.StatusData_WCRepo;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
#Service
#RequiredArgsConstructor
public class StatusDataService {
private static StatusData_WCRepo wcRepository;
public StatusData createData (WCCreationRequest request) {
StatusData statusData = new StatusData();
BeanUtils.copyProperties(request, statusData);
return wcRepository.save(statusData);
}
public static List<StatusData> getAllData() {
return wcRepository.findAll();
}
}
request:
package com.fullstack.app.Model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
#Document(collection = "StatusData_WC")
public class StatusData {
#Id
private String ID_Number;
private String Surname;
private String Full_Names;
private String Address;
private String VR;
private Integer Ward;
private Integer VD_Number;
}
Controller:
package com.fullstack.app.Controller;
import com.fullstack.app.Model.StatusData;
import com.fullstack.app.Model.Request.WCCreationRequest;
import com.fullstack.app.Service.StatusDataService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import lombok.RequiredArgsConstructor;
#RestController
#RequestMapping(value = "/api/statusData")
#RequiredArgsConstructor
public class StatusDataController {
private final StatusDataService sdService;
#GetMapping("/statusdata")
public ResponseEntity getAllData(#RequestParam(required = false) String id) {
if (id == null) {
return ResponseEntity.ok(StatusDataService.getAllData());
}
return ResponseEntity.ok(StatusDataService.getAllData());
}
}
Application properties:
spring.data.mongodb.uri=mongodb+srv://*****:******#cluster0.wlmmf.mongodb.net/myFirstDatabase?retryWrites=true&w=majority

how to properly create a rest method using dto

I need to send a request to a third-party service, then get an object from the response and display it on the browser.
package com.statusinfonew.springboot.controller;
import java.util.Collections;
import java.util.List;
import lombok.Data;
import lombok.val;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.statusinfonew.springboot.model.ParamsPays;
import com.statusinfonew.springboot.service.dto.OpenJsonFormat;
#Controller
#RestController
public class MyRestController {
RestTemplate restTemlate;
#GetMapping({"/"})
public String showGeneralPage(Model model) {
model.addAttribute("general", "Welcome to App");
return "hello";
}
#GetMapping(path = "/go")
public List<ParamsPays> getInfoError(#RequestParam(value="token") String token,
#RequestParam(value="orderId")String orderId){
final String url =String.format("https://exemple.ru/payment/rest/getOrderStatus.do?
token=%s&orderId=%s", token, orderId);
OpenJsonFormat dto =restTemlate.getForObject(url, OpenJsonFormat.class);
return Collections.singletonList(toModel(dto));
}
private ParamsPays toModel(OpenJsonFormat dto) {
return new ParamsPays();
}
package com.statusinfonew.springboot.service.dto;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
#Data
public class OpenJsonFormat {
#JsonProperty("actionCode")
private String actionCode;
#JsonProperty("amount")
private String amount;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
private Date date;
}
When I enter a get request, an error is issued:
This application has no explicit mapping for /error, so you are seeing this as a fallback.
http://localhost:8080/go?token=sdwggvpa0k4ponpkt9&orderId=6afsf0-a0bc-7ffd-a9a8-790d4s179
Wed Apr 21 21:26:35 SAMT 2021
There was an unexpected error (type=Internal Server Error, status=500).
I understand that I am making a gross mistake. Please help me figure it out

Spring Rest Controller

I can't make spring serialize the response when results is array/list .
So when I call clients from RestController it does return [{},{},{}], instead of real objects, all other methods works just fine.
package com.test.Domain.Client;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.UUID;
#Entity
#Table(name = "client")
public class Client {
#Column(name = "client_id")
#Id
private UUID clientId;
#Column(name = "name")
private String name;
private Client() {
}
private Client(UUID clientId, String name) {
this.clientId = clientId;
this.name = name;
}
public static Client create(String name)
{
return new Client(UUID.randomUUID(), name);
}
}
package com.test.Rest;
import com.test.Domain.Calendar.AppointmentRepository;
import com.test.Domain.Client.Client;
import com.test.Domain.Client.ClientRepository;
import com.test.Domain.Worker.WorkerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
#org.springframework.web.bind.annotation.RestController
public class RestController {
#Autowired
private ClientRepository clientRepository;
#Autowired
private WorkerRepository workerRepository;
#Autowired
private AppointmentRepository appointmentRepository;
#RequestMapping(path = "/client", method = RequestMethod.POST)
public void registerClient(#RequestParam(name = "name") String name) {
this.clientRepository.save(Client.create(name));
}
#RequestMapping(path = "/clientCount", method = RequestMethod.GET)
public Long countClient() {
return this.clientRepository.count();
}
#RequestMapping(path = "/clients", method = RequestMethod.GET)
#ResponseBody
public List<Client> clients() {
List<Client> list = new ArrayList<Client>();
for (Client client : this.clientRepository.findAll()) {
list.add(client);
}
return list;
}
}
Jackson needs Getter and Setter methods in order to serialize the Client object properly into JSON. Therefore a list of empty objects is returned and the values for the members are missing. Add them to Client and the response should look fine.
Spring applies first registered applicable by response mime-type HttpMessageConverter implementation when serializing the response to /clients call. In your case this is some JSON serializer. As you have no JSON configuration specified on Client class the default POJO serializing approach is used: reflection scanning of object properties. As mentioned earlier your Client class doesn't define any properties (at least getters), so serializer do not detect any.
Please refer to the following article for a more detailed explanation: https://www.javacodegeeks.com/2013/07/spring-mvc-requestbody-and-responsebody-demystified.html
P.S. Marking method with #ResponseBody in #RestController annotated class is not necessary as itself is a convenience annotation aggregating #Controller and #ResponseBody.

Spring StandardMultipartHttpServletRequest validation

Is there any possibility to validate StandardMultipartHttpServletRequest using standard #Valid annotation and custom Validator?
I've implemented such validator, annotated method param in controller the validator is not invoked.
I've figured it out myself. To make it work you need a DTO:
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
#Getter
#Setter
public class NewOrderFilesDTO {
List<MultipartFile> files;
}
Then, a validator:
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import static org.springframework.util.CollectionUtils.isEmpty;
#Component
public class NewOrderFilesValidator implements Validator {
private static final String MIME_TYPE_PDF = "application/pdf";
private static final long ALLOWED_SIZE = 3 * 1024 * 1024;
#Override
public void validate(Object target, Errors errors) {
if (target == null) {
return;
}
NewOrderFilesDTO newOrderFilesDTO = (NewOrderFilesDTO) target;
List<MultipartFile> newOrderFiles = newOrderFilesDTO.getFiles();
if (isEmpty(newOrderFiles)) {
return;
}
for (MultipartFile file : newOrderFiles) {
if (!MIME_TYPE_PDF.equals(file.getContentType())) {
errors.rejectValue(file.getName(), file.getName(), "'application/pdf' files allowed only!");
}
if (file.getSize() > ALLOWED_SIZE) {
errors.rejectValue(file.getName(), file.getName(), "File size allowed up to 3MB!");
}
}
}
#Override
public boolean supports(Class<?> cls) {
return NewOrderFilesDTO.class.equals(cls);
}
}
And finally a controller:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.Valid;
import static org.springframework.http.HttpStatus.NO_CONTENT;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
#Controller
class OrderController {
private final NewOrderFilesValidator newOrderFilesValidator;
#Autowired
OrderController(NewOrderFilesValidator newOrderFilesValidator) {
this.newOrderFilesValidator = newOrderFilesValidator;
}
#InitBinder("newOrderFiles")
void initOrderFilesBinder(WebDataBinder binder) {
binder.addValidators(newOrderFilesValidator);
}
#ResponseStatus(NO_CONTENT)
#RequestMapping(value = ORDERS_PATH, method = POST, consumes = MULTIPART_FORM_DATA_VALUE)
void createOrder(
#Valid #ModelAttribute NewOrderFilesDTO newOrderFiles
) {
}
}
With the configuration above the DTO will be validated automatically by spring.

Categories