All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.erudika.para.validation.Constraint Maven / Gradle / Ivy

There is a newer version: 1.50.3
Show newest version
/*
 * 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.utils.Config;
import com.erudika.para.utils.Utils;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
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.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.URL;

/**
 * Represents a validation constraint.
 * @author Alex Bogdanovski [[email protected]]
 */
public abstract class Constraint {

	/**
	 * Validates the given value against the this constraint.
	 * @param actualValue a value / property of an object
	 * @return true if the value satisfies the constraint
	 */
	public abstract boolean isValid(Object actualValue);

	private String name;
	private Map payload;

	private static final Map, String> VALIDATORS = new HashMap, String>() {
		private static final long serialVersionUID = 1L;

		{
			put(Min.class, "min");
			put(Max.class, "max");
			put(Size.class, "size");
			put(Email.class, "email");
			put(Digits.class, "digits");
			put(Pattern.class, "pattern");
			put(NotNull.class, "required");
			put(NotEmpty.class, "required");
			put(NotBlank.class, "required");
			put(AssertFalse.class, "false");
			put(AssertTrue.class, "true");
			put(Future.class, "future");
			put(Past.class, "past");
			put(URL.class, "url");
		}
	};

	private static final Map SIMPLE_CONSTRAINTS = new HashMap() {
		private static final long serialVersionUID = 1L;

		{
			put("required", required());
			put("email", email());
			put("false", falsy());
			put("true", truthy());
			put("future", future());
			put("past", past());
			put("url", url());
		}
	};

	private Constraint(String name, Map payload) {
		this.name = name;
		this.payload = payload;
	}

	/**
	 * The constraint name.
	 * @return a name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name of the constraint.
	 * @param name a name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * The payload (a map).
	 * @return a map
	 */
	public Map getPayload() {
		if (payload == null) {
			payload = new LinkedHashMap();
		}
		return payload;
	}

	/**
	 * Sets the payload.
	 * @param payload a map
	 */
	public void setPayload(Map payload) {
		this.payload = payload;
	}

	/**
	 * Verifies that the given annotation type corresponds to a known constraint.
	 * @param anno annotation type
	 * @param consName constraint name
	 * @return true if known
	 */
	public static boolean matches(Class anno, String consName) {
		return VALIDATORS.get(anno).equals(consName);
	}

	/**
	 * Builds a new constraint from the annotation data.
	 * @param anno JSR-303 annotation instance
	 * @return a new constraint
	 */
	public static Constraint fromAnnotation(Annotation anno) {
		if (anno instanceof Min) {
			return min(((Min) anno).value());
		} else if (anno instanceof Max) {
			return max(((Max) anno).value());
		} else if (anno instanceof Size) {
			return size(((Size) anno).min(), ((Size) anno).max());
		} else if (anno instanceof Digits) {
			return digits(((Digits) anno).integer(), ((Digits) anno).fraction());
		} else if (anno instanceof Pattern) {
			return pattern(((Pattern) anno).regexp());
		} else {
			return new Constraint(VALIDATORS.get(anno.annotationType()),
					simplePayload(VALIDATORS.get(anno.annotationType()))) {
						public boolean isValid(Object actualValue) {
							return true;
						}
					};
		}
	}

	/**
	 * Creates a new map representing a simple validation constraint.
	 * @param name the name of the constraint
	 * @return a map
	 */
	static Map simplePayload(final String name) {
		if (name == null) {
			return null;
		}
		return new LinkedHashMap() {
			{
				put("message", "messages." + name);
			}
		};
	}

	/**
	 * Creates a new map representing a {@link Min} validation constraint.
	 * @param min the minimum value
	 * @return a map
	 */
	static Map minPayload(final Object min) {
		if (min == null) {
			return null;
		}
		return new LinkedHashMap() {
			{
				put("value", min);
				put("message", "messages." + VALIDATORS.get(Min.class));
			}
		};
	}

	/**
	 * Creates a new map representing a {@link Max} validation constraint.
	 * @param max the maximum value
	 * @return a map
	 */
	static Map maxPayload(final Object max) {
		if (max == null) {
			return null;
		}
		return new LinkedHashMap() {
			{
				put("value", max);
				put("message", "messages." + VALIDATORS.get(Max.class));
			}
		};
	}

	/**
	 * Creates a new map representing a {@link Size} validation constraint.
	 * @param min the minimum length
	 * @param max the maximum length
	 * @return a map
	 */
	static Map sizePayload(final Object min, final Object max) {
		if (min == null || max == null) {
			return null;
		}
		return new LinkedHashMap() {
			{
				put("min", min);
				put("max", max);
				put("message", "messages." + VALIDATORS.get(Size.class));
			}
		};
	}

