/*
* Copyright 2001-2008 Geert Bevin <gbevin[remove] at uwyn dot com>
* Licensed under the Apache License, Version 2.0 (the "License")
* $Id: Validation.java 3918 2008-04-14 17:35:35Z gbevin $
*/
package com.uwyn.rife.site;
import java.util.*;
import com.uwyn.rife.tools.ExceptionUtils;
import com.uwyn.rife.tools.ObjectUtils;
import java.util.logging.Logger;
public class Validation<B extends ConstrainedBean, P extends ConstrainedProperty> implements ValidatedConstrained<P>, Cloneable, Constrained<B, P>, ConstrainedPropertyListener
{
private boolean mActivated = false;
private Validated mValidatedBean = null;
private List<ValidationRule> mValidationRules = null;
private List<String> mValidatedSubjects = null;
private Map<String, P> mConstrainedProperties = null;
private Set<String> mActivePropertyConstraints = null;
private Map<String, ValidationGroup<P>> mValidationGroups = null;
private B mConstrainedBean = null;
private Set<ValidationError> mValidationErrors = null;
private List<String> mErrorLimitedSubjects = null;
public Validation()
{
}
/**
* This method is called at least once and maximum once when any method
* related to Validated rules, subjects and group or Constrained
* properties are used.
* <p>By overriding this method, you can thus isolate all the validation
* setup code code and don't enforce a performance penalty at each object
* construction when doing it in the default constructor.
*
* @since 1.0
*/
protected void activateValidation()
{
}
public void provideValidatedBean(Validated bean)
{
if (mValidationRules != null)
{
for (ValidationRule rule : mValidationRules)
{
if (rule.getBean() == mValidatedBean)
{
rule.setBean(bean);
}
}
}
mValidatedBean = bean;
}
public Validated retrieveValidatedBean()
{
if (null == mValidatedBean)
{
return this;
}
return mValidatedBean;
}
private void ensureActivatedValidation()
{
if (mActivated)
{
return;
}
mActivated = true;
activateValidation();
}
public ValidationGroup<P> addGroup(String name)
{
ensureActivatedValidation();
if (null == mValidationGroups)
{
mValidationGroups = new HashMap<String, ValidationGroup<P>>();
}
ValidationGroup<P> group = new ValidationGroup<P>(name, this);
mValidationGroups.put(name, group);
return group;
}
public void focusGroup(String name)
{
ensureActivatedValidation();
if (null == mValidationGroups ||
null == mValidationErrors ||
null == name)
{
return;
}
ValidationGroup<P> group = mValidationGroups.get(name);
if (null == group)
{
return;
}
Set<ValidationError> retained_errors = new LinkedHashSet<ValidationError>();
List<String> retained_subjects = group.getSubjects();
for (ValidationError error : mValidationErrors)
{
if (retained_subjects.contains(error.getSubject()))
{
retained_errors.add(error);
}
}
mValidationErrors = retained_errors;
}
public void resetGroup(String name)
{
ensureActivatedValidation();
if (null == mValidationGroups ||
null == mValidationErrors ||
null == name)
{
return;
}
ValidationGroup<P> group = mValidationGroups.get(name);
if (null == group)
{
return;
}
Set<ValidationError> retained_errors = new LinkedHashSet<ValidationError>();
List<String> group_subjects = group.getSubjects();
for (ValidationError error : mValidationErrors)
{
if (!group_subjects.contains(error.getSubject()))
{
retained_errors.add(error);
}
}
mValidationErrors = retained_errors;
}
public void addRule(ValidationRule rule)
{
ensureActivatedValidation();
if (null == rule)
{
return;
}
if (null == mValidationRules)
{
mValidationRules = new ArrayList<ValidationRule>();
}
if (null == mValidatedSubjects)
{
mValidatedSubjects = new ArrayList<String>();
}
if (null == rule.getBean())
{
rule.setBean(retrieveValidatedBean());
}
mValidationRules.add(rule);
String subject = rule.getSubject();
if (!mValidatedSubjects.contains(subject))
{
mValidatedSubjects.add(subject);
}
}
private ValidationRule addConstrainedPropertyRule(P constrainedProperty, PropertyValidationRule rule)
{
rule.setConstrainedProperty(constrainedProperty);
rule.setSubject(constrainedProperty.getSubjectName());
addRule(rule);
return rule;
}
public List<PropertyValidationRule> generateConstrainedPropertyRules(P constrainedProperty)
{
List<PropertyValidationRule> rules = new ArrayList<PropertyValidationRule>();
if (constrainedProperty.isNotNull())
{
rules.add(new ValidationRuleNotNull(constrainedProperty.getPropertyName()));
}
if (constrainedProperty.isNotEmpty())
{
rules.add(new ValidationRuleNotEmpty(constrainedProperty.getPropertyName()));
}
if (constrainedProperty.isNotEqual())
{
rules.add(new ValidationRuleNotEqual(constrainedProperty.getPropertyName(), constrainedProperty.getNotEqual()));
}
if (constrainedProperty.hasLimitedLength())
{
rules.add(new ValidationRuleLimitedLength(constrainedProperty.getPropertyName(), constrainedProperty.getMinLength(), constrainedProperty.getMaxLength()));
}
if (constrainedProperty.isEmail())
{
rules.add(new ValidationRuleEmail(constrainedProperty.getPropertyName()));
}
if (constrainedProperty.isUrl())
{
rules.add(new ValidationRuleUrl(constrainedProperty.getPropertyName()));
}
if (constrainedProperty.matchesRegexp())
{
rules.add(new ValidationRuleRegexp(constrainedProperty.getPropertyName(), constrainedProperty.getRegexp()));
}
if (constrainedProperty.isLimitedDate())
{
rules.add(new ValidationRuleLimitedDate(constrainedProperty.getPropertyName(), constrainedProperty.getMinDate(), constrainedProperty.getMaxDate()));
}
if (constrainedProperty.isInList())
{
rules.add(new ValidationRuleInList(constrainedProperty.getPropertyName(), constrainedProperty.getInList()));
}
if (constrainedProperty.isRange())
{
rules.add(new ValidationRuleRange(constrainedProperty.getPropertyName(), constrainedProperty.getRangeBegin(), constrainedProperty.getRangeEnd()));
}
if (constrainedProperty.isSameAs())
{
rules.add(new ValidationRuleSameAs(constrainedProperty.getPropertyName(), constrainedProperty.getSameAs()));
}
if (constrainedProperty.isFormatted())
{
rules.add(new ValidationRuleFormat(constrainedProperty.getPropertyName(), constrainedProperty.getFormat()));
}
if (constrainedProperty.hasMimeType())
{
PropertyValidationRule rule = constrainedProperty.getMimeType().getValidationRule(constrainedProperty);
if (rule != null)
{
rules.add(rule);
}
}
return rules;
}
public List<PropertyValidationRule> addConstrainedPropertyRules(P constrainedProperty)
{
ensureActivatedValidation();
if (null == constrainedProperty)
{
return null;
}
if (null == mConstrainedProperties)
{
mConstrainedProperties = new LinkedHashMap<String, P>();
}
// store the constrained property and obtain the old one if it exists
P old_constrained_property = mConstrainedProperties.put(constrainedProperty.getPropertyName(), constrainedProperty);
if (old_constrained_property != null &&
mValidationRules != null)
{
// obtain all validation rules that were generated by the old constrained property
ArrayList<ValidationRule> rules_to_remove = new ArrayList<ValidationRule>();
for (ValidationRule rule : mValidationRules)
{
if (rule instanceof PropertyValidationRule)
{
if (old_constrained_property == ((PropertyValidationRule)rule).getConstrainedProperty())
{
rules_to_remove.add(rule);
}
}
}
// remove all validation rules that were generated by the old constrained property
mValidationRules.removeAll(rules_to_remove);
// merge constraints
Map<String, Object> merged_constraints = new HashMap<String, Object>(old_constrained_property.getConstraints());
merged_constraints.putAll(constrainedProperty.getConstraints());
constrainedProperty.getConstraints().putAll(merged_constraints);
}
// add the validation rules of the new constrained property
List<PropertyValidationRule> rules = generateConstrainedPropertyRules(constrainedProperty);
for (PropertyValidationRule rule : rules)
{
addConstrainedPropertyRule(constrainedProperty, rule);
}
// register which constraint names are active
if (null == mActivePropertyConstraints)
{
mActivePropertyConstraints = new HashSet<String>();
}
synchronized (mActivePropertyConstraints)
{
mActivePropertyConstraints.addAll(constrainedProperty.getConstraints().keySet());
}
// register this validation object as the listener of future constraint additions to the property
constrainedProperty.addListener(this);
// unregister this bean from the old constrained property
if (old_constrained_property != null)
{
old_constrained_property.removeListener(this);
}
return rules;
}
public void addConstraint(P constrainedProperty)
{
addConstrainedPropertyRules(constrainedProperty);
}
public void addConstraint(B constrainedBean)
{
if (null == constrainedBean)
{
return;
}
if (mConstrainedBean != null)
{
HashMap<String, Object> merged_constraints = mConstrainedBean.getConstraints();
merged_constraints.putAll(constrainedBean.getConstraints());
mConstrainedBean.getConstraints().putAll(merged_constraints);
}
mConstrainedBean = constrainedBean;
}
public void addValidationError(ValidationError newError)
{
if (null == newError)
{
return;
}
if (null == mValidationErrors)
{
mValidationErrors = new LinkedHashSet<ValidationError>();
}
if (mErrorLimitedSubjects != null &&
mErrorLimitedSubjects.contains(newError.getSubject()) &&
!isSubjectValid(newError.getSubject()))
{
return;
}
// Handle the overridable errors.
ValidationError error_to_remove = null;
for (ValidationError error : mValidationErrors)
{
if (error.getSubject().equals(newError.getSubject()))
{
// If the new error is overridable, don't add it since there's already
// and error present for the same subject.
if (newError.isOverridable())
{
return;
}
// If an error is present that is overridable, remember it so that it
// can be removed when the new error is added.
else if (error.isOverridable())
{
error_to_remove = error;
break;
}
}
}
if (error_to_remove != null)
{
mValidationErrors.remove(error_to_remove);
}
mValidationErrors.add(newError);
}
public List<ValidationRule> getRules()
{
ensureActivatedValidation();
if (null == mValidationRules)
{
mValidationRules = new ArrayList<ValidationRule>();
}
return mValidationRules;
}
public Collection<P> getConstrainedProperties()
{
ensureActivatedValidation();
if (null == mConstrainedProperties)
{
mConstrainedProperties = new LinkedHashMap<String, P>();
}
return mConstrainedProperties.values();
}
public boolean hasPropertyConstraint(String name)
{
if (null == mActivePropertyConstraints)
{
return false;
}
return mActivePropertyConstraints.contains(name);
}
public P getConstrainedProperty(String propertyName)
{
ensureActivatedValidation();
if (null == propertyName ||
0 == propertyName.length() ||
null == mConstrainedProperties)
{
return null;
}
return mConstrainedProperties.get(propertyName);
}
public Collection<ValidationGroup<P>> getGroups()
{
ensureActivatedValidation();
if (null == mValidationGroups)
{
mValidationGroups = new HashMap<String, ValidationGroup<P>>();
}
return mValidationGroups.values();
}
public ValidationGroup<P> getGroup(String name)
{
ensureActivatedValidation();
if (null == name ||
0 == name.length() ||
null == mValidationGroups)
{
return null;
}
return mValidationGroups.get(name);
}
public B getConstrainedBean()
{
ensureActivatedValidation();
return mConstrainedBean;
}
private boolean validateSubjects(List<String> subjects)
{
ensureActivatedValidation();
if (mValidationRules != null &&
mValidationRules.size() > 0)
{
for (ValidationRule rule : mValidationRules)
{
if (subjects != null &&
!subjects.contains(rule.getSubject()))
{
continue;
}
if (!rule.validate())
{
addValidationError(rule.getError());
}
}
}
return 0 == countValidationErrors();
}
public boolean validate()
{
return validateSubjects(null);
}
public boolean validate(ValidationContext context)
{
if (context != null)
{
context.validate(retrieveValidatedBean());
}
validateSubjects(null);
return 0 == countValidationErrors();
}
public boolean validateGroup(String name)
{
return validateGroup(name, null);
}
public boolean validateGroup(String name, ValidationContext context)
{
ensureActivatedValidation();
if (null == name ||
null == mValidationGroups)
{
return true;
}
List<String> subjects = null;
ValidationGroup<P> group = mValidationGroups.get(name);
if (group != null)
{
subjects = group.getSubjects();
}
if (null == subjects)
{
return true;
}
if (context != null)
{
context.validate(retrieveValidatedBean());
}
return validateSubjects(subjects);
}
public int countValidationErrors()
{
if (null == mValidationErrors)
{
return 0;
}
return mValidationErrors.size();
}
public void resetValidation()
{
if (mValidationErrors != null)
{
mValidationErrors = new LinkedHashSet<ValidationError>();
}
}
public Set<ValidationError> getValidationErrors()
{
if (null == mValidationErrors)
{
mValidationErrors = new LinkedHashSet<ValidationError>();
}
return mValidationErrors;
}
public void replaceValidationErrors(Set<ValidationError> errors)
{
mValidationErrors = errors;
}
public void limitSubjectErrors(String subject)
{
if (null == subject)
{
return;
}
if (null == mErrorLimitedSubjects)
{
mErrorLimitedSubjects = new ArrayList<String>();
}
if (!mErrorLimitedSubjects.contains(subject))
{
mErrorLimitedSubjects.add(subject);
}
}
public void unlimitSubjectErrors(String subject)
{
if (null == subject)
{
return;
}
if (null == mErrorLimitedSubjects)
{
return;
}
mErrorLimitedSubjects.remove(subject);
}
public boolean isSubjectValid(String subject)
{
if (null == subject)
{
return true;
}
if (null == mValidationErrors)
{
return true;
}
boolean valid = true;
for (ValidationError error : mValidationErrors)
{
if (error.getSubject().equals(subject))
{
valid = false;
break;
}
}
return valid;
}
public void makeSubjectValid(String subject)
{
if (null == subject)
{
return;
}
if (null == mValidationErrors)
{
return;
}
ArrayList<ValidationError> errors_to_remove = new ArrayList<ValidationError>();
for (ValidationError error : mValidationErrors)
{
if (error.getSubject().equals(subject))
{
errors_to_remove.add(error);
}
}
for (ValidationError error_to_remove : errors_to_remove)
{
mValidationErrors.remove(error_to_remove);
}
}
public void makeErrorValid(String identifier, String subject)
{
if (null == subject)
{
return;
}
if (null == identifier)
{
return;
}
if (null == mValidationErrors)
{
return;
}
ArrayList<ValidationError> errors_to_remove = new ArrayList<ValidationError>();
for (ValidationError error : mValidationErrors)
{
if (error.getSubject().equals(subject) &&
error.getIdentifier().equals(identifier))
{
errors_to_remove.add(error);
}
}
for (ValidationError error_to_remove : errors_to_remove)
{
mValidationErrors.remove(error_to_remove);
}
}
public List<String> getValidatedSubjects()
{
ensureActivatedValidation();
if (null == mValidatedSubjects)
{
mValidatedSubjects = new ArrayList<String>();
}
return mValidatedSubjects;
}
public static String getErrorIndication(Validated validated, String subject, String valid, String error)
{
if (null != validated &&
!validated.isSubjectValid(subject))
{
return error;
}
else
{
return valid;
}
}
public Collection<String> getLoadingErrors(String propertyName)
{
if (null == propertyName)
{
return null;
}
for (ValidationRule rule : getRules())
{
if (rule instanceof PropertyValidationRule)
{
PropertyValidationRule property_rule = (PropertyValidationRule)rule;
if (propertyName.equals(property_rule.getPropertyName()) &&
property_rule.getLoadingErrors() != null &&
property_rule.getLoadingErrors().size() > 0)
{
return property_rule.getLoadingErrors();
}
}
}
return null;
}
public void constraintSet(ConstrainedProperty property, String name, Object constraintData)
{
if (null == mActivePropertyConstraints)
{
mActivePropertyConstraints = new HashSet<String>();
}
mActivePropertyConstraints.add(name);
}
public Object clone()
throws CloneNotSupportedException
{
Validation new_validation = null;
try
{
new_validation = (Validation)super.clone();
if (mValidationRules != null)
{
new_validation.mValidationRules = ObjectUtils.deepClone(mValidationRules);
for (ValidationRule rule : (ArrayList<ValidationRule>)new_validation.mValidationRules)
{
if (this == rule.getBean())
{
rule.setBean(new_validation);
}
}
}
if (this == new_validation.mValidatedBean)
{
new_validation.mValidatedBean = new_validation;
}
if (mValidationErrors != null)
{
new_validation.mValidationErrors = new LinkedHashSet<ValidationError>(mValidationErrors);
}
if (mConstrainedProperties != null)
{
new_validation.mConstrainedProperties = new LinkedHashMap<String, P>();
for (Map.Entry<String, P> entry_property : mConstrainedProperties.entrySet())
{
new_validation.mConstrainedProperties.put(entry_property.getKey(), entry_property.getValue());
new_validation.addConstraint(entry_property.getValue().clone());
}
}
if (mActivePropertyConstraints != null)
{
new_validation.mActivePropertyConstraints = new HashSet<String>(mActivePropertyConstraints);
}
if (mErrorLimitedSubjects != null)
{
new_validation.mErrorLimitedSubjects = new ArrayList<String>(mErrorLimitedSubjects);
}
if (mValidationGroups != null)
{
new_validation.mValidationGroups = new HashMap<String, ValidationGroup<P>>();
ValidationGroup<P> new_group;
for (ValidationGroup<P> group : mValidationGroups.values())
{
new_group = group.clone();
new_group.setValidation(new_validation);
new_validation.mValidationGroups.put(new_group.getName(), new_group);
}
}
}
catch (CloneNotSupportedException e)
{
///CLOVER:OFF
// this should never happen
Logger.getLogger("com.uwyn.rife.site").severe(ExceptionUtils.getExceptionStackTrace(e));
///CLOVER:ON
}
return new_validation;
}
}