GWT Editor Validation HasEditorErrors is always empty - validation

I am using the GWT Editor and javax validation.
I have a model bean with a child bean like so ->
public interface BeanA {
#Valid
BeanB getBeanB();
void setBeanB(BeanB b);
}
public interface BeanB {
#NotEmpty
public String getValue();
public void setValue(String value);
}
There is a Widget that implements the LeafValueEditor, HasEditorErrors interfaces.
The value seems to be binding with no issue.
public class MyWidget extends Composite implements
LeafValueEditor<String>, HasEditorErrors<String>{
...
#Override
public void showErrors(List<EditorError> errors) {
// Even though the error is flagged
// the errors collection does not contain it.
}
}
When I call validate and the widget getValue returns null, the ConstraintViolation collection contains the error but when showErrors is called, the List is empty.
Any idea why the violation is found but then does not make it to the widget showErrors?

If you're using GWT Editor, you would have SimpleBeanEditorDriver interface created by GWT.create(...). this interface has a method setConstraintViolations(violations) which gets your constraint violation from you. when you validate your Model, you would have Set<ConstraintViolation<E>> as violations, then you would pass this to your editor driver. for example
Set<ConstraintViolation<E>> violations = getValidator().validate(model,
groups);
getEditorDriver().setConstraintViolations(violations)
After this, you would get widget-specific errors on the widget's showErrors(List<EditorError> errors) method.

This appears to be a GWT 2.4 issue. I did the same example in GWT 2.5 and the path was correctly set and the error collection was correct.

Related

How to link a Vaadin Grid with the result of Spring Mono WebClient data

This seems to be a missing part in the documentation of Vaadin...
I call an API to get data in my UI like this:
#Override
public URI getUri(String url, PageRequest page) {
return UriComponentsBuilder.fromUriString(url)
.queryParam("page", page.getPageNumber())
.queryParam("size", page.getPageSize())
.queryParam("sort", (page.getSort().isSorted() ? page.getSort() : ""))
.build()
.toUri();
}
#Override
public Mono<Page<SomeDto>> getDataByPage(PageRequest pageRequest) {
return webClient.get()
.uri(getUri(URL_API + "/page", pageRequest))
.retrieve()
.bodyToMono(new ParameterizedTypeReference<>() {
});
}
In the Vaadin documentation (https://vaadin.com/docs/v10/flow/binding-data/tutorial-flow-data-provider), I found an example with DataProvider.fromCallbacks but this expects streams and that doesn't feel like the correct approach as I need to block on the requests to get the streams...
DataProvider<SomeDto, Void> lazyProvider = DataProvider.fromCallbacks(
q -> service.getData(PageRequest.of(q.getOffset(), q.getLimit())).block().stream(),
q -> service.getDataCount().block().intValue()
);
When trying this implementation, I get the following error:
org.springframework.core.codec.CodecException: Type definition error: [simple type, class org.springframework.data.domain.Page]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Page` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1]
grid.setItems(lazyProvider);
I don't have experience with vaadin, so i'll talk about the deserialization problem.
Jackson needs a Creator when deserializing. That's either:
the default no-arg constructor
another constructor annotated with #JsonCreator
static factory method annotated with #JsonCreator
If we take a look at spring's implementations of Page - PageImpl and GeoPage, they have neither of those. So you have two options:
Write your custom deserializer and register it with the ObjectMapper instance
The deserializer:
public class PageDeserializer<T> extends StdDeserializer<Page<T>> {
public PageDeserializer() {
super(Page.class);
}
#Override
public Page<T> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
//TODO implement for your case
return null;
}
}
And registration:
SimpleModule module = new SimpleModule();
module.addDeserializer(Page.class, new PageDeserializer<>());
objectMapper.registerModule(module);
Make your own classes extending PageImpl, PageRequest, etc. and annotate their constructors with #JsonCreator and arguments with #JsonProperty.
Your page:
public class MyPage<T> extends PageImpl<T> {
#JsonCreator
public MyPage(#JsonProperty("content_prop_from_json") List<T> content, #JsonProperty("pageable_obj_from_json") MyPageable pageable, #JsonProperty("total_from_json") long total) {
super(content, pageable, total);
}
}
Your pageable:
public class MyPageable extends PageRequest {
#JsonCreator
public MyPageable(#JsonProperty("page_from_json") int page, #JsonProperty("size_from_json") int size, #JsonProperty("sort_object_from_json") Sort sort) {
super(page, size, sort);
}
}
Depending on your needs for Sort object, you might need to create MySort as well, or you can remove it from constructor and supply unsorted sort, for example, to the super constructor. If you are deserializing from input manually you need to provide type parameters like this:
JavaType javaType = TypeFactory.defaultInstance().constructParametricType(MyPage.class, MyModel.class);
Page<MyModel> deserialized = objectMapper.readValue(pageString, javaType);
If the input is from request body, for example, just declaring the generic type in the variable is enough for object mapper to pick it up.
#PostMapping("/deserialize")
public ResponseEntity<String> deserialize(#RequestBody MyPage<MyModel> page) {
return ResponseEntity.ok("OK");
}
Personally i would go for the second option, even though you have to create more classes, it spares the tediousness of extracting properties and creating instances manually when writing deserializers.
There are two parts to this question.
The first one is about asynchronously loading data for a DataProvider in Vaadin. This isn't supported since Vaadin has prioritized the typical case with fetching data straight through JDBC. This means that you end up blocking a thread while the data is loading. Vaadin 23 will add support for doing that blocking on a separate thread instead of keeping the UI thread blocked, but it will still be blocking.
The other half of your problem doesn't seem to be directly related to Vaadin. The exception message says that the Jackson instance used by the REST client isn't configured to support creating instances of org.springframework.data.domain.Page. I don't have direct experience with this part of the problem, so I cannot give any advice on exactly how to fix it.

ProxyingHandlerMethodArgumentResolver interfering with data binding

I have a web handler working with validation. When I add data-jpa dependencies, the validations stop working.
The problem is with the ProxyingHandlerMethodArgumentResolver. The data-jpa starter adds the resolver to the head of the resolver list and again later in the list. A proxy is created that does not update the model attribute object referenced in the model attribute annotation on the parameter.
My solution is to remove the resolver from the head of the resolver list, but keep it later in the list. The resolver can still be referenced, but after my custom resolvers.
I assume that this solution will cause problems later when I use more features from data-jpa. Can you suggest another way to get the original code working?
Details:
The following code works before adding the data-dependencies. I use an interface for the model attribute. As I understand, the model attribute parameter is used to bind to a model property with that name, if it exists, and create a new instance if the name does not exist in the model. Since "dataBad" is in the model, I do not expect the data binding to create a new instance, so I am able to use an interface.
#Controller
#RequestMapping("/ControllerBad")
#SessionAttributes("dataBad")
public class ControllerBad {
#ModelAttribute("dataBad")
public RequestDataRequired modelData() {
return new RequestDataRequiredSingle();
}
#PostMapping(params="confirmButton")
public String confirmMethod(
#Valid #ModelAttribute("dataBad") RequestDataRequired dataBad,
BindingResult errors
)
{
if (errors.hasErrors()) {
return "edit";
}
return "redirect:ControllerBad?confirmButton=Confirm";
}
This worked correctly. The request parameters were copied into the model attribute "dataBad".
Next, I wanted to add persistence, so I added spring-boot-starter-data-jpa and mysql-connector-java to the pom file
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
I added properties for the database to application properties
spring.datasource.url=jdbc:mysql://localhost:3306/baz
spring.datasource.username=foo
spring.datasource.password=bar
I have not created any entity classes. I have the class that binds to the form, but I have not added the annotations for an entity. At this point, I just want to get the data from the form into my bean that is in the model. Here is the interface for the form data object.
public interface RequestDataRequired {
#NotNull(message = "cannot be empty")
#Pattern(regexp = "(?i)red|green|blue",
message = "must be red, green, or blue")
public String getColor();
public void setColor(String color);
}
Nothing else was changed. When I ran the new version the validation failed, because the color property was null.
If I use an implementation of the interface, then it works. I would like to make it work with an interface, as the name of the implementation class would appear in may locations in the controller, not just in the model attribute method.
#Valid #ModelAttribute("dataBad") RequestDataRequiredSingle dataBad
I can get it working with a session attribute interface and a model attribute interface, but this entails duplicate work for copying request parameters and errors.
#PostMapping(params="confirmSessionModelButton")
public String confirmSessionModelMethod(
Model model,
#SessionAttribute RequestDataRequired dataBad,
#Valid #ModelAttribute RequestDataRequired dataModel,
BindingResult errors
)
{
BeanUtils.copyProperties(dataModel, dataBad);
if (errors.hasErrors()) {
model.addAttribute(BindingResult.class.getName() + ".dataBad", errors);
return viewLocation("edit");
}
return "redirect:ControllerBad?confirmButton=Confirm";
}
After some experimenting, I found that data-jpa added four new argument
resolvers. The ProxyingHandlerMethodArgumentResolver was included twice: once at the head of the resolver list and again after my own custom resolvers.
A proxy object is created for an interface and the request parameters are copied into the proxy. The proxy will not update the model attribute object referenced in the model attribute annotation on the parameter. The proxied object is available in the request handler with the request data, but the session attribute is not updated.
Since the proxying resolver is first in the list, any custom resolvers are not called.
If I remove the proxying resolver from the head of the argument resolver list, but leave it later in the list, I can get the code running as it did before.
public class WebConfig implements WebMvcConfigurer {
#Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
#PostConstruct
public void init() {
List<HandlerMethodArgumentResolver> argumentResolvers =
requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> newList = argumentResolvers.subList(1, argumentResolvers.size());
requestMappingHandlerAdapter.setArgumentResolvers(newList);
}
}
I am content with this solution for now but I assume that I will break something in the data-jpa that I will need later on.
Can anyone suggest a different way to get the former behavior of updating the model attribute with the request data and only creating a new instance of the model attribute when it is not already in the model?
I have found a simple solution for the problem. Data-jpa uses projections that create proxies for interfaces, and that is the problem I have. However,
data-jpa also supports DTOs, which are classes that look like the interface.
#Component
public class RequestDataRequiredDTO implements RequestDataRequired {
private String color;
#Override
public String getColor() {
return color;
}
#Override
public void setColor(String color) {
this.color = color;
}
}
I have to do two things. First I have to use a reference to the DTO in the model attribute parameter and data-jpa will project into it without using a proxy. I still use the normal interface everywhere else, I even extended the DTO from the interface.
#PostMapping(params="confirmButton")
public String confirmMethod(
#Valid #ModelAttribute("dataBad") RequestDataRequiredDTO dataBad,
BindingResult errors
)
{
if (errors.hasErrors()) {
return viewLocation("edit");
}
return "redirect:ControllerBad?confirmButton=Confirm";
}
Second, I have to define a converter from the actual class in the model to the DTO type.
#Component
public class ClassToDTOConverter
implements Converter<RequestDataRequiredSingle, RequestDataRequiredDTO> {
#Override
public RequestDataRequiredDTO convert(RequestDataRequiredSingle source) {
RequestDataRequiredDTO target = new RequestDataRequiredDTO();
target.setColor(source.getColor());
return target;
}
}

How to validate request parameters on feign client

Is there a way to add validation to feign clients on the request parameters.
For example:
#FeignClient
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") String zipCode);
}
It would be nice to verify that zipcode is not empty and is of certain length etc, before sending the HTTP call to the server.
If your validations are simple, apply to only headers and query string parameters, you can use a RequestInterceptor for this, as it provides you the opportunity to review the RequestTemplate before it is sent to the Client.
public class ValidatingRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate requestTemplate) {
// use the methods on the request template to check the query and values.
// throw an exception if the request is not valid.
}
}
If you need to validate the request body, you can use a custom Encoder
public class ValidatingEncoder implements Encoder {
public void encode(Object object, Type type, RequestTemplate template) {
// validate the object
// throw an exception if the request is not valid.
}
}
Lastly, if you want to validate individual parameters, you can provide a custom Expander for the parameter and validate it there. You can look at this answer for a complete explanation on how to create a custom expander that can work with Spring Cloud.
How to custom #FeignClient Expander to convert param?
For completeness, I've included an example for how to do this with vanilla Feign.
public class ZipCodeExpander implements Expander {
public String expand(Object value) {
// validate the object
// throw an exception if the request is not valid.
}
}
public interface ZipCodeClient {
#RequestLine("GET /zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#Param(expander = ZipCodeExpander.class) ("zipCode") String zipCode);
}
As pointed out in this comment, a solution using the Bean Validation API would be nice. And indeed, I found in a Spring Boot project that merely placing #org.springframework.validation.annotation.Validated on the interface is sufficient for enabling Bean Validation.
So for example:
#FeignClient
#Validated
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") #NotEmpty String zipCode);
}
triggering a ConstraintViolationException in the case of violations.
Any standard Bean Validation feature should work here.
UDPATE Note that there seems to be a potential issue with this solution that might require setting a Hibernate Validator configuration property like this: hibernate.validator.allow_parallel_method_parameter_constraint=true

Spring Boot + Thymeleaf - form validation

i have problem with Thymeleaf when validating form. I'm trying to create simple user register form to learn Spring and i'm unfortunately stuck.
Here is my UserForm class
public class UserForm {
#NotEmpty
private String username;
#NotEmpty
private String password;
#NotEmpty
private String passwordConfirm;
\\ Getters and Setters
}
First problem is when I add my custom validator class in initBinder
#Autowired
private UserFormValidator formValidator;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(formValidator);
}
"Default" annotated by #NotEmpty validation stops working. This is exptected behavior?
Second problem is how can I show global reject messages in thymeleaf?
My validator class is like below
public class UserFormValidator implements Validator {
#Autowired
UserService userService;
#Override
public boolean supports(Class<?> clazz) {
return UserForm.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
UserForm userForm = (UserForm) target;
if(!userForm.getPassword().equals(userForm.getPasswordConfirm())) {
errors.reject("passwords.no.match", "Passwords not match");
}
if(userService.findOneByUsername(userForm.getUsername()).isPresent()) {
errors.reject("user.exist", "User already exists (default)");
}
}
}
and post mapping from controller
#PostMapping("/create")
public String registerUser(#ModelAttribute("form") #Valid final UserForm form, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "newuser";
}
userService.saveUser(form);
return "redirect:/";
}
As "default" validation errors i can show by using exth:if="${#fields.hasErrors('passwordConfirm')}" i have no idea how can i show message for error passwords.no.match or check if this error occured?
By default spring boot uses bean validation to validated form object annotated with #Valid. If you want to use your custom validator and register it through #InitBinder, then bean validation will not take place, this is expected behavior. If you want to bean validation also works with your custom validation you need to do it manually inside your validator class or even in controller.
Here comes your second problem to show password not match error message. Inside your custom validator UserFormValidator.class while rejecting any value you need to use rejectValue() method like below:
#Override
public void validate(Object target, Errors errors) {
UserForm userForm = (UserForm) target;
if(!userForm.getPassword().equals(userForm.getPasswordConfirm())) {
errors.rejectValue("passwordConfirm", "passwords.no.match", "Passwords not match");
}
if(userService.findOneByUsername(userForm.getUsername()).isPresent()) {
errors.rejectValue("username", "user.exist", "User already exists (default)");
}
}
The rejectValue() method is used to add a validation error to the Errors object.
The first parameter identifies which field the error is associated with. The second parameter is an error code which acts a message key for the messages.properties file (or messages_en.properties or messages_fr.properties etc, if these are being used). The third parameter of rejectValue() represents the fallback default message, which is displayed if no matching error code is found in the resource bundle.
Now you can show error messages using th:if="${#fields.hasErrors('passwordConfirm')} inside your form.

Spring validation not inside web controllers

I want to use validation not with web controllers. Suppose I have a class Person
public class Person {
private String name;
private String surname;
//getters and setters
...
}
Also I have a validator class:
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname",
"surname.empty");
}
}
How I can use this validator for example in console application? Or validation is only for web application in spring?
You can use the validation tools in a console application. You simply need to call ValidationUtils.invokeValidator(validator, object, errors). Your main concern would be having a suitable Errors instance. You would probably end up using BeanPropertyBindingResult, or subclassing AbstractErrors.
You probably know, but you should consult the Spring reference and javadoc.
Rough guess at untested code:
Person person = new Person();
Errors errors = new BeanPropertyBindingResult(person, "person");
ValidationUtuls.invokeValidator(new PersonValidator(), person, errors);
if (errors.hasErrors()) { ... }
Out of interest, why are you using Spring validation in preference to javax.validation? I've found that it's generally easier to use the javax.validaton/JSR-303 API. Hibernate Validator is the reference implementation and Spring integrates with JSR-303.

Resources