001    /*
002     * Copyright 2002-2004 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.rules;
017    
018    import java.util.ArrayList;
019    import java.util.HashMap;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.commons.logging.Log;
025    import org.apache.commons.logging.LogFactory;
026    import org.springframework.beans.factory.InitializingBean;
027    import org.springframework.rules.constraint.Constraint;
028    import org.springframework.core.style.ToStringCreator;
029    import org.springframework.rules.constraint.*;
030    import org.springframework.rules.constraint.property.CompoundPropertyConstraint;
031    import org.springframework.rules.constraint.property.PropertyConstraint;
032    import org.springframework.rules.constraint.property.PropertyValueConstraint;
033    import org.springframework.util.Assert;
034    import org.springframework.validation.Errors;
035    import org.springframework.validation.Validator;
036    
037    /**
038     * A factory for creating rules.
039     * 
040     * @author Keith Donald
041     */
042    public class Rules extends ConstraintsAccessor implements Constraint, PropertyConstraintProvider, Validator,
043                    InitializingBean {
044            private static final Log logger = LogFactory.getLog(Rules.class);
045    
046            private Class domainObjectType;
047    
048        /** All constraints keyed by property name. */
049            private Map propertiesConstraints = new HashMap();
050        
051        /** Used to track the order in which rules are added so they can be evaluated
052         * in that same sequence.
053         */
054        private List orderedConstraints = new ArrayList();
055    
056            public Rules() {
057    
058            }
059    
060            public Rules(Class domainObjectClass) {
061                    setDomainObjectType(domainObjectClass);
062            }
063    
064            public Rules(Class domainObjectClass, Map propertiesConstraints) {
065                    setDomainObjectType(domainObjectClass);
066                    setPropertiesConstraints(propertiesConstraints);
067            }
068    
069            public void setDomainObjectType(Class domainObjectType) {
070                    Assert.notNull(domainObjectType, "The domainObjectType property is required");
071                    this.domainObjectType = domainObjectType;
072            }
073    
074            public Class getDomainObjectType() {
075                    return domainObjectType;
076            }
077    
078            public void afterPropertiesSet() {
079                    initRules();
080            }
081    
082            protected void initRules() {
083    
084            }
085    
086            public void setPropertiesConstraints(Map propertiesConstraints) {
087                    for (Iterator i = propertiesConstraints.entrySet().iterator(); i.hasNext();) {
088                            Map.Entry entry = (Map.Entry)i.next();
089                            String propertyName = (String)entry.getKey();
090                            Object value = entry.getValue();
091                            if (value instanceof List) {
092                                    add(propertyName, (Constraint[])((List)value).toArray(new Constraint[0]));
093                            }
094                            else if (value instanceof PropertyConstraint) {
095                                    add((PropertyConstraint)value);
096                            }
097                            else if (value instanceof Constraint) {
098                                    add(propertyName, (Constraint)value);
099                            }
100                    }
101            }
102    
103        /**
104         * Put a constraint into the collection.  Store it in the map under the property
105         * name and add it to the ordered list.
106         * 
107         * @param constraint to add
108         */
109            private void putPropertyConstraint(PropertyConstraint constraint) {
110                    And and = new And();
111                    and.add(constraint);
112                    if (logger.isDebugEnabled()) {
113                            logger.debug("Putting constraint for property '" + constraint.getPropertyName() + "', constraint -> ["
114                                            + constraint + "]");
115                    }
116            PropertyConstraint compoundConstraint = new CompoundPropertyConstraint(and);
117                    propertiesConstraints.put(constraint.getPropertyName(), compoundConstraint);
118            orderedConstraints.add( compoundConstraint );
119            }
120    
121            public PropertyConstraint getPropertyConstraint(String property) {
122                    if (propertiesConstraints.isEmpty()) {
123                            initRules();
124                    }
125                    return (PropertyConstraint)propertiesConstraints.get(property);
126            }
127    
128            public Iterator iterator() {
129            if (orderedConstraints.isEmpty()) {
130                initRules();
131            }
132                    return orderedConstraints.iterator();
133            }
134    
135            /**
136             * Adds the provided bean property expression (constraint) to the list of
137             * constraints for the constrained property.
138             * 
139             * @param constraint
140             *            the bean property expression
141             * @return this, to support chaining.
142             */
143            public Rules add(PropertyConstraint constraint) {
144                    CompoundPropertyConstraint and = (CompoundPropertyConstraint)propertiesConstraints.get(constraint
145                                    .getPropertyName());
146                    if (and == null) {
147                            putPropertyConstraint(constraint);
148                    }
149                    else {
150                            and.add(constraint);
151                    }
152                    return this;
153            }
154    
155            /**
156             * Adds a value constraint for the specified property.
157             * 
158             * @param propertyName
159             *            The property name.
160             * @param valueConstraint
161             *            The value constraint.
162             */
163            public void add(String propertyName, Constraint valueConstraint) {
164                    add(new PropertyValueConstraint(propertyName, valueConstraint));
165            }
166    
167            /**
168             * Adds a value constraint for the specified property.
169             * 
170             * @param propertyName
171             *            The property name.
172             * @param valueConstraint
173             *            The value constraint.
174             */
175            public void add(String propertyName, Constraint[] valueConstraints) {
176                    add(new PropertyValueConstraint(propertyName, all(valueConstraints)));
177            }
178    
179            public void addRequired(String propertyName) {
180                    add(propertyName, required());
181            }
182    
183            public void addRequired(String propertyName, Constraint otherConstraints) {
184                    add(propertyName, and(required(), otherConstraints));
185            }
186    
187            public void addMaxLength(String propertyName, int maxLength) {
188                    add(propertyName, maxLength(maxLength));
189            }
190    
191            public void addMinLength(String propertyName, int minLength) {
192                    add(propertyName, minLength(minLength));
193            }
194    
195            public void addRange(String propertyName, Range range) {
196                    add(propertyName, range);
197            }
198    
199            /**
200             * Adds the provided compound predicate, composed of BeanPropertyExpression
201             * objects, as a bean property constraint.
202             * 
203             * @param compoundPredicate
204             */
205            public void add(CompoundConstraint compoundPredicate) {
206                    add(new CompoundPropertyConstraint(compoundPredicate));
207            }
208    
209            public boolean test(Object bean) {
210                    for (Iterator i = orderedConstraints.iterator(); i.hasNext();) {
211                            PropertyConstraint propertyConstraint = (PropertyConstraint)i.next();
212                            if (!propertyConstraint.test(bean)) {
213                                    return false;
214                            }
215                    }
216                    return true;
217            }
218    
219            public boolean supports(Class type) {
220                    return this.domainObjectType.isAssignableFrom(type);
221            }
222    
223            public void validate(final Object bean, final Errors errors) {
224    
225            }
226    
227            public String toString() {
228                    return new ToStringCreator(this).append("domainObjectClass", domainObjectType).append("propertyRules",
229                                    propertiesConstraints).toString();
230            }
231    
232    }