001 /* 002 * Copyright 2011 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 1.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl1.php 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, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.student.datadictionary.util; 017 018 import java.util.ArrayList; 019 import java.util.Date; 020 import java.util.HashSet; 021 import java.util.List; 022 import java.util.Set; 023 import java.util.regex.Pattern; 024 import java.util.regex.PatternSyntaxException; 025 import org.kuali.rice.core.api.datetime.DateTimeService; 026 import org.kuali.rice.core.api.uif.DataType; 027 import org.kuali.rice.krad.datadictionary.AttributeDefinition; 028 import org.kuali.rice.krad.datadictionary.DataObjectEntry; 029 import org.kuali.rice.krad.datadictionary.validation.ValidationUtils; 030 import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint; 031 import org.kuali.rice.krad.datadictionary.validation.constraint.LookupConstraint; 032 import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint; 033 import org.kuali.rice.krad.datadictionary.validation.constraint.WhenConstraint; 034 035 public class DictionaryValidator { 036 037 private DateTimeService dateTimeService; 038 private DataObjectEntry doe; 039 private Set<DataObjectEntry> alreadyValidated; 040 041 public DictionaryValidator(DataObjectEntry doe, 042 Set<DataObjectEntry> alreadyValidated) { 043 this.doe = doe; 044 this.alreadyValidated = alreadyValidated; 045 } 046 047 public DateTimeService getDateTimeService() { 048 return dateTimeService; 049 } 050 051 public void setDateTimeService(DateTimeService dateTimeService) { 052 this.dateTimeService = dateTimeService; 053 } 054 055 public List<String> validate() { 056 List<String> errors = new ArrayList(); 057 if (doe.getFullClassName() == null) { 058 errors.add("The class name cannot be be left null"); 059 } 060 if (doe.getEntryClass() == null) { 061 errors.add("The entry class should not be left null"); 062 } 063 if (!doe.getEntryClass().getName().equals(doe.getFullClassName())) { 064 errors.add("The entry class should match the full class name"); 065 } 066 067 // else if (this.getClass (ode.getName ()) == null) 068 // { 069 // errors.add ("The name does not exist on the class path"); 070 // } 071 072 if (doe.getAttributes() == null) { 073 errors.add("getAttribues () is null"); 074 return errors; 075 } 076 if (doe.getCollections() == null) { 077 errors.add("getCollections () is null"); 078 return errors; 079 } 080 if (doe.getComplexAttributes() == null) { 081 errors.add("getComplexAttributes ()"); 082 return errors; 083 } 084 if (doe.getCollections().isEmpty() 085 && doe.getComplexAttributes().isEmpty() 086 && doe.getAttributes().isEmpty()) { 087 errors.add("No fields of any kind defined for this complex object structure"); 088 return errors; 089 } 090 091 Set<String> fieldNames = new HashSet(); 092 093 for (AttributeDefinition ad : doe.getAttributes()) { 094 if (ad.getName() != null) { 095 if (!fieldNames.add(ad.getName())) { 096 errors.add(ad.getName() + " is defined more than once"); 097 } 098 } 099 errors.addAll(validateAttributeDefinition(ad)); 100 } 101 102 doe.completeValidation(); 103 104 return errors; 105 } 106 107 private List<String> validateAttributeDefinition(AttributeDefinition ad) { 108 List<String> errors = new ArrayList(); 109 if (ad.getName() == null) { 110 errors.add("name cannot be null"); 111 } else if (ad.getName().trim().equals("")) { 112 errors.add("name cannot be blank"); 113 } else if (ad.getDataType() == null) { 114 errors.add(ad.getName() + " has a null data type"); 115 // } else if (ad.getDataType().equals(DataType.COMPLEX)) { 116 // errorIfNotNull(errors, ad, "exclusiveMin", ad.getExclusiveMin()); 117 // errorIfNotNull(errors, ad, "inclusiveMax", ad.getInclusiveMax()); 118 // errorIfNotNull(errors, ad, "max length", ad.getMaxLength()); 119 // errorIfNotNull(errors, ad, "min length", ad.getMinLength()); 120 // errorIfNotNull(errors, ad, "valid chars", ad.getValidCharactersConstraint()); 121 // errorIfNotNull(errors, ad, "lookup", ad.getLookupDefinition()); 122 } 123 // validateConversion(errors, ad.getName(), "defaultValue", ad.getDataType(), ad.getDefaultValue()); 124 validateConversion(errors, ad.getName(), "exclusiveMin", ad.getDataType(), ad.getExclusiveMin()); 125 validateConversion(errors, ad.getName(), "inclusiveMax", ad.getDataType(), ad.getInclusiveMax()); 126 //TODO: Cross compare to make sure min is not greater than max and that default value is valid itself 127 128 if (ad.getLookupDefinition() != null) { 129 errors.addAll(validateLookup(ad, ad.getLookupDefinition())); 130 } 131 if (ad.getCaseConstraint() != null) { 132 errors.addAll(validateCase(ad, ad.getCaseConstraint())); 133 } 134 if (ad.getValidCharactersConstraint() != null) { 135 errors.addAll(validateValidChars(ad, ad.getValidCharactersConstraint())); 136 } 137 return errors; 138 } 139 140 private void errorIfNotNull(List<String> errors, AttributeDefinition fd, 141 String validation, 142 Object value) { 143 if (value != null) { 144 errors.add("field " + fd.getName() + " has a " + validation 145 + " but it cannot be specified on a complex type"); 146 } 147 } 148 149 private Object validateConversion(List<String> errors, String fieldName, 150 String propertyName, DataType dataType, 151 Object value) { 152 if (value == null) { 153 return null; 154 } 155 switch (dataType) { 156 case STRING: 157 return value.toString().trim(); 158 // case DATE, TRUNCATED_DATE, BOOLEAN, INTEGER, FLOAT, DOUBLE, LONG, COMPLEX 159 case LONG: 160 try { 161 return ValidationUtils.getLong(value); 162 } catch (NumberFormatException ex) { 163 errors.add( 164 "field " + fieldName 165 + " has a " + propertyName 166 + " that cannot be converted into a long integer"); 167 } 168 return null; 169 case INTEGER: 170 try { 171 return ValidationUtils.getInteger(value); 172 } catch (NumberFormatException ex) { 173 errors.add( 174 "field " + fieldName 175 + " has a " + propertyName + " that cannot be converted into an integer"); 176 } 177 return null; 178 case FLOAT: 179 try { 180 return ValidationUtils.getFloat(value); 181 } catch (NumberFormatException ex) { 182 errors.add( 183 "field " + fieldName 184 + " has a " + propertyName 185 + " that cannot be converted into a floating point value"); 186 } 187 return null; 188 case DOUBLE: 189 try { 190 return ValidationUtils.getFloat(value); 191 } catch (NumberFormatException ex) { 192 errors.add( 193 "field " + fieldName 194 + " has a " + propertyName 195 + " that cannot be converted into a double sized floating point value"); 196 } 197 return null; 198 case BOOLEAN: 199 if (value instanceof Boolean) { 200 return ((Boolean) value).booleanValue(); 201 } 202 if (value instanceof String) { 203 if (((String) value).trim().equalsIgnoreCase("true")) { 204 return true; 205 } 206 if (((String) value).trim().equalsIgnoreCase("false")) { 207 return true; 208 } 209 } 210 errors.add( 211 "field " + fieldName 212 + " has a " + propertyName 213 + " that cannot be converted into a boolean true/false"); 214 return null; 215 case DATE: 216 case TRUNCATED_DATE: 217 if (value instanceof Date) { 218 return (Date) value; 219 } 220 try { 221 // TODO: make the date parser configurable like the validator is 222 return ValidationUtils.getDate(value, dateTimeService); 223 } catch (Exception e) { 224 errors.add( 225 "field " + fieldName 226 + " has a " + propertyName 227 + " that cannot be converted into a date"); 228 } 229 return null; 230 default: 231 errors.add( 232 "field " + fieldName 233 + " has a " + propertyName 234 + " that cannot be converted into an unknown/unhandled data type"); 235 return null; 236 } 237 } 238 239 private List<String> validateValidChars(AttributeDefinition fd, 240 ValidCharactersConstraint vc) { 241 List<String> errors = new ArrayList(); 242 String validChars = vc.getValue(); 243 /* 244 int typIdx = validChars.indexOf(":"); 245 String processorType = "regex"; 246 if (-1 == typIdx) { 247 validChars = "[" + validChars + "]*"; 248 } else { 249 processorType = validChars.substring(0, typIdx); 250 validChars = validChars.substring(typIdx + 1); 251 } 252 if (!processorType.equalsIgnoreCase("regex")) { 253 errors.add( 254 "field " + fd.getName() 255 + " has an invalid valid chars processor type: a simple list of characters or a regex: is supported"); 256 return errors; 257 } 258 */ 259 try { 260 Pattern pattern = Pattern.compile(validChars); 261 } catch (PatternSyntaxException ex) { 262 errors.add("field " + fd.getName() 263 + " has in invalid character pattern for a regular expression: " 264 + validChars); 265 } 266 return errors; 267 } 268 269 private List<String> validateLookup(AttributeDefinition fd, LookupConstraint lc) { 270 List<String> errors = new ArrayList(); 271 if (lc.getParams() == null) { 272 errors.add("field " + fd.getName() + " has a lookup with null parameters"); 273 } 274 //TODO: more validation 275 return errors; 276 } 277 public static final String GREATER_THAN_EQUAL = "greater_than_equal"; 278 public static final String LESS_THAN_EQUAL = "less_than_equal"; 279 public static final String GREATER_THAN = "greater_than"; 280 public static final String LESS_THAN = "less_than"; 281 public static final String EQUALS = "equals"; 282 public static final String NOT_EQUAL = "not_equal"; 283 private static final String[] VALID_OPERATORS = { 284 NOT_EQUAL, EQUALS, GREATER_THAN_EQUAL, LESS_THAN_EQUAL, GREATER_THAN, LESS_THAN 285 }; 286 287 private List<String> validateCase(AttributeDefinition fd, CaseConstraint cc) { 288 List<String> errors = new ArrayList(); 289 if (cc.getOperator() == null) { 290 errors.add("field " + fd.getName() 291 + " has a case constraint with no operator"); 292 } else { 293 boolean found = false; 294 for (int i = 0; i < VALID_OPERATORS.length; i++) { 295 if (VALID_OPERATORS[i].equalsIgnoreCase(cc.getOperator())) { 296 found = true; 297 break; 298 } 299 } 300 if (!found) { 301 errors.add("field " + fd.getName() 302 + " has a case constraint with an unknown operator " 303 + cc.getOperator()); 304 } 305 } 306 if (cc.getPropertyName() == null) { 307 errors.add( 308 "field " + fd.getName() 309 + " has a case constraint with a null for the field to use for the comparison"); 310 } else if (cc.getPropertyName().trim().equals("")) { 311 errors.add( 312 "field " + fd.getName() 313 + " has a case constraint with blanks for the field to use for the comparison"); 314 } 315 if (cc.getWhenConstraint() == null) { 316 errors.add("field " + fd.getName() 317 + " has a case constraint but null when statements"); 318 return errors; 319 } 320 if (cc.getWhenConstraint().size() == 0) { 321 errors.add("field " + fd.getName() 322 + " has a case constraint but has no when statements"); 323 } 324 for (WhenConstraint wc : cc.getWhenConstraint()) { 325 if (wc.getConstraint() == null) { 326 errors.add( 327 "field " + fd.getName() 328 + " has a as case constraint with a when statement that has no overriding constraints specified"); 329 } 330 } 331 //TODO: more validation 332 return errors; 333 } 334 }