jaxx.runtime.validator.BeanValidator 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;
import java.beans.EventSetDescriptor;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.util.ConverterUtil;
import java.beans.Introspector;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import javax.swing.event.EventListenerList;
/**
*
* A customized validator for a given bean.
*
* Note: The bean must be listenable on properyChange events (means
* must have public addPropertychangeListener and removePropertyChangeListener methods).
*
* @param type of the bean to validate.
*
* @author chemit
*/
public class BeanValidator {
/** la nom de la propriété bean */
static public final String BEAN_PROERTY = "bean";
/** la nom de la propriété contextName */
static public final String CONTEXT_NAME_PROPERTY = "contextName";
/** la nom de l'état valid */
static public final String VALID_PROERTY = "valid";
/** la nom de l'état changed */
static public final String CHANGED_PROERTY = "changed";
/** to use log facility, just put in your code: log.info(\"...\"); */
static protected final Log log = LogFactory.getLog(BeanValidator.class);
/** the type of bean to watch */
protected final Class beanClass;
/** the validation named context (can be null) */
protected String contextName;
/** to chain to a prent validator */
protected BeanValidator> parentValidator;
/** state to indicate that validator has changed since the last time bean was setted */
protected boolean changed = false;
/** state of the validator (is true if no errors of error scope is found) */
protected boolean valid = true;
/** bean to be watched */
protected B bean = null;
/** to add and remove PropertyChangeListener on watched beans */
protected EventSetDescriptor beanEventDescriptor;
/** list of fields watched by this validator */
protected Set> fields;
/** map of conversion errors detected by this validator */
protected Map conversionErrors;
/** xworks scope validator **/
protected EnumMap> validators;
/** listener that listens on bean modification */
protected PropertyChangeListener l;
/** delegate property change support */
protected PropertyChangeSupport pcs;
/** A list of event listeners for this validators */
protected EventListenerList listenerList = new EventListenerList();
public BeanValidator(Class beanClass, String contextName) {
this.beanClass = beanClass;
this.pcs = new PropertyChangeSupport(this);
this.conversionErrors = new TreeMap();
this.validators = new EnumMap>(BeanValidatorScope.class);
setContextName(contextName);
l = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
doValidate();
}
};
}
public Class getBeanClass() {
return beanClass;
}
public BeanValidator> getParentValidator() {
return parentValidator;
}
public String getContextName() {
return contextName;
}
public Set> getFields() {
return fields;
}
public Set getScopes() {
return new java.util.HashSet(validators.keySet());
}
/**
* Retourne vrai si l'objet bean a ete modifie depuis le dernier
* {@link #setBean}
*
* @return true
if bean was modify since last {@link #setBean(Object)} invocation
*/
public boolean isChanged() {
return changed;
}
public boolean isValid() {
return valid;
}
public B getBean() {
return bean;
}
public BeanValidatorField getField(String fieldName) {
for (BeanValidatorField field : fields) {
if (fieldName.equals(field.getName())) {
return field;
}
}
return null;
}
public boolean hasErrors() {
for (BeanValidatorField field : fields) {
if (field.hasErrors()) {
return true;
}
}
return false;
}
public boolean hasWarnings() {
for (BeanValidatorField field : fields) {
if (field.hasWarnings()) {
return true;
}
}
return false;
}
public boolean hasInfos() {
for (BeanValidatorField field : fields) {
if (field.hasInfos()) {
return true;
}
}
return false;
}
/**
* Test a the validator contains the field given his name
*
* @param fieldName the name of the searched field
* @return true
if validator contaisn this field, false
otherwise
*/
public boolean containsField(String fieldName) {
BeanValidatorField field = getField(fieldName);
return field != null;
}
public boolean isValid(String fieldName) {
BeanValidatorField field = getField(fieldName);
if (field == null) {
throw new IllegalArgumentException("could not find a validator field " + fieldName);
}
return field.isValid();
}
/**
* Permet de force la remise a false de l'etat de changement du bean
*
* @param changed flag to force reset of property {@link #changed}
*/
public void setChanged(boolean changed) {
this.changed = changed;
// force the property to be fired (never pass the older value)
pcs.firePropertyChange(CHANGED_PROERTY, null, changed);
}
public void setValid(boolean valid) {
this.valid = valid;
// force the property to be fired (never pass the older value)
pcs.firePropertyChange(VALID_PROERTY, null, valid);
}
public void setBean(B bean) {
B oldBean = this.bean;
if (log.isDebugEnabled()) {
log.debug(this + " : " + bean);
}
// clean conversions of previous bean
conversionErrors.clear();
if (oldBean != null) {
try {
getBeanEventDescriptor(oldBean).getRemoveListenerMethod().invoke(oldBean, l);
} catch (Exception eee) {
log.info("Can't register as listener for bean " + beanClass + " for reason " + eee.getMessage(), eee);
}
}
this.bean = bean;
if (bean == null) {
// remove all messages for all fields of the validator
for (BeanValidatorField f : fields) {
f.updateMessages(this, null, null);
}
} else {
try {
getBeanEventDescriptor(bean).getAddListenerMethod().invoke(bean, l);
} catch (Exception eee) {
log.info("Can't register as listener for bean " + beanClass + " for reason " + eee.getMessage(), eee);
}
validate();
}
setChanged(false);
setValid(!hasErrors());
pcs.firePropertyChange(BEAN_PROERTY, oldBean, bean);
}
public void setContextName(String contextName) {
String oldValidationContextName = this.contextName;
this.contextName = contextName;
// changing contextName could change fields definition
// so dettach bean, must rebuild the fields
if (bean != null) {
setBean(null);
}
// rebuild the fields
initFields();
pcs.firePropertyChange(CONTEXT_NAME_PROPERTY, oldValidationContextName, contextName);
}
public void setParentValidator(BeanValidator> parentValidator) {
this.parentValidator = parentValidator;
}
/**
* Convert a value.
*
* If an error occurs, then add an error in validator.
*
* @param the type of conversion
* @param fieldName the name of the bean property
* @param value the value to convert
* @param valueClass the type of converted value
* @return the converted value, or null if conversion was not ok
*/
@SuppressWarnings({"unchecked"})
public T convert(String fieldName, String value, Class valueClass) {
if (fieldName == null) {
throw new IllegalArgumentException("fieldName can not be null");
}
if (valueClass == null) {
throw new IllegalArgumentException("valueClass can not be null");
}
// on ne convertit pas si il y a un bean et que le resultat de la validation
// pourra etre affiche quelque part
if (!canValidate() || value == null) {
return null;
}
// remove the previous conversion error for the field
conversionErrors.remove(fieldName);
T result;
try {
Converter converter = ConverterUtil.getConverter(valueClass);
if (converter == null) {
throw new RuntimeException("could not find converter for the type " + valueClass);
}
result = (T) converter.convert(valueClass, value);
/* Why this test ? if (result != null && !value.equals(result.toString())) {
conversionErrors.put(fieldName, "error.convertor." + Introspector.decapitalize(valueClass.getSimpleName()));
result = null;
validate();
}*/
} catch (ConversionException e) {
// get
conversionErrors.put(fieldName, "error.convertor." + Introspector.decapitalize(valueClass.getSimpleName()));
result = null;
validate();
}
return result;
}
/**
* Methode pour forcer la revalidation d'un bean en mettant a jour les etats
* internes.
*
* La méthode appelle {@link #validate()} puis met à jour les etats internes
* {@link #valid} et {@link #changed}.
*
* @since 1.5
*/
public void doValidate() {
validate();
setValid(!hasErrors());
setChanged(true);
}
/**
* il faut eviter le code re-intrant (durant une validation, une autre est
* demandee). Pour cela on fait la validation dans un thread, et tant
* que la premiere validation n'est pas fini, on ne repond pas aux
* solicitations.
* Cette method est public pour permettre de force une validation par
* programmation, ce qui est utile par exemple si le bean ne supporte
* pas les {@link PropertyChangeListener}
*
* TODO la methode devra repasser en protected et on utilise la
* methode {@link #doValidate()} car {@link #validate()} ne modifie pas
* les etats internes et cela en rend son utilisation delicate (le
* validateur entre dans un etat incoherent par rapport aux messages envoyés).
*/
public void validate() {
// on ne valide que si il y a un bean et que le resultat de la validation
// pourra etre affiche quelque part
if (!canValidate()) {
return;
}
for (BeanValidatorScope scope : validators.keySet()) {
XWorkBeanValidator validator = validators.get(scope);
Map> newMessages = validator.validate(bean);
if (scope == BeanValidatorScope.ERROR) {
// treate conversion errors
// reinject them
for (Entry entry : conversionErrors.entrySet()) {
// remove from validation, errors occurs on this field
List errors = newMessages.get(entry.getKey());
String conversionError = entry.getValue();
if (errors != null) {
errors.clear();
errors.add(conversionError);
} else {
errors = java.util.Collections.singletonList(conversionError);
if (newMessages == XWorkBeanValidator.EMPTY_RESULT) {
newMessages = new java.util.HashMap>();
}
// add the concrete conversion error
newMessages.put(entry.getKey(), errors);
}
}
}
// for each field, update his list of messages
for (BeanValidatorField field : fields) {
List messagesForField = newMessages.get(field.getName());
if (field.getScopes().contains(scope)) {
field.updateMessages(this, scope, messagesForField);
}
}
}
if (parentValidator != null) {
// chained validation
// the parent validator should not be changed from this validation
boolean wasModified = parentValidator.isChanged();
parentValidator.doValidate();
if (!wasModified) {
// push back old state
parentValidator.setChanged(false);
}
}
}
@Override
public String toString() {
return super.toString() + "";
}
public void addBeanValidatorListener(BeanValidatorListener listener) {
listenerList.add(BeanValidatorListener.class, listener);
}
public void removeBeanValidatorListener(BeanValidatorListener listener) {
listenerList.remove(BeanValidatorListener.class, listener);
}
public BeanValidatorListener[] getBeanValidatorListeners() {
return listenerList.getListeners(BeanValidatorListener.class);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcs.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcs.removePropertyChangeListener(propertyName, listener);
}
/** @return true
if validation is enabled, false
otherwise. */
protected boolean canValidate() {
return !(bean == null || fields.isEmpty());
}
protected void fireFieldChanged(BeanValidatorField field, BeanValidatorScope scope, String[] toAdd, String[] toDelete) {
BeanValidatorEvent evt = new BeanValidatorEvent(this, field, scope, toAdd, toDelete);
for (BeanValidatorListener listener : listenerList.getListeners(BeanValidatorListener.class)) {
listener.onFieldChanged(evt);
}
}
protected synchronized void initFields() {
Set detectedFieldNames = new java.util.HashSet();
EnumMap> tmp = new EnumMap>(BeanValidatorScope.class);
Set> detectedFields = new java.util.HashSet>();
validators.clear();
for (BeanValidatorScope scope : BeanValidatorScope.values()) {
String scopeContext = (contextName == null ? "" : contextName + "-") + scope.name().toLowerCase();
XWorkBeanValidator newValidator = new XWorkBeanValidator(beanClass, scopeContext, false);
Set fieldNames = newValidator.getFieldNames();
if (log.isDebugEnabled()) {
log.debug("detected validators for scope " + scopeContext + " : " + fieldNames);
}
if (!fieldNames.isEmpty()) {
// fields detected in this validator, keep it
validators.put(scope, newValidator);
detectedFieldNames.addAll(fieldNames);
tmp.put(scope, fieldNames);
}
}
List scopes = new ArrayList();
for (String fieldName : detectedFieldNames) {
scopes.clear();
// detect scopes for the field
for (BeanValidatorScope scope : BeanValidatorScope.values()) {
if (tmp.containsKey(scope) && tmp.get(scope).contains(fieldName)) {
scopes.add(scope);
}
}
BeanValidatorField f = new BeanValidatorField(beanClass, fieldName, scopes);
detectedFields.add(f);
}
tmp.clear();
detectedFieldNames.clear();
this.fields = java.util.Collections.unmodifiableSet(detectedFields);
}
protected EventSetDescriptor getBeanEventDescriptor(B bean) {
if (beanEventDescriptor == null) {
// check that the bean is listenable, otherwise, can't use the validator on it
this.beanEventDescriptor = BeanValidatorUtil.getPropertyChangeListenerDescriptor(bean.getClass());
}
return beanEventDescriptor;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy