/*
* 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.validator;
import java.io.Serializable;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.MessageInterpolator.Context;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.metadata.ConstraintDescriptor;
import com.vaadin.data.ValidationResult;
import com.vaadin.data.Validator;
import com.vaadin.data.ValueContext;
import com.vaadin.data.util.BeanUtil;
/**
* A {@code Validator} using the JSR-303 (javax.validation) annotation-based
* bean validation mechanism. Values passed to this validator are compared
* against the constraints, if any, specified by annotations on the
* corresponding bean property.
* <p>
* Note that a JSR-303 implementation (for instance
* <a href="http://hibernate.org/validator/">Hibernate Validator</a> or
* <a href="http://bval.apache.org/">Apache BVal</a>) must be present on the
* project classpath when using bean validation. Specification versions 1.0 and
* 1.1 are supported.
*
* @author Vaadin Ltd.
*
* @since 8.0
*/
public class BeanValidator implements Validator<Object> {
private static final class ContextImpl implements Context, Serializable {
private final ConstraintViolation<?> violation;
private ContextImpl(ConstraintViolation<?> violation) {
this.violation = violation;
}
@Override
public ConstraintDescriptor<?> getConstraintDescriptor() {
return violation.getConstraintDescriptor();
}
@Override
public Object getValidatedValue() {
return violation.getInvalidValue();
}
}
private String propertyName;
private Class<?> beanType;
/**
* Creates a new JSR-303 {@code BeanValidator} that validates values of the
* specified property. Localizes validation messages using the
* {@linkplain Locale#getDefault() default locale}.
*
* @param beanType
* the bean type declaring the property, not null
* @param propertyName
* the property to validate, not null
* @throws IllegalStateException
* if {@link BeanUtil#checkBeanValidationAvailable()} returns
* false
*/
public BeanValidator(Class<?> beanType, String propertyName) {
if (!BeanUtil.checkBeanValidationAvailable()) {
throw new IllegalStateException("Cannot create a "
+ BeanValidator.class.getSimpleName()
+ ": a JSR-303 Bean Validation implementation not found on the classpath");
}
Objects.requireNonNull(beanType, "bean class cannot be null");
Objects.requireNonNull(propertyName, "property name cannot be null");
this.beanType = beanType;
this.propertyName = propertyName;
}
/**
* Validates the given value as if it were the value of the bean property
* configured for this validator. Returns {@code Result.ok} if there are no
* JSR-303 constraint violations, a {@code Result.error} of chained
* constraint violation messages otherwise.
* <p>
* Null values are accepted unless the property has an {@code @NotNull}
* annotation or equivalent.
*
* @param value
* the input value to validate
* @param context
* the value context for validation
* @return the validation result
*/
@Override
public ValidationResult apply(final Object value, ValueContext context) {
Set<? extends ConstraintViolation<?>> violations = getJavaxBeanValidator()
.validateValue(beanType, propertyName, value);
Locale locale = context.getLocale().orElse(Locale.getDefault());
Optional<ValidationResult> result = violations.stream()
.map(violation -> ValidationResult
.error(getMessage(violation, locale)))
.findFirst();
return result.orElse(ValidationResult.ok());
}
@Override
public String toString() {
return String.format("%s[%s.%s]", getClass().getSimpleName(),
beanType.getSimpleName(), propertyName);
}
/**
* Returns the underlying JSR-303 bean validator factory used. A factory is
* created using {@link Validation} if necessary.
*
* @return the validator factory to use
*/
protected static ValidatorFactory getJavaxBeanValidatorFactory() {
return LazyFactoryInitializer.FACTORY;
}
/**
* Returns a shared JSR-303 validator instance to use.
*
* @return the validator to use
*/
public javax.validation.Validator getJavaxBeanValidator() {
return getJavaxBeanValidatorFactory().getValidator();
}
/**
* Returns the interpolated error message for the given constraint violation
* using the locale specified for this validator.
*
* @param violation
* the constraint violation
* @param locale
* the used locale
* @return the localized error message
*/
protected String getMessage(ConstraintViolation<?> violation,
Locale locale) {
return getJavaxBeanValidatorFactory().getMessageInterpolator()
.interpolate(violation.getMessageTemplate(),
createContext(violation), locale);
}
/**
* Creates a simple message interpolation context based on the given
* constraint violation.
*
* @param violation
* the constraint violation
* @return the message interpolation context
*/
protected Context createContext(ConstraintViolation<?> violation) {
return new ContextImpl(violation);
}
private static class LazyFactoryInitializer implements Serializable {
private static final ValidatorFactory FACTORY = getFactory();
private static ValidatorFactory getFactory() {
return Validation.buildDefaultValidatorFactory();
}
private LazyFactoryInitializer() {
}
}
}