com.erudika.para.validation.ValidationUtils Maven / Gradle / Ivy
/*
* Copyright 2013-2017 Erudika. https://erudika.com
*
* 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.
*
* For issues and patches go to: https://github.com/erudika
*/
package com.erudika.para.validation;
import com.erudika.para.annotations.Email;
import com.erudika.para.core.App;
import com.erudika.para.core.ParaObject;
import com.erudika.para.core.utils.ParaObjectUtils;
import com.erudika.para.core.Sysprop;
import com.erudika.para.utils.Config;
import com.erudika.para.utils.Utils;
import static com.erudika.para.validation.Constraint.digits;
import static com.erudika.para.validation.Constraint.email;
import static com.erudika.para.validation.Constraint.falsy;
import static com.erudika.para.validation.Constraint.fromAnnotation;
import static com.erudika.para.validation.Constraint.future;
import static com.erudika.para.validation.Constraint.isValidConstraintType;
import static com.erudika.para.validation.Constraint.matches;
import static com.erudika.para.validation.Constraint.max;
import static com.erudika.para.validation.Constraint.min;
import static com.erudika.para.validation.Constraint.past;
import static com.erudika.para.validation.Constraint.pattern;
import static com.erudika.para.validation.Constraint.required;
import static com.erudika.para.validation.Constraint.size;
import static com.erudika.para.validation.Constraint.truthy;
import static com.erudika.para.validation.Constraint.url;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.hibernate.validator.constraints.URL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper methods for validating objects and generating JSON schemas.
*
* @author Alex Bogdanovski [[email protected]]
*/
public final class ValidationUtils {
private static final Logger logger = LoggerFactory.getLogger(ValidationUtils.class);
private static final Map>>> CORE_CONSTRAINTS =
new HashMap>>>();
private static Validator validator;
private ValidationUtils() {
}
/**
* A Hibernate Validator.
* @return a validator object
*/
public static Validator getValidator() {
if (validator == null) {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
return validator;
}
/**
* Validates objects using Hibernate Validator. Used for basic validation.
* @param obj an object to be validated
* @return true if the object is valid (all fields are populated properly)
*/
public static boolean isValidObject(ParaObject obj) {
return validateObject(obj).length == 0;
}
/**
* Validates objects using Hibernate Validator. Used for full object validation.
* @param app the current app
* @param obj an object to be validated
* @return true if the object is valid (all fields are populated properly)
*/
public static boolean isValidObject(App app, ParaObject obj) {
return validateObject(app, obj).length == 0;
}
/**
* Validates objects using Hibernate Validator.
* @param content an object to be validated
* @return a list of error messages or empty if object is valid
*/
public static String[] validateObject(ParaObject content) {
if (content == null) {
return new String[]{"Object cannot be null."};
}
LinkedList list = new LinkedList();
try {
for (ConstraintViolation constraintViolation : getValidator().validate(content)) {
String prop = "'".concat(constraintViolation.getPropertyPath().toString()).concat("'");
list.add(prop.concat(" ").concat(constraintViolation.getMessage()));
}
} catch (Exception e) {
logger.error(null, e);
}
return list.toArray(new String[]{});
}
/**
* Validates objects.
* @param content an object to be validated
* @param app the current app
* @return a list of error messages or empty if object is valid
*/
public static String[] validateObject(App app, ParaObject content) {
if (content == null || app == null) {
return new String[]{"Object cannot be null."};
}
try {
String type = content.getType();
boolean isCustomType = (content instanceof Sysprop) && !type.equals(Utils.type(Sysprop.class));
// Validate custom types and user-defined properties
if (!app.getValidationConstraints().isEmpty() && isCustomType) {
Map>> fieldsMap = app.getValidationConstraints().get(type);
if (fieldsMap != null && !fieldsMap.isEmpty()) {
LinkedList errors = new LinkedList();
for (Map.Entry>> e : fieldsMap.entrySet()) {
String field = e.getKey();
Object actualValue = ((Sysprop) content).getProperty(field);
// overriding core property validation rules is allowed
if (actualValue == null && PropertyUtils.isReadable(content, field)) {
actualValue = PropertyUtils.getProperty(content, field);
}
Map> consMap = e.getValue();
for (Map.Entry> constraint : consMap.entrySet()) {
buildAndValidateConstraint(constraint, field, actualValue, errors);
}
}
if (!errors.isEmpty()) {
return errors.toArray(new String[0]);
}
}
}
} catch (Exception ex) {
logger.error(null, ex);
}
return validateObject(content);
}
private static void buildAndValidateConstraint(Map.Entry> constraint, String field,
Object actualValue, LinkedList errors) {
if (constraint == null) {
return;
}
String consName = constraint.getKey();
Map vals = constraint.getValue();
if (vals == null) {
vals = Collections.emptyMap();
}
Object val = vals.get("value");
long min = NumberUtils.toLong(vals.get("min") + "", 0);
long max = NumberUtils.toLong(vals.get("max") + "", Config.DEFAULT_LIMIT);
if (isValidSimpleConstraint(consName, field, actualValue, errors)) {
if (matches(Min.class, consName) && !min(NumberUtils.toLong(val + "", 0)).isValid(actualValue)) {
errors.add(Utils.formatMessage("{0} must be a number larger than {1}.", field, val));
} else if (matches(Max.class, consName) && !max(NumberUtils.toLong(val + "",
Config.DEFAULT_LIMIT)).isValid(actualValue)) {
errors.add(Utils.formatMessage("{0} must be a number smaller than {1}.", field, val));
} else if (matches(Size.class, consName) && !size(min, max).isValid(actualValue)) {
errors.add(Utils.formatMessage("{0} must be between {1} and {2}.", field, min, max));
} else if (matches(Digits.class, consName) && !digits(NumberUtils.toLong(vals.get("integer") + "", 0),
NumberUtils.toLong(vals.get("fraction") + "", 0)).isValid(actualValue)) {
errors.add(Utils.formatMessage("{0} is not a valid number or within range.", field));
} else if (matches(Pattern.class, consName) && !pattern(val).isValid(actualValue)) {
errors.add(Utils.formatMessage("{0} doesn't match the pattern {1}.", field, val));
}
}
}
private static boolean isValidSimpleConstraint(String cName, String field, Object actual, LinkedList err) {
if ("required".equals(cName) && !required().isValid(actual)) {
err.add(Utils.formatMessage("{0} is required.", field));
return false;
} else if (matches(AssertFalse.class, cName) && !falsy().isValid(actual)) {
err.add(Utils.formatMessage("{0} must be false.", field));
return false;
} else if (matches(AssertTrue.class, cName) && !truthy().isValid(actual)) {
err.add(Utils.formatMessage("{0} must be true.", field));
return false;
} else if (matches(Future.class, cName) && !future().isValid(actual)) {
err.add(Utils.formatMessage("{0} must be in the future.", field));
return false;
} else if (matches(Past.class, cName) && !past().isValid(actual)) {
err.add(Utils.formatMessage("{0} must be in the past.", field));
return false;
} else if (matches(URL.class, cName) && !url().isValid(actual)) {
err.add(Utils.formatMessage("{0} is not a valid URL.", field));
return false;
} else if (matches(Email.class, cName) && !email().isValid(actual)) {
err.add(Utils.formatMessage("{0} is not a valid email.", field));
return false;
}
return true;
}
/**
* Returns all validation constraints that are defined by Java annotation in the core classes.
*
* @return a map of all core types to all core annotated constraints. See JSR-303.
*/
public static Map>>> getCoreValidationConstraints() {
if (CORE_CONSTRAINTS.isEmpty()) {
for (Map.Entry> e : ParaObjectUtils.getCoreClassesMap().entrySet()) {
String type = e.getKey();
List fieldlist = Utils.getAllDeclaredFields(e.getValue());
for (Field field : fieldlist) {
Annotation[] annos = field.getAnnotations();
if (annos.length > 1) {
Map> constrMap = new HashMap>();
for (Annotation anno : annos) {
if (isValidConstraintType(anno.annotationType())) {
Constraint c = fromAnnotation(anno);
if (c != null) {
constrMap.put(c.getName(), c.getPayload());
}
}
}
if (!constrMap.isEmpty()) {
if (!CORE_CONSTRAINTS.containsKey(type)) {
CORE_CONSTRAINTS.put(type, new HashMap>>());
}
CORE_CONSTRAINTS.get(type).put(field.getName(), constrMap);
}
}
}
}
}
return Collections.unmodifiableMap(CORE_CONSTRAINTS);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy