I am trying to call validator from controller using #Valid annotation, but control is not going to validator and proceeding without validating.
Controller
#Controller
#RequestMapping(value="/event")
public class EventController {
#Autowired
private EventService eventService;
#Autowired
EventValidator eventValidator;
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(eventValidator);
}
#RequestMapping(value="/add_event",method = RequestMethod.POST,produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<AjaxJSONResponse> postAddEventForm(#Valid #RequestPart("event") Event event, MultipartHttpServletRequest request) {
Boolean inserted = eventService.addEvent(event);
String contextPath = request.getContextPath();
String redirectURL = StringUtils.isEmpty(contextPath)?"/event":contextPath+"/event";
return new ResponseEntity<AjaxJSONResponse>(new AjaxJSONResponse(inserted,"Event Added Successfully",redirectURL), HttpStatus.OK);
}
}
Validator
#Component
public class EventValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Event.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Event event = (Event)target;
if (event.getEventName() == null ||!StringUtils.hasText(event.getEventName())) {
errors.rejectValue("eventName", "", "Event Name is empty");
}
}
}
Please help on this.
Thank in advance
Related
I am using springboot 2.0.1.RELEASE with spring-data-rest and followed the workaround mentioned here and my Validator is still not being invoked. Here are the details:
ValidatorRegistrar: Workaround for a bug
#Configuration
public class ValidatorRegistrar implements InitializingBean {
private static final List<String> EVENTS;
static {
List<String> events = new ArrayList<String>();
events.add("beforeCreate");
events.add("afterCreate");
events.add("beforeSave");
events.add("afterSave");
events.add("beforeLinkSave");
events.add("afterLinkSave");
events.add("beforeDelete");
events.add("afterDelete");
EVENTS = Collections.unmodifiableList(events);
}
#Autowired
ListableBeanFactory beanFactory;
#Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
#Override
public void afterPropertiesSet() throws Exception {
Map<String, Validator> validators = beanFactory.getBeansOfType(Validator.class);
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
EVENTS.stream().filter(p -> entry.getKey().startsWith(p)).findFirst()
.ifPresent(p -> validatingRepositoryEventListener.addValidator(p, entry.getValue()));
}
}
}
Validator class:
#Component("beforeSaveBidValidator")
public class BeforeSaveBidValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Bid.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Bid bid = (Bid)target;
if (!bid.getAddendaAcknowledged()) {
errors.rejectValue("addendaAcknowledged",
"addendaAcknowledged is not true");
}
}
}
Custom RestController for Bids:
#RestController
#RequestMapping(path = "/bids")
public class BidController {
private BidRepository bidRepository;
#Autowired
public BidController(
BidRepository bidRepository) {
this.bidRepository = bidRepository;
}
#PutMapping("{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
Rest Client Test Code:
Bid bid = new Bid()
...
bid.setAddendaAcknowledged(false)
Map<String, String> uriVariables = new HashMap<String, String>()
uriVariables.put("id", bid.id)
HttpHeaders headers = new HttpHeaders()
headers.setContentType(MediaType.APPLICATION_JSON)
HttpEntity<Bid> entity = new HttpEntity<>(bid, headers)
ResponseEntity<String> response = restTemplate.exchange(
"/bids/{id}", HttpMethod.PUT, entity, Bid.class, bid.id)
// Expected: response.statusCode == HttpStatus.BAD_REQUEST
// Found: response.statusCode == HttpStatus.OK
// Debugger showed that Validator was never invoked.
Any idea what I am missing?
You are trying to use your validator with custom controller, not SDR controller. In this case you can just add it to your controller with #InitBinder annotation:
#RestController
#RequestMapping("/bids")
public class BidController {
//...
#InitBinder("bid") // add this parameter to apply this binder only to request parameters with this name
protected void bidValidator(WebDataBinder binder) {
binder.addValidators(new BidValidator());
}
#PutMapping("/{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
#Component annotation on your validator is not necessary as well as ValidatorRegistrar class.
How to use validators with SDR controllers you can read in my another answer.
#RequestMapping("/validateMsg")
public boolean validateEmp(#ModelAttribute Employee emp,BindingResult bindingResult,Model model){
boolean iserror=false;
if(emp.getFirstName()=="")
{
model.addAttribute("firstName","firstName is required");
iserror=true;
}
return iserror;
}
I have written this code is this correct
You can use a validator.
#Component
public class EmploeeValidator implements Validator{
#Override
public boolean supports(Class<?> clazz) {
return Emploee.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "someProp", "someProp.empty");
//other valdiation...
}
}
Then in the controller
#Autowired
private EmploeeValidator validator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
Use it:
#RequestMapping("/emploee")
public boolean addEmp(#Valid Employee emp,Errors errors){
if(errors.hasErrors()){
//it's not valid
} else {
//ok
}
}
I'm need to access some model info in a custom editor. I've tried to use ModelMap as an initBinder params but I obtain a deny error on runtime.
Any idea?
public void initBinder(WebDataBinder binder, WebRequest request) {
binder.registerCustomEditor(MyData.class, new MyCustomEditor(model));
}
TIA
Ice72
#InitBinder just register the Editors and Validators, the ModelMap which mapping the HTML<form> will be created after the initBinder, before the #RequestMapping method.
You can use the service in you CustomEditor, and format the fields of your ModelMap which will be created.
public class MyCustomEditor extends PropertyEditorSupport {
private YourService service;
public MyCustomEditor(YourService service){
this.service = service;
}
#Override
public void setAsText(String text){
// TODO service can work here
setValue(text);
}
#Override
public String getAsText(){
// TODO service can work here
return (String) getValue();
}
}
#Resource
YourService yourService;
#InitBinder
protected void initBinder(WebDataBinder binder){
binder.registerCustomEditor(MyData.class, "fieldInMyData", new MyCustomEditor(yourService));
}
I have some model class
public class Account {
#Email
private String email;
#NotNull
private String rule;
}
and spring-validator
public class AccountValidator implements Validator {
#Override
public boolean supports(Class aClass) {
return Account.class.equals(aClass);
}
#Override
public void validate(Object obj, Errors errors) {
Account account = (Account) obj;
ValidationUtils.rejectIfEmpty(errors, "email", "email.required");
ValidationUtils.rejectIfEmpty(errors, "rule", "rule.required");
complexValidateRule(account.getRule(), errors);
}
private void complexValidateRule(String rule, Errors errors) {
// ...
}
}
I run in my service
AccountValidator validator = new AccountValidator();
Errors errors = new BeanPropertyBindingResult(account, "account");
validator.validate(account, errors);
Can I add to my validation process constraints #Email, #NotNull (JSR-303) and don't describe these rules in AccountValidator?
I know how works #Valid in spring-controllers, but what's about service layer? Is it possible? How to do such kind of validation in a proper way? May I should use Hibernate Validator?
Spring provides an Adapter to merge both validation APIs.
See the current Spring JavaDoc for more information.
An possible implementation would be
public class AccountValidator implements Validator {
private final SpringValidatorAdapter validator;
public AccountValidator(SpringValidatorAdapter validator) {
super();
this.validator = validator;
}
#Override
public boolean supports(Class aClass) {
return Account.class.equals(aClass);
}
#Override
public void validate(Object obj, Errors errors) {
//jsr303
validator.validate(obj, errors);
//custom rules
Account account = (Account) obj;
complexValidateRule(account.getRule(), errors);
}
private void complexValidateRule(String rule, Errors errors) {
// ...
}
}
Since springmvc 3.x now supports jsr303 and old spring style validator, i want to mix them in my sample apps. But there is only one method enabled for a specified controller, is that the limit of spring framework or JSR standard?
Here is my sample code.
User.java, stands for the domain model, uses JSR303 for validation.
public class User{
#Size(max = 16, message = "user loginId max-length is 16")
private String loginId;
//omit getter and setter
}
UserValidator.java, implements the org.springframework.validation.Validator interface to support user validation.
public class UserValidator implements Validator {
private UserService userService;
public boolean supports(Class<?> clazz) {
return User.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
User u = (User) target;
// loginName check for new user
if (u.getUserId() == null && !userService.isLoginIdUnique(u.getLoginId(), null)) {
errors.rejectValue("loginId", "user.loginId.unique", new Object[] { u.getLoginId() }, null);
}
}
#Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
UserController.java, uses InitBinder annotation to inject UserValidator into WebDataBinder.
#Controller("jspUserController")
#RequestMapping("/sys/users")
public class UserController {
private UserValidator userValidator;
#Autowired
public void setUserValidator(UserValidator userValidator) {
this.userValidator = userValidator;
}
/*#InitBinder("user")
public void initBinderUser(WebDataBinder binder) {
binder.setValidator(userValidator);
}*/
#RequestMapping(value = "/save")
public String save(#Valid User user, BindingResult bindingResult, Model model, HttpServletRequest request) {
if (bindingResult.hasErrors()) {
return "/sys/user/edit";
}
userService.saveUser(user);
return "redirect:/sys/users/index";
}
}
If I uncomment the #InitBinder("user") in UserController, the JSR303 validation will be disabled. While the current commented code will use JSR validator to do the validation.
Can anyone give me a workaround to mix them in one controller?
You can ADD your validator instead of SETTING it :
#InitBinder("user")
public void initBinderUser(WebDataBinder binder) {
binder.addValidators(userValidator);
}
This will execute the JSR303 validations first and then your custom validator. No need then to call the validator directly in the save method.
You can use your validator directly and let the global LocalValidatorFactoryBean (JSR-303) do its work as well:
#Controller("jspUserController")
#RequestMapping("/sys/users")
public class UserController {
private UserValidator userValidator;
#Autowired
public void setUserValidator(UserValidator userValidator) {
this.userValidator = userValidator;
}
#RequestMapping(value = "/save")
public String save(#Valid User user, BindingResult bindingResult, Model model, HttpServletRequest request) {
this.userValidator.validate(user, bindingResult);
if (bindingResult.hasErrors()) {
return "/sys/user/edit";
}
userService.saveUser(user);
return "redirect:/sys/users/index";
}
}