com.erudika.para.utils.Constraint.java Source code

Java tutorial

Introduction

Here is the source code for com.erudika.para.utils.Constraint.java

Source

/*
 * Copyright 2013-2015 Erudika. http://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.utils;

import com.erudika.para.annotations.Email;
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 [alex@erudika.com]
 */
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<String, Object> payload;

    static Map<Class<? extends Annotation>, String> validators = new HashMap<Class<? extends Annotation>, 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 Constraint(String name, Map<String, Object> 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<String, Object> getPayload() {
        if (payload == null) {
            payload = new LinkedHashMap<String, Object>();
        }
        return payload;
    }

    /**
     * Sets the payload.
     * @param payload a map
     */
    public void setPayload(Map<String, Object> 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<? extends Annotation> 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<String, Object> simplePayload(final String name) {
        if (name == null) {
            return null;
        }
        return new LinkedHashMap<String, Object>() {
            {
                put("message", "messages." + name);
            }
        };
    }

    /**
     * Creates a new map representing a {@link Min} validation constraint.
     * @param min the minimum value
     * @return a map
     */
    static Map<String, Object> minPayload(final Object min) {
        if (min == null) {
            return null;
        }
        return new LinkedHashMap<String, Object>() {
            {
                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<String, Object> maxPayload(final Object max) {
        if (max == null) {
            return null;
        }
        return new LinkedHashMap<String, Object>() {
            {
                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<String, Object> sizePayload(final Object min, final Object max) {
        if (min == null || max == null) {
            return null;
        }
        return new LinkedHashMap<String, Object>() {
            {
                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<String, Object> digitsPayload(final Object i, final Object f) {
        if (i == null || f == null) {
            return null;
        }
        return new LinkedHashMap<String, Object>() {
            {
                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<String, Object> patternPayload(final Object regex) {
        if (regex == null) {
            return null;
        }
        return new LinkedHashMap<String, Object>() {
            {
                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<? extends Annotation> 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 Object min) {
        return new Constraint("min", minPayload(min)) {
            public boolean isValid(Object actualValue) {
                if (actualValue != null) {
                    if (!(actualValue instanceof Number)) {
                        return false;
                    } else {
                        if (min != null && min instanceof Number) {
                            if (((Number) min).longValue() > ((Number) actualValue).longValue()) {
                                return false;
                            }
                        }
                    }
                }
                return true;
            }
        };
    }

    /**
     * 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 Object max) {
        return new Constraint("max", maxPayload(max)) {
            public boolean isValid(Object actualValue) {
                if (actualValue != null) {
                    if (!(actualValue instanceof Number)) {
                        return false;
                    } else {
                        if (max != null && max instanceof Number) {
                            if (((Number) max).longValue() < ((Number) actualValue).longValue()) {
                                return false;
                            }
                        }
                    }
                }
                return true;
            }
        };
    }

    /**
     * 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 Object min, final Object max) {
        return new Constraint("size", sizePayload(min, max)) {
            public boolean isValid(Object actualValue) {
                if (actualValue != null) {
                    if (min != null && max != null && min instanceof Number && max instanceof Number) {
                        if (actualValue instanceof String) {
                            if (((String) actualValue).length() < ((Number) min).longValue()
                                    || ((String) actualValue).length() > ((Number) max).longValue()) {
                                return false;
                            }
                        } else if (actualValue instanceof Collection) {
                            if (((Collection) actualValue).size() < ((Number) min).longValue()
                                    || ((Collection) actualValue).size() > ((Number) max).longValue()) {
                                return false;
                            }
                        } else if (actualValue instanceof Map) {
                            if (((Map) actualValue).size() < ((Number) min).longValue()
                                    || ((Map) actualValue).size() > ((Number) max).longValue()) {
                                return false;
                            }
                        } else if (actualValue.getClass().isArray()) {
                            int len = ArrayUtils.getLength(actualValue);
                            if (len < ((Number) min).longValue() || len > ((Number) max).longValue()) {
                                return false;
                            }
                        } else {
                            return false;
                        }
                    }
                }
                return true;
            }
        };
    }

    /**
     * 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 Object integer, final Object 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 && integer instanceof Number
                                && fraction instanceof Number) {
                            String val = actualValue.toString();
                            String[] split = val.split("[,.]");
                            if (!NumberUtils.isDigits(split[0])) {
                                return false;
                            }
                            if (((Number) integer).intValue() < split[0].length()) {
                                return false;
                            }
                            if (split.length > 1 && ((Number) 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<String, Object> payload) {
        if (cname != null && payload != null && !payload.isEmpty()) {
            if ("required".equals(cname)) {
                return required();
            } else if ("email".equals(cname)) {
                return email();
            } else if ("false".equals(cname)) {
                return falsy();
            } else if ("true".equals(cname)) {
                return truthy();
            } else if ("future".equals(cname)) {
                return future();
            } else if ("past".equals(cname)) {
                return past();
            } else if ("url".equals(cname)) {
                return url();
            } else if ("min".equals(cname) && payload.containsKey("value")) {
                return min(payload.get("value"));
            } else if ("max".equals(cname) && payload.containsKey("value")) {
                return max(payload.get("value"));
            } else if ("size".equals(cname) && payload.containsKey("min") && payload.containsKey("max")) {
                return size(payload.get("min"), payload.get("max"));
            } else if ("digits".equals(cname) && payload.containsKey("integer")
                    && payload.containsKey("fraction")) {
                return digits(payload.get("integer"), payload.get("fraction"));
            } else if ("pattern".equals(cname) && payload.containsKey("value")) {
                return pattern(payload.get("value"));
            }
        }
        return null;
    }
}