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

jaxx.runtime.validator.field.CollectionFieldExpressionValidator Maven / Gradle / Ivy

There is a newer version: 3.0-alpha-6
Show newest version
/*
 * *##% 
 * JAXX Runtime
 * Copyright (C) 2008 - 2009 CodeLutin
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * ##%*
 */
package jaxx.runtime.validator.field;

import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.validator.ValidationException;
import com.opensymphony.xwork2.validator.validators.FieldExpressionValidator;

import java.util.Collection;
import java.util.Set;
import org.apache.commons.lang.builder.HashCodeBuilder;

/**
 * Un validateur basé sur {@link FieldExpressionValidator} qui valide sur une
 * collection de propriéte.
 *
 * @author chemit
 */
public class CollectionFieldExpressionValidator extends FieldExpressionValidator {

    public enum Mode {

        /** au moins une entrée de la collection doit etre valide */
        AT_LEAST_ONE,
        /** exactement une entrée dela collection doit être valide */
        EXACTLY_ONE,
        /** toutes les valeurs de la collection doivent etre valides */
        ALL,
        /** aucune valeur de la collection doivent etre valides */
        NONE,
        /** detection de clef unique */
        UNIQUE_KEY
    }
    /** le mode de validation sur la liste */
    protected Mode mode;
    /**
     * pour indiquer la propriété qui contient la liste à valider.
     *
     * Si cette prorpiété n'est pas renseignée alors on utilise la
     * {@link #getFieldName()} pour obtenir la collection.
     *
     * Cela permet d'effectuer une validation si une collection mais portant
     * en fait sur un autre champs
     * @since 1.5
     */
    protected String collectionFieldName;
    /**
     * drapeau pour utiliser le contexte de parcours pour valider
     * l'expression, on dispose donc alors des variables previous, current,
     * index, size et empty dans l'expression.
     *
     * Sinon l'expression s'applique directement sur l'entrée courant dans le
     * parcours sans préfixe.
     */
    protected boolean useSensitiveContext;
    /**
     * expression a valider sur la premiètre entrée de la collection.
     *
     * Note : Pour le moment, on autorise uniquement cela en mode ALL.
     */
    protected String expressionForFirst;
    /**
     * expression a valider sur la dernière entrée de la collection.
     *
     * Note : Pour le moment, on autorise uniquement cela en mode ALL.
     */
    protected String expressionForLast;
    /**
     * la liste des propriétés d'une entrée de la collection qui définit la
     * clef unique (en mode UNIQUE_KEY).
     */
    protected String[] keys;
    /** le context de parcours */
    protected WalkerContext c;
    private boolean useFirst, useLast;

    public Mode getMode() {
        return mode;
    }

    public void setMode(Mode mode) {
        this.mode = mode;
    }

    public String getCollectionFieldName() {
        return collectionFieldName;
    }

    public void setCollectionFieldName(String collectionFieldName) {
        this.collectionFieldName = collectionFieldName;
    }

    public boolean isUseSensitiveContext() {
        return useSensitiveContext;
    }

    public void setUseSensitiveContext(boolean useSensitiveContext) {
        this.useSensitiveContext = useSensitiveContext;
    }

    public String getExpressionForFirst() {
        return expressionForFirst;
    }

    public void setExpressionForFirst(String expressionForFirst) {
        this.expressionForFirst = expressionForFirst;
    }

    public String getExpressionForLast() {
        return expressionForLast;
    }

    public void setExpressionForLast(String expressionForLast) {
        this.expressionForLast = expressionForLast;
    }

    public String[] getKeys() {
        return keys;
    }

    public void setKeys(String[] keys) {
        if (keys != null && keys.length == 1 && keys[0].indexOf(",") != -1) {
            this.keys = keys[0].split(",");
        } else {
            this.keys = keys;
        }
    }

    @Override
    public void validate(Object object) throws ValidationException {
        if (mode == null) {
            throw new ValidationException("no mode defined!");
        }
        useFirst = expressionForFirst != null && !expressionForFirst.trim().isEmpty();
        useLast = expressionForLast != null && !expressionForLast.trim().isEmpty();

        if (useFirst && mode != Mode.ALL) {
            throw new ValidationException("can  only use expressionForFirst in " +
                    "mode ALL but was " + mode);
        }
        if (useLast && mode != Mode.ALL) {
            throw new ValidationException("can  only use expressionForLast in " +
                    "mode ALL but was " + mode);
        }

        String fieldName = getFieldName();

        Collection col = getCollection(object);

        if (useSensitiveContext) {
            c = new WalkerContext(col.size());
        }

        boolean answer;

        boolean pop = false;

        if (!stack.getRoot().contains(object)) {
            stack.push(object);
            pop = true;
        }

        switch (mode) {
            case ALL:
                answer = validateAllEntries(col);
                break;
            case AT_LEAST_ONE:
                answer = validateAtLeastOneEntry(col);
                break;
            case EXACTLY_ONE:
                answer = validateExtacltyOneEntry(col);
                break;
            case NONE:
                answer = validateNoneEntry(col);
                break;
            case UNIQUE_KEY:
                if (keys == null || keys.length == 0) {
                    throw new ValidationException("no unique keys defined");
                }
                answer = validateUniqueKey(col);
                break;

            default:
                // should never come here...
                answer = false;
        }

        if (!answer) {
            addFieldError(fieldName, object);
        }
        if (pop) {
            stack.pop();
        }
    }
    protected ValueStack stack;

    @Override
    public void setValueStack(ValueStack stack) {
        super.setValueStack(stack);
        this.stack = stack;
    }

    @Override
    public String getMessage(Object object) {
        boolean pop = false;

        if (useSensitiveContext && !stack.getRoot().contains(c)) {
            stack.push(c);
            pop = true;
        }
        String message = super.getMessage(object);

        if (pop) {
            stack.pop();
        }
        return message;
    }

    protected Boolean validateAllEntries(Collection col) throws ValidationException {
        boolean answer = true;
        for (Object entry : col) {
            answer = validateOneEntry(entry);
            if (!answer) {
                // validation on one entry has failed
                // no need to continue
                break;
            }
        }
        return answer;
    }

    protected Boolean validateNoneEntry(Collection col) throws ValidationException {
        boolean answer = true;
        for (Object entry : col) {
            boolean b = validateOneEntry(entry);
            if (b) {
                // one entry has sucessed, validation has failed
                // no need to continue
                answer = false;
                break;
            }
        }
        return answer;
    }

    protected Boolean validateAtLeastOneEntry(Collection col) throws ValidationException {
        boolean answer = false;
        for (Object entry : col) {
            answer = validateOneEntry(entry);
            if (answer) {
                // one entry was succes, validation is ok,
                // no need to continue
                break;
            }
        }
        return answer;
    }

    protected Boolean validateExtacltyOneEntry(Collection col) throws ValidationException {
        int count = 0;
        for (Object entry : col) {
            boolean answer = validateOneEntry(entry);
            if (answer) {
                // one entry has succed
                count++;
                if (count > 1) {
                    // more than one entriy was successfull
                    // so validation has failed
                    break;
                }

            }
        }
        return count == 1;
    }

    protected Boolean validateUniqueKey(Collection col) throws ValidationException {
        boolean answer = true;

        Set hashCodes = new java.util.HashSet();
        int index = -1;
        for (Object entry : col) {
            index++;
            // construction du hash de la clef d'unicite
            Integer hash = getUniqueKeyHashCode(entry);
            if (!hashCodes.contains(hash)) {
                hashCodes.add(hash);
                continue;
            }
            // une entree avec ce hash a deja ete trouvee
            // on est donc en violation sur la clef unique
            answer = false;
            if (log.isDebugEnabled()) {
                log.debug("duplicated uniquekey " + hash + " for entry " + index);
            }
        }
        hashCodes.clear();
        return answer;
    }

    protected boolean validateOneEntry(Object object) throws ValidationException {

        Boolean answer = Boolean.FALSE;

        boolean extraExpression = false;

        if (useSensitiveContext) {
            c.addCurrent(object);
            object = c;

            if (c.isFirst() && useFirst) {
                // on valide l'expression sur la premiètre entrée
                answer = evaluateExpression(expressionForFirst, object);
                extraExpression = true;
            }
            if (c.isLast() && useLast) {
                // on valide l'expression sur la dernière entrée
                answer = (!extraExpression || answer) && evaluateExpression(expressionForLast, object);
                extraExpression = true;
            }
        }

        answer = (!extraExpression || answer) && evaluateExpression(getExpression(), object);

        return answer;
    }

    protected boolean evaluateExpression(String expression, Object object) throws ValidationException {
        Object obj = null;
        try {
            obj = getFieldValue(expression, object);
        } catch (ValidationException e) {
            throw e;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            // let this pass, but it will be logged right below
        }

        Boolean answer = Boolean.FALSE;

        if (obj != null && obj instanceof Boolean) {
            answer = (Boolean) obj;
        } else {
            log.warn("Got result of " + obj + " when trying to get Boolean for expression " + expression);
        }
        return answer;
    }

    /**
     * @param object the incoming object containing the collection to test
     * @return the collection of the incoming object given by the fieldName property
     * @throws ValidationException if any pb to retreave the collection
     */
    protected Collection getCollection(Object object) throws ValidationException {
        String fieldName = getCollectionFieldName();
        if (fieldName == null || fieldName.trim().isEmpty()) {
            // on travaille directement sur le fieldName
            fieldName = getFieldName();
        }

        Object obj = null;

        // obtain the collection to test
        try {
            obj = getFieldValue(fieldName, object);
        } catch (ValidationException e) {
            throw e;
        } catch (Exception e) {
            // let this pass, but it will be logged right below
        }

        if (obj == null) {
            // la collection est nulle, donc on renvoie une collection vide
            return java.util.Collections.EMPTY_LIST;
        }

        if (!Collection.class.isInstance(obj)) {
            throw new ValidationException("field " + fieldName + " is not a collection type! (" + obj.getClass() + ")");
        }
        return (Collection) obj;
    }

    /**
     * Calcule pour une entrée donné, le hash de la clef unique
     *
     * @param o l'entree de la collection dont on va calculer le hash de la clef unique
     * @return le hashCode calclé de la clef unique sur l'entrée donné
     * @throws ValidationException if any pb to retreave properties values
     */
    protected Integer getUniqueKeyHashCode(Object o) throws ValidationException {
        // calcul du hash à la volée
        HashCodeBuilder builder = new HashCodeBuilder();
        for (String key : this.keys) {
            Object property = getFieldValue(key, o);
            if (log.isDebugEnabled()) {
                log.debug("key " + key + " : " + property);
            }
            builder.append(property);
        }
        return builder.toHashCode();
    }

    @Override
    public String getValidatorType() {
        return "collectionFieldExpression";
    }

    public class WalkerContext {

        protected final int size;

        public WalkerContext(int size) {
            this.size = size;
        }
        protected int index = -1;
        protected Object current;
        protected Object previous;

        public void addCurrent(Object current) {
            index++;
            previous = this.current;
            this.current = current;
        }

        public Object getCurrent() {
            return current;
        }

        public int getIndex() {
            return index;
        }

        public Object getPrevious() {
            return previous;
        }

        public int getSize() {
            return size;
        }

        public boolean isEmpty() {
            return size == 0;
        }

        public boolean isFirst() {
            return index == 0;
        }

        public boolean isLast() {
            return index == size - 1;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy