jaxx.runtime.validator.field.CollectionFieldExpressionValidator Maven / Gradle / Ivy
/*
* *##%
* 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