Capturing Hibernate Validator message text in Spring MVC - java

I am working on a REST api using Spring-MVC and json. I running my automatic tests using Jetty and an in-memory database. I want to test that posting an invalid domain object gives me the error message from the #NotEmpty annotation. But all I get is the default Jetty 400 Bad Request page.
I have a domain class with some validation:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty(message = "Name is a required field")
private String name;
/* getters, setters */
}
Here's the controller
#Controller
public class CompanyController {
#RequestMapping(value = "/company",
method = RequestMethod.POST,
consumes = "application/json")
public void createCompany(
#Valid #RequestBody final Company company,
final HttpServletResponse response) {
//persist company
response.setStatus(HttpServletResponse.SC_CREATED);
response.setHeader("location", "/company/" + company.getId());
}
}
How can I get the value "Name is a required field" returned as part of the response?

This does it:
#ExceptionHandler(value = MethodArgumentNotValidException.class)
public void exceptionHandler(final MethodArgumentNotValidException ex,
final HttpServletResponse response) throws IOException {
for (final ObjectError objectError : ex.getBindingResult().getAllErrors()) {
response.getWriter().append(objectError.getDefaultMessage()).append(".");
}
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}

Spring 3.2 variant using ControllerAdvice and ResponseEntityExceptionHandler:
#ControllerAdvice
public class ErrorHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object>
handleMethodArgumentNotValid(MethodArgumentNotValidException e,
HttpHeaders headers, HttpStatus status,
WebRequest request) {
BindingResult bindingResult = e.getBindingResult();
ArrayList<String> errors = new ArrayList<String>();
for (ObjectError error : bindingResult.getGlobalErrors()) {
errors.add(error.getDefaultMessage());
}
for (FieldError error : bindingResult.getFieldErrors()) {
errors.add(
String.format("%s %s", error.getField(), error.getDefaultMessage())
);
}
return handleExceptionInternal(e, StringUtils.join(errors, ", "),
headers, HttpStatus.UNPROCESSABLE_ENTITY, request);
}

You need to add a BindingResult to your handler method. That object will automatically add the results to the Model, which can then be easily accessed from the Spring Forms taglib.

Related

Error GlobalExceptionHandler, Error 401 doesn't show error details

I recently implemented authentication in my API Rest, but when the user tries to access to api without being auntenticatedn the exception message doesn't appear:
This is my GlobalExceptionHandler with the method exception that cover all errors in the api:
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDetails> handlerExceptionMethod(Exception ex , WebRequest
webRequest){
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
webRequest.getDescription(false));
return new ResponseEntity<ErrorDetails>(errorDetails,
HttpStatus.INTERNAL_SERVER_ERROR);
}
The Postman sofware returns an 1, I want to see the errorDetails parameters. This is my ErrorDetails class:
#Getter
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
}
I solved the problem by addding the following line of code in Security COnfig class:
.antMatchers("/error").permitAll()

Validating multipart/form-data in Spring REST api

I recently came up to an issue related to validation. Typically, I am building a REST api that allow users to create their account including avatars. All of the information should be submitted when user clicks to Register button. So, my server will then receive a request that includes some fields like name (string), birthday (datetime), ... and avatar (multipart file). So, the question is how to validate the received file is a truly image and has an allowed size and simultaneously validate that the others (email, password) are also valid.
For the case that all fields is text, we can easily validate them using the combination of annotations like this
Controller
#PostMapping(path = "")
public ResponseEntity<?> createNewAccount(#RequestBody #Valid RegisterRequest registerRequest) {
Long resourceId = service.createNewCoderAccount(registerRequest);
return ResponseEntity.created(location(resourceId)).build();
}
Request DTO
#ConfirmedPassword
public class RegisterRequest extends BaseRequest implements ShouldConfirmPassword {
#NotBlank(message = "Field 'email' is required but not be given")
#Email
#Unique(message = "Email has been already in use", service = UserValidatorService.class, column = "email")
private String email;
#NotBlank(message = "Field 'password' is required but not be given")
#Size(min = 6, message = "Password should contain at least 6 characters")
private String password;
#NotBlank(message = "Field 'confirmPassword' is required but not be given")
private String confirmPassword;
#NotBlank(message = "Field 'firstName' is required but not be given")
private String firstName;
#NotBlank(message = "Field 'lastName' is required but not be given")
private String lastName;
}
Or in case that the request containing only file(s), we can absolutely do like this
Controller
#PostMapping(path = "/{id}")
public ResponseEntity<?> editChallengeMetadata(
#ModelAttribute ChallengeMetadataRequest request,
BindingResult bindingResult,
#PathVariable("id") Long id,
#CurrentUser User user
) throws BindException {
challengeMetadataRequestValidator.validate(request, bindingResult);
if (bindingResult.hasErrors()) {
throw new BindException(bindingResult);
}
Long challengeId = service.updateChallengeMetadata(id, request, user);
return ResponseEntity.ok(RestResponse.build(challengeId, HttpStatus.OK));
}
Validator
public class ChallengeMetadataRequestValidator implements Validator {
#Override
public boolean supports(#NonNull Class<?> aClass) {
return ChallengeMetadataRequest.class.isAssignableFrom(aClass);
}
#Override
public void validate(#NonNull Object o, #NonNull Errors errors) {
ChallengeMetadataRequest request = (ChallengeMetadataRequest) o;
if (request.getBanner() != null && !request.getBanner().isEmpty()) {
if (!List.of("image/jpeg", "image/png").contains(request.getBanner().getContentType())) {
errors.rejectValue("banner", "challenge.mime-type.not-supported", new String[]{request.getBanner().getContentType()}, "Mime-type is not supported");
}
}
}
}
As you seen above, if I wrap all data (including avatar) in a DTO class, I definitely write its own validator. But what will happen if then I have to write manually hundreds validators like that.
So, do anyone have any idea about it, typically, make the multipart/form-data request becomes simalar with application/json request ?
Thanks and regards,