	/**
	 * Creates a new map representing a {@link Digits} validation constraint.
	 * @param i the max size of the integral part of the number
	 * @param f the max size of the fractional part of the number
	 * @return a map
	 */
	static Map digitsPayload(final Object i, final Object f) {
		if (i == null || f == null) {
			return null;
		}
		return new LinkedHashMap() {
			{
				put("integer", i);
				put("fraction", f);
				put("message", "messages." + VALIDATORS.get(Digits.class));
			}
		};
	}

	/**
	 * Creates a new map representing a {@link Pattern} validation constraint.
	 * @param regex a regular expression
	 * @return a map
	 */
	static Map patternPayload(final Object regex) {
		if (regex == null) {
			return null;
		}
		return new LinkedHashMap() {
			{
				put("value", regex);
				put("message", "messages." + VALIDATORS.get(Pattern.class));
			}
		};
	}

	/**
	 * Returns true if that validator is the list of known validators.
	 * @param name a name
	 * @return true if validator is known
	 */
	public static boolean isValidConstraintName(String name) {
		return name != null && VALIDATORS.containsValue(name.toLowerCase());
	}

	/**
	 * Returns true if that validator is the list of known validators.
	 * @param type annotation class type
	 * @return true if validator is known
	 */
	public static boolean isValidConstraintType(Class type) {
		return type != null && VALIDATORS.containsKey(type);
	}

	/**
	 * The 'required' constraint - marks a field as required.
	 * @return constraint
	 */
	public static Constraint required() {
		return new Constraint("required", simplePayload("required")) {
			public boolean isValid(Object actualValue) {
				return !(actualValue == null || StringUtils.isBlank(actualValue.toString()));
			}
		};
	}

	/**
	 * The 'min' constraint - field must contain a number larger than or equal to min.
	 * @param min the minimum value
	 * @return constraint
	 */
	public static Constraint min(final Number min) {
		return new Constraint("min", minPayload(min)) {
			public boolean isValid(Object actualValue) {
				return actualValue == null || (actualValue instanceof Number && min != null &&
						min.longValue() <= ((Number) actualValue).longValue());
			}
		};
	}

	/**
	 * The 'max' constraint - field must contain a number smaller than or equal to max.
	 * @param max the maximum value
	 * @return constraint
	 */
	public static Constraint max(final Number max) {
		return new Constraint("max", maxPayload(max)) {
			public boolean isValid(Object actualValue) {
				return actualValue == null || (actualValue instanceof Number && max != null &&
						max.longValue() >= ((Number) actualValue).longValue());
			}
		};
	}

	/**
	 * The 'size' constraint - field must be a {@link String}, {@link Map}, {@link Collection} or array
	 * with a given minimum and maximum length.
	 * @param min the minimum length
	 * @param max the maximum length
	 * @return constraint
	 */
	public static Constraint size(final Number min, final Number max) {
		return new Constraint("size", sizePayload(min, max)) {
			public boolean isValid(Object actualValue) {
				if (actualValue != null && min != null && max != null) {
					if (actualValue instanceof String) {
						return !isOutOfRange(((String) actualValue).length(), min, max);
					} else if (actualValue instanceof Collection) {
						return !isOutOfRange(((Collection) actualValue).size(), min, max);
					} else if (actualValue instanceof Map) {
						return !isOutOfRange(((Map) actualValue).size(), min, max);
					} else if (actualValue.getClass().isArray()) {
						return !isOutOfRange(ArrayUtils.getLength(actualValue), min, max);
					} else {
						return false;
					}
				}
				return true;
			}
		};
	}

	private static boolean isOutOfRange(int x, Number min, Number max) {
		return (x < min.longValue() || x > max.longValue());
	}

	/**
	 * The 'digits' constraint - field must be a {@link Number} or {@link String} containing digits where the
	 * number of digits in the integral part is limited by 'integer', and the
	 * number of digits for the fractional part is limited
	 * by 'fraction'.
	 * @param integer the max number of digits for the integral part
	 * @param fraction the max number of digits for the fractional part
	 * @return constraint
	 */
	public static Constraint digits(final Number integer, final Number fraction) {
		return new Constraint("digits", digitsPayload(integer, fraction)) {
			public boolean isValid(Object actualValue) {
				if (actualValue != null) {
					if (!((actualValue instanceof Number) || (actualValue instanceof String))) {
						return false;
					} else {
						if (integer != null && fraction != null) {
							String val = actualValue.toString();
							String[] split = val.split("[,.]");
							if (!NumberUtils.isDigits(split[0])) {
								return false;
							}
							if (integer.intValue() < split[0].length()) {
								return false;
							}
							if (split.length > 1 && fraction.intValue() < split[1].length()) {
								return false;
							}
						}
					}
				}
				return true;
			}
		};
	}

