View Javadoc

1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.domain.validation;
17  
18  import org.acegisecurity.domain.dao.DetachmentContextHolder;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import org.springframework.beans.factory.InitializingBean;
24  
25  import org.springframework.util.Assert;
26  
27  import org.springframework.validation.BindException;
28  import org.springframework.validation.Errors;
29  import org.springframework.validation.Validator;
30  
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Vector;
34  
35  
36  /***
37   * Default implementation of {@link ValidationManager}.
38   *
39   * @author Ben Alex
40   * @author Matthew E. Porter
41   * @version $Id: ValidationManagerImpl.java,v 1.9 2005/11/17 00:55:50 benalex Exp $
42   */
43  public class ValidationManagerImpl implements InitializingBean,
44      ValidationManager {
45      //~ Instance fields ========================================================
46  
47      protected final Log logger = LogFactory.getLog(getClass());
48      private IntrospectionManager introspectionManager;
49      private ValidationRegistryManager validationRegistryManager = new ValidationRegistryManagerImpl();
50      private boolean strictValidation = true;
51  
52      //~ Methods ================================================================
53  
54      public void setIntrospectionManager(
55          IntrospectionManager introspectionManager) {
56          this.introspectionManager = introspectionManager;
57      }
58  
59      public IntrospectionManager getIntrospectionManager() {
60          return introspectionManager;
61      }
62  
63      /***
64       * Indicates whether a {@link ValidatorNotFoundException} should be thrown
65       * if any domain object does not have a corresponding
66       * <code>Validator</code>.
67       * 
68       * <p>
69       * Defaults to <code>true</code>. This is a reasonable default, as callers
70       * of <code>ValidationManager</code> should expect the object to support
71       * validation.
72       * </p>
73       *
74       * @param strictValidation set to <code>false</code> if you wish to
75       *        silently ignore any domain object that is missing a
76       *        <code>Validator</code>
77       */
78      public void setStrictValidation(boolean strictValidation) {
79          this.strictValidation = strictValidation;
80      }
81  
82      public boolean isStrictValidation() {
83          return strictValidation;
84      }
85  
86      public void setValidationRegistryManager(
87          ValidationRegistryManager validationRegistryManager) {
88          this.validationRegistryManager = validationRegistryManager;
89      }
90  
91      public ValidationRegistryManager getValidationRegistryManager() {
92          return validationRegistryManager;
93      }
94  
95      public void afterPropertiesSet() throws Exception {
96          Assert.notNull(validationRegistryManager,
97              "A ValidationRegistryManager is required");
98          Assert.notNull(introspectionManager,
99              "An IntrospectionManager is required");
100     }
101 
102     /***
103      * Validates the passed domain object, along with any children,
104      * grandchildren, great-grandchildren etc.
105      *
106      * @param domainObject to validate (cannot be <code>null</code>)
107      *
108      * @throws BindException if a validation problem occurs
109      * @throws ValidatorNotFoundException if no matching <code>Validator</code>
110      *         could be found for the object or its children (only ever thrown
111      *         if the {@link #strictValidation}) was set to
112      *         <code>true</code>).
113      */
114     public void validate(Object domainObject)
115         throws BindException, ValidatorNotFoundException {
116         // Abort if null
117         Assert.notNull(domainObject,
118             "Cannot validate a null domain object, as unable to getClass()");
119 
120         // Construct a list of objects to be validated and adds self
121         List<Object> allObjects = new Vector<Object>();
122         allObjects.add(domainObject);
123 
124         // Add all children (and grandchildren, great-grandchildren etc)
125         // of domain object to the list of objects to be validated
126         // (list never contains null)
127         obtainAllChildren(domainObject, allObjects);
128 
129         Assert.notEmpty(allObjects,
130             "The list of objects to be validated was empty");
131 
132         // Process list of objects to be validated by validating each
133         Iterator<Object> iter = allObjects.iterator();
134 
135         while (iter.hasNext()) {
136             Object currentDomainObject = iter.next();
137             Class clazz = currentDomainObject.getClass();
138 
139             DetachmentContextHolder.setForceReturnOfDetachedInstances(true);
140             try {
141                 // Call bindSupport() if this class wishes
142                 BindBeforeValidationUtils.bindIfRequired(currentDomainObject);
143 
144                 Errors errors = new BindException(currentDomainObject,
145                         clazz.getName());
146                 Validator v = findValidator(clazz);
147 
148                 // Perform validation
149                 v.validate(currentDomainObject, errors);
150 
151                 // Handle validation outcome
152                 if (errors.getErrorCount() == 0) {
153                     if (logger.isDebugEnabled()) {
154                         logger.debug("Validated '" + clazz + "' successfully using '"
155 							+ v.getClass() + "'");
156                     }
157                 } else {
158                     if (logger.isDebugEnabled()) {
159                         logger.debug("Validated '" + clazz + "' using '" + v.getClass()
160                             + "' but errors detected");
161                     }
162 
163                     throw (BindException) errors;
164                 }
165             } catch (ValidatorNotFoundException validatorNotFoundException) {
166                 if (strictValidation) {
167                     if (logger.isErrorEnabled()) {
168                         logger.error(validatorNotFoundException);
169                     }
170 
171                     throw validatorNotFoundException;
172                 }
173 
174                 if (logger.isDebugEnabled()) {
175                     logger.debug("Could not locate validator for class '"
176                         + clazz + "'; skipping without error");
177                 }
178             } finally {
179             	DetachmentContextHolder.setForceReturnOfDetachedInstances(false);
180             }
181         }
182     }
183 
184     private Validator findValidator(Class clazz)
185         throws ValidatorNotFoundException {
186         Assert.notNull(clazz, "Class cannot be null");
187 
188         Validator validator = this.validationRegistryManager.findValidator(clazz);
189 
190         if (validator == null) {
191             throw new ValidatorNotFoundException(
192                 "No Validator found for class '" + clazz + "'");
193         }
194 
195         return validator;
196     }
197 
198     /***
199      * Locates all immediate children of the passed <code>parentObject</code>,
200      * adding each of those immediate children to the <code>allObjects</code>
201      * list and then calling this same method for each of those immediate
202      * children.
203      * 
204      * <p>
205      * Does <b>not</b> add the passed <code>parentObject</code> to the
206      * <code>allObjects</code> list. The caller of this method should ensure
207      * the <code>parentObject</code> is added to the list instead.
208      * </p>
209      *
210      * @param parentObject the object we wish to locate all children for
211      * @param allObjects the list to add the located children to
212      */
213     private void obtainAllChildren(Object parentObject, List<Object> allObjects) {
214         Assert.notNull(parentObject, "Violation of parentObject method contract");
215         Assert.notNull(allObjects, "Violation of allObjects method contract");
216         Assert.isTrue(allObjects.contains(parentObject),
217             "List of objects missing the requested parentObject");
218 
219 		if (logger.isDebugEnabled()) {
220 			logger.debug("Searching for children of " + parentObject);
221 		}
222 		
223         // Add immediate children of this domain object
224         List<Object> currentChildren = new Vector<Object>();
225         introspectionManager.obtainImmediateChildren(parentObject,
226             currentChildren);
227 
228         // Now iterate the children, adding their children to the object list
229         Iterator<Object> childrenIter = currentChildren.iterator();
230 
231         while (childrenIter.hasNext()) {
232             Object childObject = childrenIter.next();
233 
234             if (childObject != null) {
235 				if (allObjects.contains(childObject)) {
236 					if (logger.isDebugEnabled()) {
237 						logger.debug("Already processed this object (will not re-add): " + childObject);
238 					}
239 				} else {
240 					if (logger.isDebugEnabled()) {
241 						logger.debug("New child object found; adding child object to list of objects, and searching for its children: " + childObject);
242 					}
243 					allObjects.add(childObject);
244 	                obtainAllChildren(childObject, allObjects);
245 				}
246             }
247         }
248     }
249 }