How to allow specific words using javax.validation.constraints.Pattern? - spring

I would like to use javax.validation.constraints.Pattern for validation. The text should not be none or others.
Allowed Examples:
something
word
NOT allowed:
none
others
I am trying around but I dont catch my issue. Something like:
#NotNull
#Pattern(regexp = "(^none)")
String countryValue;
Thanks for your hint.
UPDATE:
As Anish said with the online regex validator, the regex ^(?!others|none) should be correct. But Spring-MVC still denied. Is there a special syntax to be used? I give more code to have a bigger picture:
Controller:
#PostMapping
public String post(#ModelAttribute #Valid DisclaimerFormDto disclaimerForm, BindingResult errors, ModelMap modelMap) {
if(errors.hasErrors()) {
errors.getAllErrors().forEach(System.out::println);
return "redirect:/disclaimer";
}
return "redirect:/product";
}
FormDto (with changes mentioned from Anish):
#Data
#ToString
public class DisclaimerFormDto {
#NotNull
#Pattern(regexp = "^(?!others|none)")
String countryValue;
}
Output of BindingResult:
Field error in object 'disclaimerFormDto' on field 'countryValue': rejected value [none]; codes [Pattern.disclaimerFormDto.countryValue,Pattern.countryValue,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [disclaimerFormDto.countryValue,countryValue]; arguments []; default message [countryValue],[Ljavax.validation.constraints.Pattern$Flag;#59374db6,^(?!(none|others)$).*$]; default message [muss auf Ausdruck "^(?!(none|others)$).*$" passen]

Try with this :
#NotNull
// #Pattern(regexp = "^(?!others|none)")
// updated to take any kind of string to match.
#Pattern(regexp = "^((?!(none|others)).)*$")
private String countryValue;
Check this regex example here: ^((?!(none|others)).)*$
Test case 1 : String like "abc"
Screenshot :
Test case 2 : Strings like "abc others", "abc none", "none" or "words"

Related

#PathVariable of GetMapping in Spring throws an error when the input is #

I have made an autosuggest input field that automatically searches the database on every keypress. It works fine when i insert regular characters like letters and numbers but it gets spooky when you try start the search request with the character #. Doing that throws the error org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'long'; nested exception is java.lang.NumberFormatException: For input string: "get"
When i add some letters before the # (for example des#) it will throw an 404 page not found error and if i use the % character it will throw an 400 'unauthorized' error.
This strange behavior has probably something to do that i'm expecting a GetRequest instead of a PostRequest. If i turn it into a PostMapping i'm sure the errors will dissapear. But my question is; why is this happening? Does # have a special meaning? Why does spring seemingly try to convert # to a long value even though the pathvariable is typed as String? And why has the input string become "get" according to the error? I know that in an url # has a special meaning in that it signifies an href anchor but why should it be a special character for spring?
Heres the code of my getMapping
#GetMapping("/get/varietynames/{searchString}/{languageCode}")
public List<CropVarietyNameSelectionDTO> getCropVarietySelectionDTOBySearchString(#PathVariable("searchString") #NotBlank #Pattern(regexp = "^[A-Za-z0-9]+$", message = "Search input only allows for letters and numbers")
#Size(min = 1, max = 40, message = "Search input cannot exceed 40 characters") String searchString, #PathVariable("languageCode") String languageCode){
return seedService.getCropVarietySelectionDTOBySearchString(searchString,languageCode);
}
Edit
Request on the frontend side is:
private basePath:string = this.apiUrl + "/seed";
getCropVarietySelectionDTOBySearchString(searchString: string):Observable<CropVarietyNameSelectionDTO[]>{
return (searchString && (searchString.trim().length > 0)) ? this.http.post<CropVarietyNameSelectionDTO[]>(this.basePath + "/get/varietynames/" + this.languageService.getCodeOfPreferredLanguage(), searchString) : Observable.of([]);
}
this.apiUrl = localhost:4200
That is not the correct way or option to use #PathVariable annotation which indicates that a method parameter should be bound to a URI template variable. You need to use #RequestParam annotation which indicates that a method parameter should be bound to a web request parameter. You can see this answer that is a #RequestParam vs #PathVariable
#GetMapping("/get/varietynames")
public List<CropXXXDTO> getXXXXXhString(#RequestParam #NotBlank
#Pattern(regexp = "^xx+$", message = "xxxxx")
#Size(min = 1, max = 40, message = "xxxxx") String searchString,
#RequestParam(required = false, defaultValue = "EN") String languageCode){
return seedService.getXXXXtring(searchString, languageCode);
}
Then you can check the URL by following way:
/get/varietynames?searchString=XXXXX&languageCode=EN

Bean validation - validate optional fields

Given a class that represents payload submitted from a form, I want to apply bean validation to a field that may or may not be present, for example:
class FormData {
#Pattern(...)
#Size(...)
#Whatever(...)
private String optionalField;
...
}
If optionalField is not sent in the payload, I don't want to apply any of the validators above, but if it is sent, I want to apply all of them. How can it be done?
Thanks.
So usually all of these constraints consider null value as valid. If your optional filed is null when it's not part of the payload all should work just fine as it is.
And for any mandatory fields you can put #NotNull on them.
EDIT
here's an example:
class FormData {
#Pattern(regexp = "\\d+")
#Size(min = 3, max = 3)
private final String optionalField;
#Pattern(regexp = "[a-z]+")
#Size(min = 3, max = 3)
#NotNull
private final String mandatoryField;
}
#Test
public void test() {
Validator validator = getValidator();
// optonal field is null so no violations will rise on it
FormData data = new FormData( null, "abc" );
Set<ConstraintViolation<FormData>> violations = validator.validate( data );
assertThat( violations ).isEmpty();
// optional field is present but it should fail the pattern validation:
data = new FormData( "aaa", "abc" );
violations = validator.validate( data );
assertThat( violations ).containsOnlyViolations(
violationOf( Pattern.class ).withProperty( "optionalField" )
);
}
You can see that in the first case you don't get any violations as the optional field is null. but in the second exmaple you receive a violation of pattern constraint as aaa is not a string of digits.

#RequestParam is ignoring variables with a pipe

I am calling `/path/to/search/mytable?filter=field1|value1,field2|value2
#RequestMapping(value = "/path/do/{blar}/{foo}", method = RequestMethod.GET)
public List<MyType> getTableData(#PathVariable("blar") String blar,
#PathVariable("foo") String foo,
#RequestParam(name= "filter", required = false) String filter,
#RequestParam(name= "sort", required = false) String sort) {
but for some reason having a pipe "|" in the text means that it doesn't get matched. Can someone help me understand what is going on?
I tried converting filter to String[] but if there is an pipe the whole thing is ignored and I get 400 back from the service.
Any help would be great.

Springboot controller request param for map always null

I'm trying to pass in a bunch of id's to create a filter.
The incoming request looks like (ignoring the pagination stuff, which works fine)
http://localhost:8080/news-items?filter%5B%5D=09c731de-7ed8-385d-849c-f4d6535137ab&filter%5B%5D=dd1ba187-2df9-3985-ad1c-a4cde2dfe669&modelPath=controller.newsItems&page=0&per_page=25
Where the filter param equals an ID, but there is a bunch of them, for example:
filter: [
"09c731de-7ed8-385d-849c-f4d6535137ab",
"dd1ba187-2df9-3985-ad1c-a4cde2dfe669"
],
I can't seem to collect the filters in the controller. At the moment I have
public String getFeeds(#RequestParam(value = "filter", required = false) MultiValueMap<String, String> filter, #RequestParam(value = "page", required = false) int page, #RequestParam(value = "per_page", required = false) int perPage) {
log.info("Filter: {}", filter);
}
However filter is always null. I've tried using a String rather than a map but that is also always null.
How do I go about accepting an unknown number of params in this manner? I get the feeling this is really simple but I'm just missing the obvious...
Turns out it was simple like I thought. When using a Map in the #RequestParam it takes all the incoming params, regardless of what they are.
So from what I can tell the correct solution is to do something like
#GetMapping(produces = APPLICATION_JSON)
public String getFeeds(#RequestParam MultiValueMap<String, String> params) {
params.forEach(//something);
}
I think what you are looking for is just an Array or a List, something like below :
public String getFeeds(#RequestParam(value = "filter", required = false) List<String> filters) { ... }

GroupSequence and ordered evaluation in JSR 303

In our application we have such a case:
Constraints should be evaluated in particular order. (cheap to expensive)
Constraints should not be evaluated after a violation per field.
All fields should be validated.
For first two, groupsequence is fitting very good. However for my 3rd requirement I could not find a way to solve.
public class AccountBean {
#CheepValidation
#ExpensiveValidation
#VeryExpensiveValidation
private String name;
#CheepValidation
#ExpensiveValidation
#VeryExpensiveValidation
private String surname
}
For example,
Let's say that, for name field VeryExpensiveValidationconstraint is violated and for surname field ExpensiveValidation constraint is violated.
For this case I should display:
For field name: Only VeryExpensiveValidation error message
For field surname: Only ExpensiveValidation error message
Note that for field surname we did not evaluate VeryExpensiveValidation constraint.
Is there a way to implement it with JSR 303?
Thanks
You can use groups and #GroupSequence, but it's a bit unwieldy.
public class AccountBean {
#CheapValidation(groups=Name1.class)
#ExpensiveValidation(groups=Name2.class)
#VeryExpensiveValidation(groups=Name3.class)
String name;
#CheapValidation(groups=Surname1.class)
#ExpensiveValidation(groups=Surname2.class)
#VeryExpensiveValidation(groups=Surname3.class)
String surname;
public interface Name1 {}
public interface Name2 {}
public interface Name3 {}
#GroupSequence({Name1.class, Name2.class, Name3.class})
public interface Name {}
public interface Surname1 {}
public interface Surname2 {}
public interface Surname3 {}
#GroupSequence({Surname1.class, Surname2.class, Surname3.class})
public interface Surname {}
}
Then validate with:
validator.validate(myAccountBean,
AccountBean.Name.class, AccountBean.Surname.class)
The key is to have two entirely independent group sequences.
Unfortunately, it seems you must explicitly list the groups for all the fields you want to validate. I wasn't able to get it working with a 'default' #GroupSequence. Can anyone improve on this?
I've implemented ordered validation with GroupSequence but, generally speaking, GroupSequence beans validation implementation is not transparent.
Meaning, untill first group is fully validated, you can not trigger the validation of the second group.
E.g.
I have 3 validated fields with custom validators. The idea is pretty straightforward: every field should be validated with the set of validators from top to bottom independently (descending cardinality).
#StringPropertyNotNullOrEmptyConstraint(message = "Group name is required", groups = {ValidationStep1.class})
private final StringProperty groupName;
#StringPropertyNotNullOrEmptyConstraint(message = "Group password is required", groups = {ValidationStep1.class})
#StringPropertyMatchConstraint(message = "The given password phrases do not match", dependentProperties = {"groupPasswordMatch"}, groups = {ValidationStep2.class})
private final StringProperty groupPassword;
#StringPropertyNotNullOrEmptyConstraint(message = "Group password match is required", groups = {ValidationStep1.class})
#StringPropertyMatchConstraint(message = "The given passwords phrases do not match", dependentProperties = {"groupPassword"}, groups = {ValidationStep2.class})
private final StringProperty groupPasswordMatch;
public interface ValidationStep1 {
}
public interface ValidationStep2 {
}
#GroupSequence({GroupDialogModel.class, ValidationStep1.class, ValidationStep2.class})
public interface GroupDialogModelValidationSequence {
}
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
Set<ConstraintViolation<GroupDialogModel>> constraintViolations = validator.validate(this, GroupDialogModelValidationSequence.class);
The caveat of this approach is that each field should go through ValidationStep1 first and only after each validation of step 1 succeeds it goes to step 2. For example, even if password fields are not empty, but contain different values, validation for them succeeds if group name field does not contain any value. And only after I enter some value to the group name, ValidationStep1 group succeeds and then it displays validation result of ValidationStep2 (passwords do not match).
Making each group for each field in every sequence is bad practice IMHO, but it seems like there is no other choice.
Any other solution is much appreciated.

Resources