Spring REST API multiple RequestParams vs controller implementation

I'm wondering about proper way of implementating of controller in case of GET request with multiple request params given. In my understanding of REST it's much better to have one endpoint with additional parameters for filtering/sorting than several endpoints (one for each case). I'm just wondering about maintanance and extensibility of such endpoint. Please have a look on example below :
#RestController
#RequestMapping("/customers")
public class CustomerController {
#Autowired
private CustomerRepository customerRepo;
#GetMapping
public Page<Customer> findCustomersByFirstName(
#RequestParam("firstName") String firstName,
#RequestParam("lastName") String lastName,
#RequestParam("status") Status status, Pageable pageable) {
if (firstName != null) {
if (lastName != null) {
if (status != null) {
return customerRepo.findByFirstNameAndLastNameAndStatus(
firstName, lastName, status, pageable);
} else {
return customerRepo.findByFirstNameAndLastName(
firstName, lastName, pageable);
}
} else {
// other combinations omitted for sanity
}
} else {
// other combinations omitted for sanity
}
}
}
Such endpoint seems to be very convenient to use (order of parameters doesn't matter, all of them are optional...), but maintaining something like this looks like a hell (number of combinations can be enormous).
My question is - what is the best way to deal with something like this? How is it designed in "professional" APIs?
What is the best way to deal with something like this?
The best way to deal with it is to use the tools already available. As you are using Spring Boot and, I assume therefore, Spring Data JPA then enable the QueryDsl suppport and web support extensions for Spring Data JPA.
You controller then simply becomes:
#GetMapping
public Page<Customer> searchCustomers(
#QuerydslPredicate(root = Customer.class) Predicate predicate, Pageable pageable) {
return customerRepo.findBy(predicate, pageable);
}
and your repository is simply extended to support QueryDsl:
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>,
QueryDslPredicateExecutor<Customer>{
}
You can now query by any combination of params without writing any further code.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web.type-safe
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.extensions.querydsl
Good day. I can't call myself a professional, but here are some tips which can make this controller looks better.
Use DTO instead of using a group of parameters
public class CustomerDTO {
private String firstName;
private String lastName;
private String status;
}
With this class your method's signature will look like this:
#GetMapping
public Page<Customer> findCustomersByFirstName(CustomerDTO customerDTO, Pageable pageable) {
...
}
Use validation if you need one
For example, you can make some of these fields are required:
public class CustomerDTO {
#NotNull(message = "First name is required")
private String firstName;
private String lastName;
private String status;
}
Don't forget to add #Valid annotation before the DTO parameter in your controller.
Use specification instead of this block with if-else
Here is a great guide on how to do it - REST Query Language with Spring Data JPA Specifications
Use the service layer, don't need to call repository from the controller
#GetMapping
public Page<Customer> findCustomersByFirstName(#Valid CustomerDTO customerDTO, BindingResult bindingResult, Pageable pageable) {
if (bindingResult.hasErrors()) {
// error handling
}
return customerService.findAllBySpecification(new CustomerSpecification(customerDTO));
}
Your controller should not contain any logic about working with entities or some business stuff. It's only about handling request/errors, redirects, views, etc...
Its good to have a POST request with such validations instead of a GET request.You can use following method for the controller.
#PostMapping(value = "/findCustomer",produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> findCustomersByFirstName(#Valid #RequestBody Customer customer){
return customerRepo.findByFirstNameAndLastNameAndStatus(customer.getFirstName, customer.getLastName(), customer.getStatus(), pageable);
}
use the DTO as follows.
public class Customer {
private String firstName;
private String lastName;
private String status;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName= firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName= lastName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status= status;
}
public LivenessInputModel(String firstName, String lastName, String status) {
this.firstName= firstName;
this.lastName= lastName;
this.status= status;
}
public LivenessInputModel() {
}
}
And add a controller level exception advice to return the response in errors.
#ControllerAdvice
public class ControllerExceptionAdvice {
private static final String EXCEPTION_TRACE = "Exception Trace:";
private static Logger log = LoggerFactory.getLogger(ControllerExceptionAdvice.class);
public ControllerExceptionAdvice() {
super();
}
#ExceptionHandler({ BaseException.class })
public ResponseEntity<String> handleResourceException(BaseException e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(e);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, e.getHttpStatus());
}
#ExceptionHandler({ Exception.class })
public ResponseEntity<String> handleException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.INTERNAL_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
#ExceptionHandler({ MethodArgumentNotValidException.class })
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException e,
HttpServletRequest request, HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
ValidationException validationEx = new ValidationException(e);
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(validationEx);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, validationEx.getHttpStatus());
}
#ExceptionHandler({ HttpMediaTypeNotSupportedException.class, InvalidMimeTypeException.class,
InvalidMediaTypeException.class, HttpMessageNotReadableException.class })
public ResponseEntity<String> handleMediaTypeNotSupportException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.BAD_REQUEST_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
#ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
public ResponseEntity<String> handleMethodNotSupportException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.METHOD_NOT_ALLOWED;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.METHOD_NOT_ALLOWED);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
#ExceptionHandler({ MissingServletRequestParameterException.class })
public ResponseEntity<String> handleMissingServletRequestParameterException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.BAD_REQUEST_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
}
Actually, you answered half of the answer yourself, Query Params are used for filtration purposes, and as you can see in your code this will be allowed via GET request. But your question regarding validations is something a trade-off.
For example; if you don't want to have this kind of check, you can depend on mandatory required = true which is the default in #RequestParam, and handle it in the response immediately.
Or you can use alternatively #RequestBody with support of #Valid for more clear info for what wrong had occurred; for example
#PostMapping(value = "/order")
public ResponseEntity<?> submitRequest(#RequestBody #Valid OrderRequest requestBody,
Errors errors) throws Exception {
if (errors.hasErrors())
throw new BusinessException("ERR-0000", "", HttpStatus.NOT_ACCEPTABLE);
return new ResponseEntity<>(sendOrder(requestBody), HttpStatus.CREATED);
}
// Your Pojo
public class OrderRequest {
#NotNull(message = "channel is required")
private String channel;
#NotNull(message = "Party ID is required")
private long partyId;
}
For more information check this #Valid usage in Spring
This way will decouple your validation mechanism from Controller layer to Business layer. which in turns will save lots of boilerplate code, but as you noticed with change to POST instead.
So in general, there is no direct answer to your question, and the short answer is it depends, so choose whatever easy for you with good capabilities and less maintainance will be the best practice
As an alternative solution besides other ones, you can use JpaSpecificationExecutor<T> in your repository and create a specification object based on your arguments and pass it to the findAll method.
So, your repository should extend from the JpaSpecificationExecutor<Customer> interface as follows:
#Repository
interface CustomerRepository extends JpaSpecificationExecutor<Customer> {
}
Your controller should get the required arguments as Map<String, String to gain dynamic behavior.
#RestController
#RequestMapping("/customers")
public class CustomerController {
private final CustomerRepository repository;
#Autowired
public CustomerController(CustomerRepository repository) {
this.repository = repository;
}
#GetMapping
public Page<Customer> findAll(#RequestBody HashMap<String, String> filters, Pageable pageable) {
return repository.findAll(QueryUtils.toSpecification(filters), pageable);
}
}
And, you should define a method to convert provided arguments to Specification<Customer>:
class QueryUtils {
public static Specification<Customer> toSpecification(Map<String, String> filters) {
Specification<Customer> conditions = null;
for (Map.Entry<String, String> entry : filters.entrySet()) {
Specification<Customer> condition = Specification.where((root, query, cb) -> cb.equal(root.get(entry.getKey()), entry.getValue()));
if (conditions == null) {
conditions = condition;
} else {
conditions = conditions.and(condition);
}
}
return conditions;
}
}
Also, You can use the Meta model to make better criteria query and combine it with the provided solution.

