Getting default message of Javax.Validation.Constraints from within a subclass? - java

I have an HTML form that is submitting data to a spring post method. I've enabled validation with the javax.validation library and the #Valid tag:
public String openForm(#ModelAttribute("task")#Valid Task task,
BindingResult result, Model model) {
if(result.hasErrors()) {
System.out.println("Form does not validate.");
List<ObjectError> errors = result.getAllErrors();
for(ObjectError error: errors) {
System.out.println(error.getDefaultMessage());
}
}
return "home";
}
Task is the main object being binded to the form, but Task contains a class called User. User has a fullName. There are two fields that I am trying to validate:
systemType is a String within the Task object:
#Size(min=1, message="The System Type must contain a valid option")
private String systemType;
fullName is a String within the User object:
#Size(min=1, message="A User must be selected.")
private String fullName;
The validation itself is working, but what I am trying to figure out how to do is get the default message working. When I iterate through the BindingResult result for errors and print out the error.getDefaultMessage() the systemType error message works as expected.
However, when there is a validation error with the fullName field, it prints:
Property 'user.fullName' threw exception; nested exception is java.lang.ArrayIndexOutOfBoundsException: 1
I believe this to be because it is an inner class of the main class Task which is what is actually being validated. I guess I could just write some logic to say that if this error comes along to just print the error message I actually want - but is there a way to go within error and get the default message I originally specified?

Related

How can I access the request object when handling a MethodArgumentNotValidException?

I'm handling a MethodArgumentNotValidException thrown after a failed validation of a request object. All the usual stuff is in place: #Valid, #ControllerAdvice, and an extended ResponseEntityExceptionHandler, in which I override handleMethodArgumentNotValid().
As it happens, I need to access that same request object in order to form a customized error response. One way would be to intercept the request before it hits the controller and create a #RequestScope bean with the needed fields in case validation fails later.
Is there a better way?
Thanks to a suggestion from a colleague, I've found that the BindingResult within MethodArgumentNotValidException has a method named getTarget() that returns the validated object. As seen from the method signature (Object getTarget()), the return value needs a cast.
You should have the error fields in the MethodArgumentNotValidException class. Your handleMethodArgumentNotValid function might look like something as follows.
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public CustomInputErrorResponse handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
String message = "Invalid inputs";
ArrayList<String> fieldNames = new ArrayList<String>();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
fieldNames.add(fieldError.getField());
}
return new CustomInputErrorResponse(message, fieldNames);
}
Considering you have a CustomInputErrorResponse class that takes two arguments for a custom message and error field names.

How to perform validation on #RequestParam in Spring