	/**
	 * The 'pattern' constraint - field must contain a value matching a regular expression.
	 * @param regex a regular expression
	 * @return constraint
	 */
	public static Constraint pattern(final Object regex) {
		return new Constraint("pattern", patternPayload(regex)) {
			public boolean isValid(Object actualValue) {
				if (actualValue != null) {
					if (regex != null && regex instanceof String) {
						if (!(actualValue instanceof String) || !((String) actualValue).matches((String) regex)) {
							return false;
						}
					}
				}
				return true;
			}
		};
	}

	/**
	 * The 'email' constraint - field must contain a valid email.
	 * @return constraint
	 */
	public static Constraint email() {
		return new Constraint("email", simplePayload("email")) {
			public boolean isValid(Object actualValue) {
				if (actualValue != null) {
					if (!(actualValue instanceof String) || !Utils.isValidEmail((String) actualValue)) {
						return false;
					}
				}
				return true;
			}
		};
	}

	/**
	 * The 'falsy' constraint - field value must not be equal to 'true'.
	 * @return constraint
	 */
	public static Constraint falsy() {
		return new Constraint("false", simplePayload("false")) {
			public boolean isValid(Object actualValue) {
				if (actualValue != null) {
					if ((actualValue instanceof Boolean && ((Boolean) actualValue))
							|| (actualValue instanceof String && Boolean.parseBoolean((String) actualValue))) {
						return false;
					}
				}
				return true;
			}
		};
	}

	/**
	 * The 'truthy' constraint - field value must be equal to 'true'.
	 * @return constraint
	 */
	public static Constraint truthy() {
		return new Constraint("true", simplePayload("true")) {
			public boolean isValid(Object actualValue) {
				if (actualValue != null) {
					if ((actualValue instanceof Boolean && !((Boolean) actualValue))
							|| (actualValue instanceof String && !Boolean.parseBoolean((String) actualValue))) {
						return false;
					}
				}
				return true;
			}
		};
	}

	/**
	 * The 'future' constraint - field value must be a {@link Date} or a timestamp in the future.
	 * @return constraint
	 */
	public static Constraint future() {
		return new Constraint("future", simplePayload("future")) {
			public boolean isValid(Object actualValue) {
				if (actualValue != null) {
					long now = System.currentTimeMillis();
					if ((actualValue instanceof Date && ((Date) actualValue).getTime() <= now)
							|| (actualValue instanceof Number && ((Number) actualValue).longValue() <= now)) {
						return false;
					}
				}
				return true;
			}
		};
	}

	/**
	 * The 'past' constraint - field value must be a {@link Date} or a timestamp in the past.
	 * @return constraint
	 */
	public static Constraint past() {
		return new Constraint("past", simplePayload("past")) {
			public boolean isValid(Object actualValue) {
				if (actualValue != null) {
					long now = System.currentTimeMillis();
					if ((actualValue instanceof Date && ((Date) actualValue).getTime() >= now)
							|| (actualValue instanceof Number && ((Number) actualValue).longValue() >= now)) {
						return false;
					}
				}
				return true;
			}
		};
	}

	/**
	 * The 'url' constraint - field value must be a valid URL.
	 * @return constraint
	 */
	public static Constraint url() {
		return new Constraint("url", simplePayload("url")) {
			public boolean isValid(Object actualValue) {
				if (actualValue != null) {
					if (!Utils.isValidURL(actualValue.toString())) {
						return false;
					}
				}
				return true;
			}
		};
	}

	/**
	 * Builds a new constraint from a given name and payload.
	 * @param cname the constraint name
	 * @param payload the payload
	 * @return constraint
	 */
	public static Constraint build(String cname, Map payload) {
		if (cname != null && payload != null) {
			if ("min".equals(cname) && payload.containsKey("value")) {
				return min(NumberUtils.toLong(payload.get("value") + "", 0));
			} else if ("max".equals(cname) && payload.containsKey("value")) {
				return max(NumberUtils.toLong(payload.get("value") + "", Config.DEFAULT_LIMIT));
			} else if ("size".equals(cname) && payload.containsKey("min") && payload.containsKey("max")) {
				return size(NumberUtils.toLong(payload.get("min") + "", 0),
						NumberUtils.toLong(payload.get("max") + "", Config.DEFAULT_LIMIT));
			} else if ("digits".equals(cname) && payload.containsKey("integer") && payload.containsKey("fraction")) {
				return digits(NumberUtils.toLong(payload.get("integer") + "", 0),
					NumberUtils.toLong(payload.get("fraction") + "", 0));
			} else if ("pattern".equals(cname) && payload.containsKey("value")) {
				return pattern(payload.get("value"));
			} else {
				return SIMPLE_CONSTRAINTS.get(cname);
			}
		}
		return null;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy