/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.data;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.PropertyDescriptor;
import com.vaadin.data.BeanPropertySet.NestedBeanPropertyDefinition;
import com.vaadin.data.util.BeanUtil;
import com.vaadin.data.validator.BeanValidator;
/**
* @author Vaadin Ltd
* @see Binder
* @see HasValue
*
* @since 8.0
*/
public class BeanValidationBinder<BEAN> extends Binder<BEAN> {
private final Class<BEAN> beanType;
private RequiredFieldConfigurator requiredConfigurator = RequiredFieldConfigurator.DEFAULT;
/**
* Creates a new binder that uses reflection based on the provided bean type
* to resolve bean properties. It assumes that JSR-303 bean validation
* implementation is present on the classpath. If there is no such
* implementation available then {@link Binder} class should be used instead
* (this constructor will throw an exception). Otherwise
* {@link BeanValidator} is added to each binding that is defined using a
* property name.
*
* @param beanType
* the bean type to use, not <code>null</code>
*/
public BeanValidationBinder(Class<BEAN> beanType) {
super(beanType);
if (!BeanUtil.checkBeanValidationAvailable()) {
throw new IllegalStateException(
BeanValidationBinder.class.getSimpleName()
+ " cannot be used because a JSR-303 Bean Validation "
+ "implementation not found on the classpath. Use "
+ Binder.class.getSimpleName() + " instead");
}
this.beanType = beanType;
}
/**
* Sets a logic which allows to configure require indicator via
* {@link HasValue#setRequiredIndicatorVisible(boolean)} based on property
* descriptor.
* <p>
* Required indicator configuration will not be used at all if
* {@code configurator} is null.
* <p>
* By default the {@link RequiredFieldConfigurator#DEFAULT} configurator is
* used.
*
* @param configurator
* required indicator configurator, may be {@code null}
*/
public void setRequiredConfigurator(
RequiredFieldConfigurator configurator) {
requiredConfigurator = configurator;
}
/**
* Gets field required indicator configuration logic.
*
* @see #setRequiredConfigurator(RequiredFieldConfigurator)
*
* @return required indicator configurator, may be {@code null}
*/
public RequiredFieldConfigurator getRequiredConfigurator() {
return requiredConfigurator;
}
@Override
protected BindingBuilder<BEAN, ?> configureBinding(
BindingBuilder<BEAN, ?> binding,
PropertyDefinition<BEAN, ?> definition) {
Class<?> actualBeanType = findBeanType(beanType, definition);
BeanValidator validator = new BeanValidator(actualBeanType,
definition.getName());
if (requiredConfigurator != null) {
configureRequired(binding, definition, validator);
}
return binding.withValidator(validator);
}
/**
* Finds the bean type containing the property the given definition refers
* to.
*
* @param beanType
* the root beanType
* @param definition
* the definition for the property
* @return the bean type containing the given property
*/
@SuppressWarnings({ "rawtypes" })
private Class<?> findBeanType(Class<BEAN> beanType,
PropertyDefinition<BEAN, ?> definition) {
if (definition instanceof NestedBeanPropertyDefinition) {
return ((NestedBeanPropertyDefinition) definition).getParent()
.getType();
} else {
// Non nested properties must be defined in the main type
return beanType;
}
}
private void configureRequired(BindingBuilder<BEAN, ?> binding,
PropertyDefinition<BEAN, ?> definition, BeanValidator validator) {
assert requiredConfigurator != null;
Class<?> propertyHolderType = definition.getPropertyHolderType();
BeanDescriptor descriptor = validator.getJavaxBeanValidator()
.getConstraintsForClass(propertyHolderType);
PropertyDescriptor propertyDescriptor = descriptor
.getConstraintsForProperty(definition.getName());
if (propertyDescriptor == null) {
return;
}
if (propertyDescriptor.getConstraintDescriptors().stream()
.map(ConstraintDescriptor::getAnnotation)
.anyMatch(requiredConfigurator)) {
binding.getField().setRequiredIndicatorVisible(true);
}
}
}