/*
* Copyright 2010 Google Inc. Copyright 2016 Manfred Tremmel
*
* 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 de.knightsoftnet.validators.client.impl;
import de.knightsoftnet.validators.client.impl.metadata.BeanMetadata;
import de.knightsoftnet.validators.client.impl.metadata.MessageAndPath;
import de.knightsoftnet.validators.client.impl.metadata.ValidationGroupsMetadata;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintViolation;
import javax.validation.MessageInterpolator;
import javax.validation.ValidationException;
import javax.validation.groups.Default;
/**
* Base methods for implementing a {@link GwtSpecificValidator}.
* <p>
* All methods that do not need to be generated go here.
* </p>
*
* @param <G> the type object to validate
*/
public abstract class AbstractGwtSpecificValidator<G> implements GwtSpecificValidator<G> {
/**
* Builds attributes one at a time.
* <p>
* Used to create a attribute map for annotations.
* </p>
*/
public static final class AttributeBuilder {
private final HashMap<String, Object> tempMap = new HashMap<>();
private AttributeBuilder() {
super();
}
public Map<String, Object> build() {
return Collections.unmodifiableMap(this.tempMap);
}
public AttributeBuilder put(final String key, final Object value) {
this.tempMap.put(key, value);
return this;
}
}
public static AttributeBuilder attributeBuilder() {
return new AttributeBuilder(); // NOPMD
}
protected static Class<?>[] groupsToClasses(final Group... groups) {
final int numGroups = groups.length;
final Class<?>[] array = new Class<?>[numGroups];
for (int i = 0; i < numGroups; i++) {
array[i] = groups[i].getGroup();
}
return array;
}
@Override
public <T> Set<ConstraintViolation<T>> validate(final GwtValidationContext<T> context,
final G object, final Class<?>... groups) {
context.addValidatedObject(object);
try {
final GroupValidator classGroupValidator = new ClassGroupValidator(object);
final GroupChain groupChain = this.createGroupChainFromGroups(context, groups);
final BeanMetadata beanMetadata = this.getBeanMetadata();
final List<Class<?>> defaultGroupSeq = beanMetadata.getDefaultGroupSequence();
if (beanMetadata.defaultGroupSequenceIsRedefined()) {
// only need to check this on class-level validation
groupChain.checkDefaultGroupSequenceIsExpandable(defaultGroupSeq);
}
return this.validateGroups(context, classGroupValidator, groupChain);
} catch (final IllegalArgumentException e) { // NOPMD
throw e;
} catch (final ValidationException e) { // NOPMD
throw e;
} catch (final Exception e) {
throw new ValidationException("Error validating " + object.getClass(), e);
}
}
/**
* Perform the actual validation of a single {@link ConstraintValidator}.
* <p>
* As a side effect {@link ConstraintViolation}s may be added to {@code violations}.
* </p>
*
* @return true if there was any constraint violations
*/
protected <A extends Annotation, T, V> boolean validate(final GwtValidationContext<T> context,
final Set<ConstraintViolation<T>> violations, final G object, final V value,
final ConstraintValidator<A, ? super V> validator,
final ConstraintDescriptorImpl<A> constraintDescriptor, final Class<?>... groups) {
validator.initialize(constraintDescriptor.getAnnotation());
final ConstraintValidatorContextImpl<A, V> constraintValidatorContext =
context.createConstraintValidatorContext(constraintDescriptor);
final List<Class<?>> groupsList = Arrays.asList(groups);
final ValidationGroupsMetadata validationGroupsMetadata =
context.getValidator().getValidationGroupsMetadata();
final Set<Class<?>> constraintGroups = constraintDescriptor.getGroups();
// check groups requested are in the set of constraint groups (including the implicit group)
if (!this.containsAny(groupsList, constraintGroups)
&& !groupsList.contains(this.getConstraints(validationGroupsMetadata).getElementClass())) {
return false;
}
if (!validator.isValid(value, constraintValidatorContext)) {
this.addViolations(//
context, //
violations, //
object, //
value, //
constraintDescriptor, //
constraintValidatorContext);
return true;
}
return false;
}
@Override
public <T> Set<ConstraintViolation<T>> validateProperty(final GwtValidationContext<T> context,
final G object, final String propertyName, final Class<?>... groups)
throws ValidationException {
try {
final GroupValidator propertyGroupValidator =
new PropertyGroupValidator(object, propertyName);
final GroupChain groupChain = this.createGroupChainFromGroups(context, groups);
return this.validateGroups(context, propertyGroupValidator, groupChain);
} catch (final IllegalArgumentException e) { // NOPMD
throw e;
} catch (final ValidationException e) { // NOPMD
throw e;
} catch (final Exception e) {
throw new ValidationException(
"Error validating property " + propertyName + " of " + object.getClass(), e);
}
}
@Override
public <T> Set<ConstraintViolation<T>> validateValue(final GwtValidationContext<T> context,
final Class<G> beanType, final String propertyName, final Object value,
final Class<?>... groups) throws ValidationException {
try {
final GroupValidator valueGroupValidator =
new ValueGroupValidator(beanType, propertyName, value);
final GroupChain groupChain = this.createGroupChainFromGroups(context, groups);
return this.validateGroups(context, valueGroupValidator, groupChain);
} catch (final IllegalArgumentException e) { // NOPMD
throw e;
} catch (final ValidationException e) { // NOPMD
throw e;
} catch (final Exception e) {
throw new ValidationException(
"Error validating property " + propertyName + " with value " + value + " of " + beanType,
e);
}
}
protected List<Class<?>> addDefaultGroupWhenEmpty(final List<Class<?>> pgroups) {
List<Class<?>> groups = pgroups;
if (groups.isEmpty()) {
groups = new ArrayList<>();
groups.add(Default.class);
}
return groups;
}
protected <V, T, A extends Annotation> void addSingleViolation(
final GwtValidationContext<T> context, final Set<ConstraintViolation<T>> violations,
final G object, final V value, final ConstraintDescriptorImpl<A> constraintDescriptor) {
final ConstraintValidatorContextImpl<A, V> constraintValidatorContext =
context.createConstraintValidatorContext(constraintDescriptor);
this.addViolations(context, violations, object, value, constraintDescriptor,
constraintValidatorContext);
}
private <V, T, A extends Annotation> void addViolations(final GwtValidationContext<T> context,
final Set<ConstraintViolation<T>> violations, final G object, final V value,
final ConstraintDescriptorImpl<A> constraintDescriptor,
final ConstraintValidatorContextImpl<A, V> constraintValidatorContext) {
final Set<MessageAndPath> mps = constraintValidatorContext.getMessageAndPaths();
for (final MessageAndPath messageAndPath : mps) {
final ConstraintViolation<T> violation = this.createConstraintViolation(//
context, //
object, //
value, //
constraintDescriptor, //
messageAndPath);
violations.add(violation);
}
}
private <T> boolean containsAny(final Collection<T> left, final Collection<T> right) {
for (final T t : left) {
if (right.contains(t)) {
return true;
}
}
return false;
}
private <T, V, A extends Annotation> ConstraintViolation<T> createConstraintViolation(
final GwtValidationContext<T> context, final G object, final V value,
final ConstraintDescriptorImpl<A> constraintDescriptor, final MessageAndPath messageAndPath) {
final MessageInterpolator messageInterpolator = context.getMessageInterpolator();
final de.knightsoftnet.validators.client.impl.MessageInterpolatorContextImpl messageContext =
new MessageInterpolatorContextImpl(constraintDescriptor, value);
final String message =
messageInterpolator.interpolate(messageAndPath.getMessage(), messageContext);
return ConstraintViolationImpl.<T>builder() //
.setConstraintDescriptor(constraintDescriptor) //
.setInvalidValue(value) //
.setLeafBean(object) //
.setMessage(message) //
.setMessageTemplate(messageAndPath.getMessage()) //
.setPropertyPath(messageAndPath.getPath()) //
.setRootBean(context.getRootBean()) //
.setRootBeanClass(context.getRootBeanClass()) //
.setElementType(constraintDescriptor.getElementType()) //
.build();
}
private <T> GroupChain createGroupChainFromGroups(final GwtValidationContext<T> context,
final Class<?>... groups) {
final List<Class<?>> groupsList = this.addDefaultGroupWhenEmpty(Arrays.asList(groups));
final ValidationGroupsMetadata validationGroupsMetadata =
context.getValidator().getValidationGroupsMetadata();
return new GroupChainGenerator(validationGroupsMetadata).getGroupChainFor(groupsList);
}
/**
* Performs the top-level validation using a helper {@link GroupValidator}. This takes group
* sequencing and Default group overriding into account.
*/
private <T> Set<ConstraintViolation<T>> validateGroups(final GwtValidationContext<T> context,
final GroupValidator groupValidator, final GroupChain groupChain) {
final Set<ConstraintViolation<T>> violations = new HashSet<>();
final Collection<Group> allGroups = groupChain.getAllGroups();
final Group[] allGroupsArray = allGroups.toArray(new Group[allGroups.size()]);
groupValidator.validateGroups(context, violations, allGroupsArray);
// handle sequences
final Iterator<List<Group>> sequenceIterator = groupChain.getSequenceIterator();
while (sequenceIterator.hasNext()) {
final List<Group> sequence = sequenceIterator.next();
for (final Group group : sequence) {
final int numberOfViolations = violations.size();
groupValidator.validateGroups(context, violations, group);
if (violations.size() > numberOfViolations) {
// stop processing when an error occurs
break;
}
}
}
return violations;
}
private class ClassGroupValidator implements GroupValidator {
private final G object;
public ClassGroupValidator(final G object) {
this.object = object;
}
@Override
public <T> void validateGroups(final GwtValidationContext<T> context,
final Set<ConstraintViolation<T>> violations, final Group... groups) {
AbstractGwtSpecificValidator.this.expandDefaultAndValidateClassGroups(context, this.object,
violations, groups);
}
}
private class PropertyGroupValidator implements GroupValidator {
private final G object;
private final String propertyName;
public PropertyGroupValidator(final G object, final String propertyName) {
this.object = object;
this.propertyName = propertyName;
}
@Override
public <T> void validateGroups(final GwtValidationContext<T> context,
final Set<ConstraintViolation<T>> violations, final Group... groups) {
AbstractGwtSpecificValidator.this.expandDefaultAndValidatePropertyGroups(context, this.object,
this.propertyName, violations, groups);
}
}
private class ValueGroupValidator implements GroupValidator {
private final Class<G> beanType;
private final String propertyName;
private final Object value;
public ValueGroupValidator(final Class<G> beanType, final String propertyName,
final Object value) {
this.beanType = beanType;
this.propertyName = propertyName;
this.value = value;
}
@Override
public <T> void validateGroups(final GwtValidationContext<T> context,
final Set<ConstraintViolation<T>> violations, final Group... groups) {
AbstractGwtSpecificValidator.this.expandDefaultAndValidateValueGroups(context, this.beanType,
this.propertyName, this.value, violations, //
groups);
}
}
}