grails.gorm.validation.DefaultConstrainedProperty.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grails-datastore-gorm-validation Show documentation
Show all versions of grails-datastore-gorm-validation Show documentation
GORM - Grails Data Access Framework
package grails.gorm.validation
import grails.gorm.validation.exceptions.ValidationConfigurationException
import groovy.transform.CompileStatic
import groovy.transform.ToString
import groovy.util.logging.Slf4j
import org.grails.datastore.gorm.validation.constraints.BlankConstraint
import org.grails.datastore.gorm.validation.constraints.InListConstraint
import org.grails.datastore.gorm.validation.constraints.MatchesConstraint
import org.grails.datastore.gorm.validation.constraints.MaxConstraint
import org.grails.datastore.gorm.validation.constraints.MaxSizeConstraint
import org.grails.datastore.gorm.validation.constraints.MinConstraint
import org.grails.datastore.gorm.validation.constraints.MinSizeConstraint
import org.grails.datastore.gorm.validation.constraints.NotEqualConstraint
import org.grails.datastore.gorm.validation.constraints.NullableConstraint
import org.grails.datastore.gorm.validation.constraints.RangeConstraint
import org.grails.datastore.gorm.validation.constraints.ScaleConstraint
import org.grails.datastore.gorm.validation.constraints.SizeConstraint
import org.grails.datastore.gorm.validation.constraints.factory.ConstraintFactory
import org.grails.datastore.gorm.validation.constraints.registry.ConstraintRegistry
import org.grails.datastore.mapping.reflect.ClassPropertyFetcher
import org.springframework.validation.Errors
/**
* Default implementation of the {@link ConstrainedProperty} interface
*
* @author Graeme Rocher
* @since 6.0
*/
@CompileStatic
@Slf4j
@ToString(includes = ['ownerClass', 'propertyType', 'propertyName', 'appliedConstraints'])
class DefaultConstrainedProperty implements ConstrainedProperty {
/**
* The owning class
*/
final Class> owningClass
/**
* The property type
*/
final Class> propertyType
/**
* the property name
*/
final String propertyName
protected final ConstraintRegistry constraintRegistry
protected final Map appliedConstraints = new LinkedHashMap()
// simple constraints
/** whether the property should be displayed */
boolean display = true
/**
* whether the property is editable
*/
boolean editable = true
/**
* The order of the property
*/
int order
/**
* the format of the property (for example a date pattern)
*/
String format
/**
* The widget to use to render the property
*/
String widget
/**
* whether the property is a password
*/
boolean password
Map attributes = Collections.EMPTY_MAP; // a map of attributes of property
private Map metaConstraints = new HashMap()
private final static ClassPropertyFetcher PROPERTY_FETCHER = ClassPropertyFetcher.forClass(DefaultConstrainedProperty)
/**
* Constructs a new ConstrainedProperty for the given arguments.
*
* @param owningClass The owning class
* @param propertyName The name of the property
* @param propertyType The property type
*/
DefaultConstrainedProperty(Class> owningClass, String propertyName, Class> propertyType, ConstraintRegistry constraintRegistry) {
this.owningClass = owningClass
this.propertyName = propertyName
this.propertyType = propertyType
this.constraintRegistry = constraintRegistry
}
/**
* @return Returns the appliedConstraints.
*/
public Collection getAppliedConstraints() {
return appliedConstraints.values();
}
/**
* Obtains an applied constraint by name.
* @param name The name of the constraint
* @return The applied constraint
*/
public Constraint getAppliedConstraint(String name) {
return appliedConstraints.get(name);
}
/**
* @param constraintName The name of the constraint to check
* @return Returns true if the specified constraint name is being applied to this property
*/
@Override
public boolean hasAppliedConstraint(String constraintName) {
return appliedConstraints.containsKey(constraintName);
}
/**
* @return Returns the max.
*/
@Override
public Comparable getMax() {
Comparable maxValue = null;
MaxConstraint maxConstraint = (MaxConstraint)appliedConstraints.get(MAX_CONSTRAINT);
RangeConstraint rangeConstraint = (RangeConstraint)appliedConstraints.get(RANGE_CONSTRAINT);
if (maxConstraint != null || rangeConstraint != null) {
Comparable maxConstraintValue = maxConstraint == null ? null : maxConstraint.getMaxValue();
Comparable rangeConstraintHighValue = rangeConstraint == null ? null : rangeConstraint.getRange().getTo();
if (maxConstraintValue != null && rangeConstraintHighValue != null) {
maxValue = (maxConstraintValue.compareTo(rangeConstraintHighValue) < 0) ? maxConstraintValue : rangeConstraintHighValue;
}
else if (maxConstraintValue == null && rangeConstraintHighValue != null) {
maxValue = rangeConstraintHighValue;
}
else if (maxConstraintValue != null && rangeConstraintHighValue == null) {
maxValue = maxConstraintValue;
}
}
return maxValue;
}
/**
* @param max The max to set.
*/
@SuppressWarnings("rawtypes")
public void setMax(Comparable max) {
String constraintName = MAX_CONSTRAINT
if (max == null) {
appliedConstraints.remove(constraintName);
return;
}
if (!propertyType.equals(max.getClass())) {
throw new MissingPropertyException(constraintName,propertyType);
}
Range r = getRange();
if (r != null) {
log.warn("Range constraint already set ignoring constraint [" + constraintName + "] for value [" + max + "]");
return;
}
applyConstraintInternal(constraintName, max)
}
/**
* @return Returns the min.
*/
@Override
public Comparable getMin() {
Comparable minValue = null;
MinConstraint minConstraint = (MinConstraint)appliedConstraints.get(MIN_CONSTRAINT);
RangeConstraint rangeConstraint = (RangeConstraint)appliedConstraints.get(RANGE_CONSTRAINT);
if (minConstraint != null || rangeConstraint != null) {
Comparable minConstraintValue = minConstraint != null ? minConstraint.getMinValue() : null;
Comparable rangeConstraintLowValue = rangeConstraint != null ? rangeConstraint.getRange().getFrom() : null;
if (minConstraintValue != null && rangeConstraintLowValue != null) {
minValue = (minConstraintValue.compareTo(rangeConstraintLowValue) > 0) ? minConstraintValue : rangeConstraintLowValue;
}
else if (minConstraintValue == null && rangeConstraintLowValue != null) {
minValue = rangeConstraintLowValue;
}
else if (minConstraintValue != null && rangeConstraintLowValue == null) {
minValue = minConstraintValue;
}
}
return minValue;
}
/**
* @param min The min to set.
*/
@SuppressWarnings("rawtypes")
public void setMin(Comparable min) {
if (min == null) {
appliedConstraints.remove(MIN_CONSTRAINT);
return;
}
if (!propertyType.equals(min.getClass())) {
throw new MissingPropertyException(MIN_CONSTRAINT,propertyType);
}
Range r = getRange();
if (r != null) {
log.warn("Range constraint already set ignoring constraint ["+MIN_CONSTRAINT+"] for value ["+min+"]");
return
}
applyConstraintInternal(MIN_CONSTRAINT, min)
}
/**
* @return Returns the inList.
*/
@Override
@SuppressWarnings("rawtypes")
public List getInList() {
InListConstraint c = (InListConstraint)appliedConstraints.get(IN_LIST_CONSTRAINT);
return c == null ? null : c.getList();
}
/**
* @param inList The inList to set.
*/
@SuppressWarnings("rawtypes")
public void setInList(List inList) {
Constraint c = appliedConstraints.get(IN_LIST_CONSTRAINT);
if (inList == null) {
appliedConstraints.remove(IN_LIST_CONSTRAINT);
}
else {
applyConstraintInternal(IN_LIST_CONSTRAINT, inList)
}
}
/**
* @return Returns the range.
*/
@Override
@SuppressWarnings("rawtypes")
public Range getRange() {
RangeConstraint c = (RangeConstraint)appliedConstraints.get(RANGE_CONSTRAINT);
return c == null ? null : c.getRange();
}
/**
* @param range The range to set.
*/
@SuppressWarnings("rawtypes")
public void setRange(Range range) {
if (appliedConstraints.containsKey(MAX_CONSTRAINT)) {
log.warn("Setting range constraint on property ["+propertyName+"] of class ["+ this.owningClass +"] forced removal of max constraint");
appliedConstraints.remove(MAX_CONSTRAINT)
}
if (appliedConstraints.containsKey(MIN_CONSTRAINT)) {
log.warn("Setting range constraint on property ["+propertyName+"] of class ["+ this.owningClass +"] forced removal of min constraint");
appliedConstraints.remove(MIN_CONSTRAINT)
}
if (range == null) {
appliedConstraints.remove(RANGE_CONSTRAINT)
}
else {
applyConstraintInternal(RANGE_CONSTRAINT, range)
}
}
/**
* @return The scale, if defined for this property; null, otherwise
*/
@Override
public Integer getScale() {
ScaleConstraint scaleConstraint = (ScaleConstraint)appliedConstraints.get(SCALE_CONSTRAINT)
return scaleConstraint == null ? null : scaleConstraint.getScale()
}
/**
* @return Returns the size.
*/
@Override
@SuppressWarnings("rawtypes")
public Range getSize() {
SizeConstraint c = (SizeConstraint)appliedConstraints.get(SIZE_CONSTRAINT);
return c == null ? null : c.getRange()
}
/**
* @param size The size to set.
*/
@SuppressWarnings("rawtypes")
public void setSize(Range size) {
Constraint c = appliedConstraints.get(SIZE_CONSTRAINT)
if (size == null) {
appliedConstraints.remove(SIZE_CONSTRAINT)
}
else {
applyConstraintInternal(SIZE_CONSTRAINT, size)
}
}
/**
* @return the blank.
*/
@Override
public boolean isBlank() {
Object cons = appliedConstraints.get(BLANK_CONSTRAINT);
return cons == null || (Boolean)((BlankConstraint)cons).getParameter();
}
/**
* @param blank The blank to set.
*/
public void setBlank(boolean blank) {
if (isNotValidStringType()) {
throw new MissingPropertyException("Blank constraint can only be applied to a String property",
BLANK_CONSTRAINT, this.owningClass)
}
if (!blank) {
applyConstraintInternal(BLANK_CONSTRAINT, blank)
}
else {
appliedConstraints.remove(BLANK_CONSTRAINT);
}
}
/**
* @return Returns the email.
*/
@Override
public boolean isEmail() {
if (isNotValidStringType()) {
throw new MissingPropertyException("Email constraint only applies to a String property",
EMAIL_CONSTRAINT, this.owningClass);
}
return appliedConstraints.containsKey(EMAIL_CONSTRAINT);
}
/**
* @param email The email to set.
*/
public void setEmail(boolean email) {
if (isNotValidStringType()) {
throw new MissingPropertyException("Email constraint can only be applied to a String property",
EMAIL_CONSTRAINT, this.owningClass);
}
Constraint c = appliedConstraints.get(EMAIL_CONSTRAINT);
if (email) {
applyConstraintInternal(EMAIL_CONSTRAINT, email)
}
else {
if (c != null) {
appliedConstraints.remove(EMAIL_CONSTRAINT);
}
}
}
private boolean isNotValidStringType() {
return !CharSequence.class.isAssignableFrom(propertyType);
}
/**
* @return Returns the creditCard.
*/
@Override
public boolean isCreditCard() {
if (isNotValidStringType()) {
throw new MissingPropertyException("CreditCard constraint only applies to a String property",
CREDIT_CARD_CONSTRAINT, this.owningClass);
}
return appliedConstraints.containsKey(CREDIT_CARD_CONSTRAINT);
}
/**
* @param creditCard The creditCard to set.
*/
public void setCreditCard(boolean creditCard) {
if (isNotValidStringType()) {
throw new MissingPropertyException("CreditCard constraint only applies to a String property",
CREDIT_CARD_CONSTRAINT, this.owningClass);
}
Constraint c = appliedConstraints.get(CREDIT_CARD_CONSTRAINT);
if (creditCard) {
applyConstraintInternal(CREDIT_CARD_CONSTRAINT, creditCard)
}
else {
if (c != null) {
appliedConstraints.remove(CREDIT_CARD_CONSTRAINT);
}
}
}
/**
* @return Returns the matches.
*/
@Override
public String getMatches() {
if (isNotValidStringType()) {
throw new MissingPropertyException("Matches constraint only applies to a String property",
MATCHES_CONSTRAINT, this.owningClass)
}
MatchesConstraint c = (MatchesConstraint)appliedConstraints.get(MATCHES_CONSTRAINT)
return c == null ? null : c.regex
}
/**
* @param regex The matches to set.
*/
public void setMatches(String regex) {
if (isNotValidStringType()) {
throw new MissingPropertyException("Matches constraint can only be applied to a String property",
MATCHES_CONSTRAINT, this.owningClass)
}
if (regex == null) {
appliedConstraints.remove(MATCHES_CONSTRAINT)
}
else {
applyConstraintInternal(MATCHES_CONSTRAINT, regex)
}
}
/**
* @return Returns the notEqual.
*/
@Override
public Object getNotEqual() {
NotEqualConstraint c = (NotEqualConstraint)appliedConstraints.get(NOT_EQUAL_CONSTRAINT);
return c == null ? null : c.getNotEqualTo();
}
/**
* @return Returns the maxSize.
*/
@Override
public Integer getMaxSize() {
Integer maxSize = null;
MaxSizeConstraint maxSizeConstraint = (MaxSizeConstraint)appliedConstraints.get(MAX_SIZE_CONSTRAINT);
SizeConstraint sizeConstraint = (SizeConstraint)appliedConstraints.get(SIZE_CONSTRAINT);
if (maxSizeConstraint != null || sizeConstraint != null) {
int maxSizeConstraintValue = maxSizeConstraint == null ? Integer.MAX_VALUE : maxSizeConstraint.getMaxSize();
int sizeConstraintHighValue = sizeConstraint == null ? Integer.MAX_VALUE : sizeConstraint.getRange().getToInt();
maxSize = Math.min(maxSizeConstraintValue, sizeConstraintHighValue);
}
return maxSize;
}
/**
* @param maxSize The maxSize to set.
*/
public void setMaxSize(Integer maxSize) {
applyConstraintInternal(MAX_SIZE_CONSTRAINT, maxSize)
}
/**
* @return Returns the minSize.
*/
@Override
public Integer getMinSize() {
Integer minSize = null;
MinSizeConstraint minSizeConstraint = (MinSizeConstraint)appliedConstraints.get(MIN_SIZE_CONSTRAINT);
SizeConstraint sizeConstraint = (SizeConstraint)appliedConstraints.get(SIZE_CONSTRAINT);
if (minSizeConstraint != null || sizeConstraint != null) {
int minSizeConstraintValue = minSizeConstraint == null ? Integer.MIN_VALUE : minSizeConstraint.getMinSize();
int sizeConstraintLowValue = sizeConstraint == null ? Integer.MIN_VALUE : sizeConstraint.getRange().getFromInt();
minSize = Math.max(minSizeConstraintValue, sizeConstraintLowValue);
}
return minSize;
}
/**
* @param minSize The minLength to set.
*/
public void setMinSize(Integer minSize) {
applyConstraintInternal(MIN_SIZE_CONSTRAINT, minSize)
}
/**
* @param notEqual The notEqual to set.
*/
public void setNotEqual(Object notEqual) {
if (notEqual == null) {
appliedConstraints.remove(NOT_EQUAL_CONSTRAINT)
}
else {
applyConstraintInternal(NOT_EQUAL_CONSTRAINT, notEqual)
}
}
/**
* @return Returns the nullable.
*/
@Override
public boolean isNullable() {
if (appliedConstraints.containsKey(NULLABLE_CONSTRAINT)) {
NullableConstraint nc = (NullableConstraint)appliedConstraints.get(NULLABLE_CONSTRAINT);
return nc.isNullable()
}
return false
}
/**
* @param nullable The nullable to set.
*/
public void setNullable(boolean nullable) {
applyConstraintInternal(NULLABLE_CONSTRAINT, nullable)
}
/**
* @return Returns the url.
*/
@Override
public boolean isUrl() {
if (isNotValidStringType()) {
throw new MissingPropertyException("URL constraint can only be applied to a String property",
URL_CONSTRAINT, this.owningClass);
}
return appliedConstraints.containsKey(URL_CONSTRAINT);
}
/**
* @param url The url to set.
*/
public void setUrl(boolean url) {
if (isNotValidStringType()) {
throw new MissingPropertyException("URL constraint can only be applied to a String property",URL_CONSTRAINT, this.owningClass);
}
Constraint c = appliedConstraints.get(URL_CONSTRAINT);
if (url) {
applyConstraintInternal(URL_CONSTRAINT, url)
}
else {
if (c != null) {
appliedConstraints.remove(URL_CONSTRAINT)
}
}
}
@SuppressWarnings("rawtypes")
public Map getAttributes() {
return attributes;
}
@SuppressWarnings("rawtypes")
public void setAttributes(Map attributes) {
this.attributes = attributes;
}
/**
* Validate this constrainted property against specified property value
*
* @param target The target object to validate
* @param propertyValue The value of the property to validate
* @param errors The Errors instances to report errors to
*/
public void validate(Object target, Object propertyValue, Errors errors) {
List delayedConstraints = new ArrayList();
// validate only vetoing constraints first, putting non-vetoing into delayedConstraints
for (Constraint c in appliedConstraints.values()) {
if (c instanceof VetoingConstraint) {
// stop validation process when constraint vetoes
if (((VetoingConstraint)c).validateWithVetoing(target, propertyValue, errors)) {
return
}
}
else {
delayedConstraints.add(c)
}
}
// process non-vetoing constraints
for (Constraint c : delayedConstraints) {
c.validate(target, propertyValue, errors)
}
}
/**
* Checks with this ConstraintedProperty instance supports applying the specified constraint.
*
* @param constraintName The name of the constraint
* @return true if the constraint is supported
*/
@Override
public boolean supportsContraint(String constraintName) {
List constraintFactories = constraintRegistry.findConstraintFactories(constraintName)
if (constraintFactories.isEmpty()) {
return PROPERTY_FETCHER.getPropertyDescriptor(constraintName)?.getWriteMethod() != null;
}
try {
for(ConstraintFactory cf in constraintFactories) {
if(cf.supports(propertyType)) {
return true
}
}
}
catch (Exception e) {
log.error("Exception thrown instantiating constraint [$constraintName] to class [$owningClass]", e)
throw new ValidationConfigurationException("Exception thrown instantiating constraint [$constraintName] to class [$owningClass]: ${e.message}", e)
}
}
/**
* Applies a constraint for the specified name and consraint value.
*
* @param constraintName The name of the constraint
* @param constrainingValue The constraining value
*
* @throws ValidationConfigurationException Thrown when the specified constraint is not supported by this ConstrainedProperty. Use supportsContraint(String constraintName)
to check before calling
*/
@Override
public void applyConstraint(String constraintName, Object constrainingValue) {
List constraintFactories = constraintRegistry.findConstraintFactories(constraintName)
if (!constraintFactories.isEmpty()) {
if (constrainingValue == null) {
appliedConstraints.remove(constraintName);
}
else {
try {
Constraint c = instantiateConstraint(constraintName, constrainingValue, true);
if (c != null) {
appliedConstraints.put(constraintName, c);
}
}
catch (Exception e) {
throw new ValidationConfigurationException("Exception thrown applying constraint [$constraintName] to class [$owningClass] for value [$constrainingValue]: ${e.message}", e)
}
}
}
else if (hasProperty(constraintName)) {
((GroovyObject)this).setProperty(constraintName, constrainingValue)
}
else {
throw new ValidationConfigurationException("Constraint [$constraintName] is not supported for property [$propertyName] of class [$owningClass] with type [$propertyType]")
}
}
protected void applyConstraintInternal(String constraintName, Object constrainingValue) {
Constraint c = appliedConstraints.get(constraintName)
if (c == null) {
for (factory in constraintRegistry.findConstraintFactories(constraintName)) {
c = factory.build(owningClass, propertyName, constrainingValue)
if (c != null && c.supports(propertyType)) {
appliedConstraints.put(constraintName, c)
break
}
}
}
}
@Override
public Class getOwner() {
return this.owningClass;
}
private Constraint instantiateConstraint(String constraintName, Object constraintValue, boolean validate) throws InstantiationException, IllegalAccessException {
List candidateConstraints = constraintRegistry.findConstraintFactories(constraintName);
for (ConstraintFactory constraintFactory in candidateConstraints) {
Constraint c = constraintFactory.build(owningClass, propertyName, constraintValue)
if (validate && c.isValid()) {
return c
}
if (!validate) {
return c
}
}
return null
}
/**
* Adds a meta constraints which is a non-validating informational constraint.
*
* @param name The name of the constraint
* @param value The value
*/
public void addMetaConstraint(String name, Object value) {
metaConstraints.put(name, value);
}
/**
* Obtains the value of the named meta constraint.
* @param name The name of the constraint
* @return The value
*/
public Object getMetaConstraintValue(String name) {
return metaConstraints.get(name);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy