001 package edu.nrao.sss.validation; 002 003 import java.util.ArrayList; 004 import java.util.HashMap; 005 import java.util.List; 006 import java.util.Map; 007 008 /** 009 * The entry point for validating model objects. 010 * <p> 011 * Most clients need use only the {@link #validate(Object, ValidationPurpose)} 012 * method. A typical usage pattern is:</p> 013 * <div style="margin-left: 2em; margin-right: 18em; border-style: double; 014 * color:darkred; background-color:#F9F9F9; font-weight:bold"> 015 * <pre> 016 * ValidationManager myMgr = new ValidationManager(); 017 * 018 * if (!myMgr.validate(newCar, ValidationPurpose.CERTIFY_READY_TO_USE)) 019 * communicate(myMgr.getFailures());</pre></div> 020 * <p> 021 * <b><u>Two Different Failure Lists</u></b> 022 * <br/> 023 * In the original implementation this class had one list of failures. 024 * If the <tt>validate</tt> method returned <i>true</i> (i.e., success), 025 * the <tt>getFailures()</tt> list would be empty. If it returned 026 * <i>false</i>, it would contain one or more validation failures. 027 * This can cause problems, though, when the only failures in the list 028 * are those that are not intended for display.</p> 029 * <p> 030 * To remedy this problem, 031 * this manager now holds two lists of failures -- one for displayable 032 * failures and one for nondisplayable failures. If the <tt>validate</tt> 033 * method returns <i>true</i>, both of these lists will be empty. However, 034 * if it returns <i>false</i>, one, the other, or both may have failures, 035 * with a minimum of one failure in the combined lists. This means that 036 * clients, upon seeing a <i>false</i> return value, must now see if there 037 * are any displayable failures before committing to interaction with the 038 * user.</p> 039 * <p> 040 * <b><u>How the Manager Finds Validators</u></b> 041 * <br/> 042 * The <tt>ValidationManager</tt> finds a validator for an instance of a given 043 * target validation class by either referring to a validator registered by 044 * one of its clients or by relying on its default search mechanism. 045 * <p> 046 * Clients may use one of two methods to register validators for a particular 047 * class of validation target: {@link #registerValidator(Class, Class)} and 048 * {@link #registerValidator(Validator, Class)}. The first method associates 049 * a class of validator with the class of validation target. The first time 050 * such a target is to be validated, the manager instantiates an instance of 051 * the validator class and uses it. It will use that same instance each time 052 * validation is requested on that class of validation target, unless the 053 * {@link #unregisterValidator(Class)} method has been called on that 054 * class of validation target, or if a specific validator has been specified 055 * via the second method. This second method associates a particular instance 056 * of a validator with the given class of validation target. This validator 057 * may have been preconfigured by the client in some special way. The manager 058 * will continue to use that validator until either the <tt>unregister</tt> 059 * method is called or a new instance of a validator is registered.</p> 060 * <p> 061 * If there is no instance or class of validator associated with a class of 062 * validation target, the manager will create and use a validator based on this 063 * naming convention: if the class of the validation target is 064 * <tt>abc.def.PqrStu</tt>, the validator class sought is 065 * <tt>abc.def.PqrStuValidator</tt>. If this class cannot be instantiated, 066 * the same logic will be applied to the superclass of <tt>abc.def.PqrStu</tt> 067 * until a validator is found. If a validator cannot be found for any of 068 * the classes on the inheritance tree, 069 * the return value of the <tt>validate</tt> method will be <i>false</i> 070 * and the list of failures will contain one failure that notes the lack 071 * of validator for the validation target class.</p> 072 * <p> 073 * <b>Version Info:</b> 074 * <table style="margin-left:2em"> 075 * <tr><td>$Revision: 1707 $</td></tr> 076 * <tr><td>$Date: 2008-11-14 10:23:59 -0700 (Fri, 14 Nov 2008) $</td></tr> 077 * <tr><td>$Author: dharland $</td></tr> 078 * </table></p> 079 * 080 * @author David M. Harland 081 * @since 2007-02-02 082 */ 083 public class ValidationManager 084 { 085 private Map<Class<?>, Class<? extends Validator>> validatorClassMap; 086 private Map<Class<?>, Validator> validatorMap; 087 088 //These pertain to the most recently run validation 089 private Object validationTarget; 090 private List<ValidationFailure> failures; 091 private List<ValidationFailure> nonDisplayableFailures; 092 093 private boolean validationInProgress; 094 095 /** Creates a new instance. */ 096 public ValidationManager() 097 { 098 validatorClassMap = new HashMap<Class<?>, Class<? extends Validator>>(); 099 validatorMap = new HashMap<Class<?>, Validator>(); 100 failures = new ArrayList<ValidationFailure>(); 101 nonDisplayableFailures = new ArrayList<ValidationFailure>(); 102 validationInProgress = false; 103 } 104 105 /** 106 * Validates the target object for the given purpose. 107 * <p> 108 * If all tests were passed, this method returns <i>true</i>. 109 * If this method returns <i>false</i>, the list returned 110 * by {@link #getFailures()} will contain one or more 111 * {@link ValidationFailure}s.</p> 112 * 113 * @param target the object to be validated. 114 * 115 * @param purpose the reason for the validation. Different purposes trigger 116 * different validations and/or different levels of the same 117 * validations. 118 * 119 * @return <i>true</i> if {@code target} passed all the tests required for 120 * the given {@code purpose}, <i>false</i> otherwise. 121 */ 122 public boolean validate(Object target, ValidationPurpose purpose) 123 { 124 validationInProgress = true; 125 validationTarget = target; 126 127 failures.clear(); 128 nonDisplayableFailures.clear(); 129 130 Validator validator = getValidator(target.getClass()); 131 132 if (validator != null) 133 { 134 //TODO EVL-699 asks us to look at this idea of setting mgr 135 // first to self and then to null. 136 validator.setManager(this); 137 failures.addAll(validator.validate(target, purpose)); 138 validator.setManager(null); 139 } 140 141 boolean success = failures.size() == 0; 142 143 reapportionFailures(); 144 145 validationInProgress = false; 146 147 return success; 148 } 149 150 /** 151 * Moves all failures that have no user-displayable messages from 152 * the failures variable to the nonDisplayableFailures messages. 153 */ 154 private void reapportionFailures() 155 { 156 for (ValidationFailure failure : failures) 157 if (!failure.isDisplayable()) 158 nonDisplayableFailures.add(failure); 159 160 failures.removeAll(nonDisplayableFailures); 161 } 162 163 /** 164 * Configures this manager to validate instances of {@code targetClass} 165 * with {@code validator}. 166 * <p> 167 * Note that this manager will hold a reference to {@code validator}. 168 * This means that any changes made to it after calling this method 169 * will be reflected in this object. This same validator will be 170 * returned via the {@link #getValidator(Class)} method.</p> 171 * <p> 172 * If either parameter is <i>null</i>, this method does nothing.</p> 173 * 174 * @param validator the validator to use on instances of {@code targetClass}. 175 * 176 * @param targetClass the type of objects on which to use {@code validator}. 177 */ 178 public void registerValidator(Validator validator, Class<?> targetClass) 179 { 180 if (validator != null && targetClass != null) 181 { 182 validatorMap.put(targetClass, validator); 183 validatorClassMap.put(targetClass, validator.getClass()); 184 } 185 } 186 187 /** 188 * Configures this manager to validate instances of {@code targetClass} 189 * with an instance of {@code validator}. 190 * <p> 191 * The next time a validator is needed of an object of type 192 * {@code targetClass} a new instance of {@code validatorClass} will 193 * be created and used.</p> 194 * <p> 195 * If either parameter is <i>null</i>, this method does nothing.</p> 196 * 197 * @param validatorClass the type of validator to use on objects of 198 * type {@code targetClass}. 199 * 200 * @param targetClass the type of objects on which to use an instance of 201 * {@code validatorClass}. 202 */ 203 public void registerValidator(Class<? extends Validator> validatorClass, 204 Class<?> targetClass) 205 { 206 if (validatorClass != null && targetClass != null) 207 { 208 validatorMap.remove(validatorClass); 209 validatorClassMap.put(targetClass, validatorClass); 210 } 211 } 212 213 /** 214 * Unregisters the validator associated with {@code targetClass}. 215 * After this call no validator instance or validator class will 216 * be associated with {@code targetClass}. 217 * 218 * @param targetClass a class of objects subject to validation. 219 * 220 * @return the validator registered for use with instances of 221 * {@code targetClass} prior to calling this method. 222 * If no validator or validator class had been so 223 * registered, <i>null</i> is returned. 224 */ 225 public Validator unregisterValidator(Class<?> targetClass) 226 { 227 Validator oldValidator = validatorMap.remove(targetClass); 228 229 if (oldValidator == null) 230 oldValidator = instantiateValidator(targetClass); 231 232 validatorClassMap.remove(targetClass); 233 234 return oldValidator; 235 } 236 237 //============================================================================ 238 // FAILURES 239 //============================================================================ 240 241 /** 242 * Returns a copy of the list of displayable validation failures triggered 243 * by the most validation. This list contains only those failures that can 244 * be shown to external users, it excludes failures that have no displayable 245 * message. 246 * 247 * @return a copy of the list of displayable validation failures triggered 248 * by the most recent validation. 249 * 250 * @see #getNonDisplayableFailures() 251 */ 252 public List<ValidationFailure> getFailures() 253 { 254 //If ValidationFailure is made publicly mutable, 255 //change logic to return list of clones. 256 257 return new ArrayList<ValidationFailure>(failures); 258 } 259 260 /** 261 * Returns a copy of the list of nondisplayable validation failures triggered 262 * by the most validation. This list contains only those failures that have 263 * no displayable message. These failures should, though, have a debug 264 * message. 265 * 266 * @return a copy of the list of nondisplayable validation failures triggered 267 * by the most recent validation. 268 * 269 * @see #getFailures() 270 */ 271 public List<ValidationFailure> getNonDisplayableFailures() 272 { 273 //If ValidationFailure is made publicly mutable, 274 //change logic to return list of clones. 275 276 return new ArrayList<ValidationFailure>(nonDisplayableFailures); 277 } 278 279 //============================================================================ 280 // FINDING & CREATING VALIDATOR INSTANCES 281 //============================================================================ 282 283 /** 284 * Returns the validator that this manager will use for objects of type 285 * {@code targetClass}. 286 * <p> 287 * The validator returned is the one actually held by this manager. 288 * This means that if you alter the properties of the returned validator, 289 * those changes will be reflected in this manager. 290 * If this manager holds no validator for {@code targetClass}, 291 * it traverses the target class's hierarchy tree to the top. 292 * If no validator is found for any of the superclasses, 293 * <i>null</i> is returned.</p> 294 * 295 * @param targetClass the class of object for which a validator is desired. 296 * 297 * @return a validator for {@code targetClass}, or <i>null</i> if this manager 298 * cannot find one. 299 */ 300 public Validator getValidator(Class<?> targetClass) 301 { 302 Validator validator = null; 303 304 //Look for a validator for targetClass. 305 //If not found, traverse its hierarchy to the top. 306 while ((validator == null) && (targetClass != null)) 307 { 308 validator = fetchOrMakeValidator(targetClass); 309 targetClass = targetClass.getSuperclass(); 310 } 311 312 //If we couldn't find or build one, create and store a failure 313 if ((validator == null) && validationInProgress) 314 failures.add(validatorNotFoundFailure()); 315 316 return validator; 317 } 318 319 /** Fetches or makes a validator for targetClass. Can return null. */ 320 private Validator fetchOrMakeValidator(Class<?> targetClass) 321 { 322 Validator validator = validatorMap.get(targetClass); 323 324 //If we didn't have a validator already stored, try to build one & store it 325 if (validator == null) 326 validator = instantiateValidator(targetClass); 327 328 return validator; 329 } 330 331 /** Tries to create a validator for {@code target}. */ 332 private Validator instantiateValidator(Class<?> target) 333 { 334 Validator validator = null; 335 336 Class<? extends Validator> vc = validatorClassMap.get(target); 337 338 //If we didn't have a class already registered, try to build one 339 if (vc == null) 340 vc = makeValidatorClass(target); 341 342 if (vc != null) 343 { 344 try 345 { 346 validator = vc.newInstance(); //Try to make a new validator 347 validator.setManager(this); //If successful, set manager & 348 validatorMap.put(target, validator); //save in map. 349 } 350 catch (Exception ex) 351 { 352 //leave validator = null, which we'll return 353 } 354 } 355 356 return validator; 357 } 358 359 /** Tries to create a Class that represents a validator for {@code target}. */ 360 private Class<? extends Validator> makeValidatorClass(Class<?> target) 361 { 362 Class<? extends Validator> vc = null; 363 364 try 365 { 366 //Try to make class ClassNameValidator 367 vc = Class.forName(target.getName() + "Validator") 368 .asSubclass(Validator.class); 369 370 validatorClassMap.put(target, vc); //If successful, save in map 371 } 372 catch (ClassNotFoundException ex) 373 { 374 //leave vc=null, which we'll return 375 } 376 377 return vc; 378 } 379 380 /** 381 * Returns a validation failure indicating that a validator could 382 * not be found for {@code targetClass}. 383 * 384 * NOTE: This method should be called ONLY while validation is 385 * in progress because it uses the validationTarget for info. 386 */ 387 private ValidationFailure validatorNotFoundFailure() 388 { 389 String debugMsg = "Could not find validator for class " + 390 validationTarget.getClass().getName(); 391 392 return new ValidationFailure("", 393 debugMsg, 394 FailureSeverity.WARNING, 395 validationTarget, 396 "", 397 "no validator"); 398 } 399 400 /** 401 * Returns a validation failure indicating that a validator could 402 * not be found for {@code targetClass}. 403 404 private ValidationFailure validatorNotFoundFailure(Class targetClass) 405 { 406 String debugMsg = "Could not find validator for class " + 407 targetClass.getName(); 408 409 return new ValidationFailure("", 410 debugMsg, 411 FailureSeverity.WARNING, 412 null, 413 ""); 414 } 415 */ 416 }