Validator for MethodArgumentNotValidException only handles constraint of same type

I'm trying to validate my form against constraints set on my bean. Spring-MVC version i am using is 3.2.4. The problem is that default Spring validator does not validate all constraints; only the ones that are of the same type.
I have the following controller code:
#Controller
#SessionAttributes()
public class FormSubmitController {
#RequestMapping(value = "/saveForm", method = RequestMethod.POST)
#ResponseBody
public ModelMap saveForm(#Valid #RequestBody Form form, HttpSession session) {
session.setAttribute("form", form);
ModelMap map = new ModelMap();
map.addAttribute("hasErrors", false);
return map;
}
}
and the following bean:
public class Form implements IForm, Serializable {
#NotNull(message = "Category should not be empty")
protected String category;
#NotNull(message = "Sub-Category should not be empty")
protected String subCategory;
#Size(min=0, message="Firstname should not be empty")
protected String firstName;
#Size(min=0, message="Lastname should not be empty")
protected String lastName;
#Pattern(regexp="^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\\d\\d$", message="Date of birth should be in dd-mm-jjjj format")
protected String dateOfBirth;
//getters and setters
}
The handler for MethodArgumentNotValidException looks like this:
#ControllerAdvice
public class FormExceptionController {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ModelMap handleMethodArgumentNotValidException(MethodArgumentNotValidException error) {
List<FieldError> errors = error.getBindingResult().getFieldErrors();
ModelMap map = new ModelMap();
ModelMap errorMap = new ModelMap();
map.addAttribute("hasErrors", true);
for (FieldError fieldError : errors) {
errorMap.addAttribute(fieldError.getField(), fieldError.getDefaultMessage());
}
map.addAttribute("bindingErrors", errorMap);
return map;
}
}
So, an empty form results in the first two error messages.
The firts two properties of the form filled results in the third and fourth error messages.
Only when i use the same contraint type (i.e. NotNull) for all properties on my bean it will return all error messages.
What can be wrong here?
Nothing is wrong the validator for #Size and #Pattern by default accept null as valid. So you actually need both annotations (#NotNull and #Pattern/#Size). These annotations only trigger validation of values which are non-null, these validations don't imply that null values are invalid that is where the #NotNull is for.
This is assuming you are using hibernate-vaildator (as that is the out-of-the-box supported validator).