I'm a newbie in java spring. I have checked for solutions online and I can't seem to get my hands on anything helpful.
I have a form that has been bound to an Entity, however, I have a field in that form that was not bound, which I receive as a requestParam.
So when the field is submitted I need to validate that parameter to ensure it's not empty.
#PostMapping("/store")
public String store(#Valid Quote quote,BindingResult result, Model model,#RequestParam(required=false) String [] tagsFrom) {
if(result.hasErrors()) {
model.addAttribute("jsontags", returnTagsAsJson(tagRepo.findAll()));
return "admin/quotes/create";
}
List<Tag> listtags = new ArrayList<Tag> ();
for(String tag : tagsFrom) {
Tag theTag = new Tag();
theTag.setTag(tag);
theTag.setCreatedAt(new Date());
theTag.setUpdatedAt(new Date());
if(tagRepo.findByTag(tag) == null) {
tagRepo.save(theTag);
}
listtags.add(tagRepo.findByTag(tag));
}
quote.setTags(listtags);
quoteRepo.save(quote);
return "redirect:/dashboard/quotes";
}
What I have tried;
I created a custom validation and added the annotation to the parameter but that gave an error "The annotation #EmptyArrayString is disallowed for this location"
public String store(#Valid Quote quote,BindingResult result, Model model,
#RequestParam(required=false) #EmptyArrayString String [] tagsFrom)
I have tried using #NotEmpty on the parameter which throws NullPointerException
I need a solution that allows me to display the error on the HTML form like this
<span th:if="${#fields.hasErrors('tags')}"
th:errors="${quote.tags}" class="errors">
</span>
So when the field is submitted I need to validate that parameter to ensure it's not empty.
,#RequestParam(required=false) String [] tagsFrom
By default, required is set to true. So, if the URL must have the param, you shouldn't do required=false.
String [] tagsFrom implies you expect a bunch of tag params. But, is it of the form http://localhost:xxx?param=1,2,3 or
http://localhost:xxx?param1=1&param2="stringvalue" ?
For the first one, you can have the mapping method as:
public String store(...#RequestParam List<String> param)
For the second one, you can do:
public String store(...#RequestParam Map<String,String> allQueryParameters)
You can then do your necessary validations.
Read more here

Model.addAttribute : Failed to convert value of type 'java.util.Arrays$ArrayList' to required type 'java.lang.String'

I want to add a list of items to a Spring Model. It sometimes throws ConversionFailedException. The method in question:
private void addAuthoritiesToModel(Model model){
List<Authority> allAuthorities = Arrays.asList(
Authority.of("ROLE_ADMIN","Admin"),
Authority.of("ROLE_USER","User")
);
model.addAttribute("allAuthorities",allAuthorities);
}
The method throws on the last line. The curious thing it only does so, when called from a particular method and not others. For example, it works fine here:
#GetMapping("/users/new")
public String newUserForm(Model model){
model.addAttribute("user",User.blank());
model.addAttribute("newUser",true);
addAuthoritiesToModel(model);
return "user_details";
}
But it blows here:
#PostMapping(value = {"/users","/profile","/users/{any}"})
public String postUser(#Valid #ModelAttribute("user") User user,
BindingResult bindingResult,
#RequestParam("newPassword") Optional<String> newPassword,
#RequestParam("confirmPassword") Optional<String> confirmPassword,
RedirectAttributes redirectAttributes,
#PathVariable("any") String pathVariable
){
if(bindingResult.hasErrors()) {
if(user.getId()==null)
redirectAttributes.addAttribute("newUser",true);
addAuthoritiesToModel(redirectAttributes);
return "user_details";
}
...
}
I have tried exchanging Arrays.asList to another List implementation, but that doesn't solve the problem. And it wouldn't explain, why it doesn't work in the first case.
There is difference between Model and RedirectAttributes.
The values in RedirectAttributes are getting formatted as String.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/support/RedirectAttributes.html
A specialization of the Model interface that controllers can use to select attributes for a redirect scenario. Since the intent of adding redirect attributes is very explicit -- i.e. to be used for a redirect URL, attribute values may be formatted as Strings and stored that way to make them eligible to be appended to the query string or expanded as URI variables in org.springframework.web.servlet.view.RedirectView.
You should not use unless required for redirecting and in such case should be string values.

Spring Validation - How to retrieve errors.rejectValue in my Contoller?

I have my validate method in my TestValidator as follows
#Override
public void validate(Object target, Errors errors) {
Test test = (Test) target;
String testTitle = test.getTestTitle();
//**ErrorCheck1** - This works, and I am able to pull the value in my controller
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "testTitle", "test.testTitle.projec", "My msg");
if (testTitle != null && testTitle.length() < 4) {
logger.error("Inside custom validation"+test.testTitle().length());
//**Error Check2**
//***HOW DO I RETRIEVE THE BELOW VALUE in My controller
errors.rejectValue(testTitle, "test.testTitle.lessThen4");
errors.addAllErrors(errors);
logger.error("Entered If condition of validate");
}
}
And my controller is
#RequestMapping(value = "/test", method = RequestMethod.PUT)
public ResponseEntity<BasicResponseDTO> newTest(#Valid #RequestBody Test test, BindingResult result) {
if (result.hasErrors()){
logger.error("Entered Errors");
BasicResponseDTO basicResponseDTO = new BasicResponseDTO();
basicResponseDTO.setCode(ResponseCode.BAD_REQUEST);
return new ResponseEntity<BasicResponseDTO>(basicResponseDTO, HttpStatus.BAD_REQUEST);
}
}
When my ErrorCheck1 condition is activated, my IF condition inside the controller is able to retrieve it.
However, in my ErrorCheck2, because of of the errors.rejectValue I immediately get an error on the console and am not able to gracefully handle the situation when the testTitle length is less than 4.
What is the alternative to errors.rejectValue so that I may handle the
error in my controller ?
Ok - Got it. All i had to do was change
errors.rejectValue(testTitle, "test.testTitle.lessThen4");
to
errors.reject(testTitle, "test.testTitle.lessThen4");
RejectValue is a Field error and is not global in nature.
Reject is a Global error and can be accessed from inside the errors list in the controller.
From the Documentation
void reject(String errorCode, String defaultMessage);
Register a global error for the entire target object, using the given error description.
#Override
public void validate(Object target, Errors errors) {
Test test = (Test) target;
String testTitle = test.getTestTitle();
//**ErrorCheck1** - This works, and I am able to pull the value in my controller
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "testTitle", "test.testTitle.projec", "My msg");
if (testTitle != null && testTitle.length() < 4) {
logger.error("Inside custom validation"+test.testTitle().length());
//**Error Check2**
//***HOW DO I RETRIEVE THE BELOW VALUE in My controller
errors.reject(testTitle, "test.testTitle.lessThen4");
errors.addAllErrors(errors);
logger.error("Entered If condition of validate");
}
}
Hope that helps someone.
You cannot access the value directly but there is a way to include the value into error message
${validatedValue}
If you annotate a field like this
#Size(min = 10, message =”Phone number entered [${validatedValue}] is invalid. It must have at least {min} digits”)
private String phoneNumber;
and enter 123 your error message should be
Phone number entered [123] is invalid. It must have at least 10 digits. Thus you can access the value.
See https://raymondhlee.wordpress.com/2014/07/26/including-field-value-in-validation-message-using-spring-validation-framework-for-jsr-303/

How to Unit Test Spring MVC validation annotation error messages?

Let's say I have a model class like this:
public class User {
#NotEmpty(message = "Email is required")
private String email;
}
I want to be able to unit test for the custom error message "Email is required"
So far, I have this Unit Test code which works, but does not check for the error message.
#Test
public void should_respond_bad_request_with_errors_when_invalid() throws Exception
{
mock().perform(post("/registration/new"))
.andExpect(status().isBadRequest())
.andExpect(view().name("registration-form"))
.andExpect(model().attributeHasFieldErrors("user", "email"));
}
Seems you can't.
But I suggest you work around through the attributeHasFieldErrorCode method.
Having the following:
#NotNull(message="{field.notnull}")
#Size(min=3, max=3, message="{field.size}")
public String getId() {
return id;
}
I have in my test methods the following (in case my data fails for Minimum or Maximum constraints, it belongs to the #Size annotation)
.andExpect(model().attributeHasFieldErrorCode("persona", "id", is("Size")))
or with
.andExpect(model().attributeHasFieldErrorCode("persona", "id", "Size"))
The method has two versions, with and without Hamcrest
Even when you use message = "Email is required" (raw/direct content) and I use message="{field.size}") the key to be used from a .properties file, our message attribute content belongs practically for a unique kind of annotation. In your case for a #NotNull and in my case for a Size.
Therefore, in some way does not matter what is the message (raw or key), the important is that the owner of that message (in this case the annotation) did its work and it has been validated through the attributeHasFieldErrorCode method.
Do not confuse with the similar methods names: attributeHasFieldErrors and attributeHasFieldErrorCode.
I have had the same issue. I have found some solution described below. The main idea is to check error code, not the error message.
BindingResult bindingResult = (BindingResult)
mock().perform(post("/registration/new")).andReturn().getModelAndView().getModelMap().get("org.springframework.validation.BindingResult.user");
assertEquals(bindingResult.getFieldError(email).getCode(), "error.code");

Categories