Parameter binding to a VO with #Form- RestEasy - JAX-Rs

I have a few variables as #PathParam. I want to put them in a Bean and accept all of them in one.
public void show( #PathParam("personId"> String personId,
#PathParam("addressId") String addressId
#Context HttpRequest request) {
// Code
}
Now I would like to put all of the parameters in a Bean/VO with #Form argument.
My class:
class RData {
private String personId;
private String addressId;
private InputStream requestBody;
#PathParam("personId")
public void setPersonId(String personId) {
this.personId = personId;
}
#PathParam("addressId")
public void setAddressId(String addressId) {
this.addressId = addressId;
}
// NOW HERE I NEED TO BIND HttpRequest Context object to request object in my VO.
// That is #Context param in the original method.
}
My method would change to:
public void show( #Form RData rData) {
// Code
}
My VO class above contains what I need to do.
So I need to map #Context HttpRequest request to HttpRequest instance variable in my VO.
How to do that? Because it does not have an attribute name like #PathParam.
You can inject #Context values into properties just like the form, path, and header parameters.
Example Resource Method:
#POST
#Path("/test/{personId}/{addressId}")
public void createUser(#Form MyForm form)
{
System.out.println(form.toString());
}
Example Form Class:
public class MyForm {
private String personId;
private String addressId;
private HttpRequest request;
public MyForm() {
}
#PathParam("personId")
public void setPersonId(String personId) {
this.personId = personId;
}
#PathParam("addressId")
public void setAddressId(String addressId) {
this.addressId = addressId;
}
public HttpRequest getRequest() {
return request;
}
#Context
public void setRequest(HttpRequest request) {
this.request = request;
}
#Override
public String toString() {
return String.format("MyForm: [personId: '%s', addressId: '%s', request: '%s']",
this.personId, this.addressId, this.request);
}
}
Url:
http://localhost:7016/v1/test/1/1
Output:
MyForm: [personId: '1', addressId: '1', request: 'org.jboss.resteasy.plugins.server.servlet.HttpServletInputMessage#15d694da']
I thought I would add an answer for those that are using pure JAX-RS not not RestEasy specifically. Faced with the same problem, and surprised that JAX-RS doesn't have out-of-box support for http Form binding to Java Objects, I created a Java API to marshal/unmarshal java objects to forms, and then used that to create a JAX-RS messagebody reader and writer.
https://github.com/exabrial/form-binding

Categories

Resources