1 package org.kuali.student.common.dictionary.service.impl;
2
3 import java.util.ArrayList;
4 import java.util.Date;
5 import java.util.HashSet;
6 import java.util.List;
7 import java.util.Set;
8 import java.util.regex.Pattern;
9 import java.util.regex.PatternSyntaxException;
10
11 import org.kuali.student.common.dictionary.dto.CaseConstraint;
12 import org.kuali.student.common.dictionary.dto.DataType;
13 import org.kuali.student.common.dictionary.dto.FieldDefinition;
14 import org.kuali.student.common.dictionary.dto.LookupConstraint;
15 import org.kuali.student.common.dictionary.dto.ObjectStructureDefinition;
16 import org.kuali.student.common.dictionary.dto.ValidCharsConstraint;
17 import org.kuali.student.common.dictionary.dto.WhenConstraint;
18 import org.kuali.student.common.validator.ServerDateParser;
19 import org.kuali.student.common.validator.ValidatorUtils;
20
21
22 public class DictionaryValidator
23 {
24
25 private ObjectStructureDefinition os;
26 private boolean processSubStructures = false;
27 private Set<ObjectStructureDefinition> alreadyValidated;
28
29 public DictionaryValidator (ObjectStructureDefinition os,
30 Set<ObjectStructureDefinition> alreadyValidated,
31 boolean processSubstructures)
32 {
33 this.os = os;
34 this.alreadyValidated = alreadyValidated;
35 this.processSubStructures = processSubstructures;
36 }
37
38 public List<String> validate ()
39 {
40 List<String> errors = new ArrayList ();
41 if (os.getName () == null)
42 {
43 errors.add ("The name cannbe be left null");
44 }
45 if (os.getBusinessObjectClass () != null)
46 {
47 errors.add (
48 "The business object class is not used and should not be filled in");
49 }
50
51
52
53
54
55 if (os.getAttributes () == null)
56 {
57 errors.add ("getAttribues () is null -- null for field defintion");
58 return errors;
59 }
60 if (os.getAttributes ().size () == 0)
61 {
62 errors.add ("No fields defined for complex object structure");
63 return errors;
64 }
65 Set<String> fieldNames = new HashSet ();
66 for (FieldDefinition fd : os.getAttributes ())
67 {
68 if (fd.getName () != null)
69 {
70 if ( ! fieldNames.add (fd.getName ()))
71 {
72 errors.add (fd.getName () + " is defined more than once");
73 }
74 }
75 errors.addAll (validateField (fd));
76 }
77 return errors;
78 }
79
80
81
82
83
84
85
86
87
88
89
90
91
92 private List<String> validateField (FieldDefinition fd)
93 {
94 List<String> errors = new ArrayList ();
95 if (fd.getName () == null)
96 {
97 errors.add ("name cannot be null");
98 }
99 else if (fd.getName ().trim ().equals (""))
100 {
101 errors.add ("name cannot be blank");
102 }
103 if (fd.getDataType ().equals (DataType.COMPLEX))
104 {
105 errorIfNotNull (errors, fd, "exclusiveMin", fd.getExclusiveMin ());
106 errorIfNotNull (errors, fd, "inclusiveMax", fd.getInclusiveMax ());
107 errorIfNotNull (errors, fd, "max length", fd.getMaxLength ());
108 errorIfNotNull (errors, fd, "min length", fd.getMinLength ());
109 errorIfNotNull (errors, fd, "valid chars", fd.getValidChars ());
110 errorIfNotNull (errors, fd, "lookup", fd.getLookupDefinition ());
111 if (fd.getDataObjectStructure () == null)
112 {
113 errors.add (
114 "field " + fd.getName ()
115 + " does not have an object structure definition but it required on a complex type");
116 }
117 else
118 {
119 if (this.processSubStructures)
120 {
121 if (alreadyValidated.add (fd.getDataObjectStructure ()))
122 {
123 errors.addAll (new DictionaryValidator (fd.getDataObjectStructure (),
124 alreadyValidated,
125 processSubStructures).validate ());
126 }
127 }
128 }
129 }
130 validateConversion (errors, fd.getName (), "defaultValue", fd.getDataType (), fd.getDefaultValue ());
131 validateConversion (errors, fd.getName (), "exclusiveMin", fd.getDataType (), fd.getExclusiveMin ());
132 validateConversion (errors, fd.getName (), "inclusiveMax", fd.getDataType (), fd.getInclusiveMax ());
133
134 if (fd.getMaxLength () != null)
135 {
136 try
137 {
138 Integer.parseInt (fd.getMaxLength ());
139 }
140 catch (NumberFormatException ex)
141 {
142 errors.add (
143 "field " + fd.getName ()
144 + " has a maxlength that is not an integer");
145 }
146 }
147
148 if (fd.getLookupDefinition () != null)
149 {
150 errors.addAll (validateLookup (fd, fd.getLookupDefinition ()));
151 }
152 if (fd.getCaseConstraint () != null)
153 {
154 errors.addAll (validateCase (fd, fd.getCaseConstraint ()));
155 }
156 if (fd.getValidChars () != null)
157 {
158 errors.addAll (validateValidChars (fd, fd.getValidChars ()));
159 }
160 return errors;
161 }
162
163 private void errorIfNotNull (List<String> errors, FieldDefinition fd,
164 String validation,
165 Object value)
166 {
167 if (value != null)
168 {
169 errors.add ("field " + fd.getName () + " has a " + validation
170 + " but it cannot be specified on a complex type");
171 }
172 }
173
174 private Object validateConversion (List<String> errors, String fieldName,
175 String propertyName, DataType dataType,
176 Object value)
177 {
178 if (value == null)
179 {
180 return null;
181 }
182 switch (dataType)
183 {
184 case STRING:
185 return value.toString ().trim ();
186
187 case LONG:
188 try
189 {
190 return ValidatorUtils.getLong (value);
191 }
192 catch (NumberFormatException ex)
193 {
194 errors.add (
195 "field " + fieldName
196 + " has a " + propertyName
197 + " that cannot be converted into a long integer");
198 }
199 return null;
200 case INTEGER:
201 try
202 {
203 return ValidatorUtils.getInteger (value);
204 }
205 catch (NumberFormatException ex)
206 {
207 errors.add (
208 "field " + fieldName
209 + " has a " + propertyName + " that cannot be converted into an integer");
210 }
211 return null;
212 case FLOAT:
213 try
214 {
215 return ValidatorUtils.getFloat (value);
216 }
217 catch (NumberFormatException ex)
218 {
219 errors.add (
220 "field " + fieldName
221 + " has a " + propertyName
222 + " that cannot be converted into a floating point value");
223 }
224 return null;
225 case DOUBLE:
226 try
227 {
228 return ValidatorUtils.getFloat (value);
229 }
230 catch (NumberFormatException ex)
231 {
232 errors.add (
233 "field " + fieldName
234 + " has a " + propertyName
235 + " that cannot be converted into a double sized floating point value");
236 }
237 return null;
238 case BOOLEAN:
239 if (value instanceof Boolean)
240 {
241 return ((Boolean) value).booleanValue ();
242 }
243 if (value instanceof String)
244 {
245 if (((String) value).trim ().equalsIgnoreCase ("true"))
246 {
247 return true;
248 }
249 if (((String) value).trim ().equalsIgnoreCase ("false"))
250 {
251 return true;
252 }
253 }
254 errors.add (
255 "field " + fieldName
256 + " has a " + propertyName
257 + " that cannot be converted into a boolean true/false");
258 return null;
259 case DATE:
260 case TRUNCATED_DATE:
261 if (value instanceof Date)
262 {
263 return (Date) value;
264 }
265 try
266 {
267
268 return new ServerDateParser ().parseDate (value.toString ());
269 }
270 catch (Exception e)
271 {
272 errors.add (
273 "field " + fieldName
274 + " has a " + propertyName
275 + " that cannot be converted into a date");
276 }
277 return null;
278 default:
279 errors.add (
280 "field " + fieldName
281 + " has a " + propertyName
282 + " that cannot be converted into an unknown/unhandled data type");
283 return null;
284 }
285 }
286
287 private List<String> validateValidChars (FieldDefinition fd,
288 ValidCharsConstraint vc)
289 {
290 List<String> errors = new ArrayList ();
291 String validChars = vc.getValue ();
292 int typIdx = validChars.indexOf (":");
293 String processorType = "regex";
294 if (-1 == typIdx)
295 {
296 validChars = "[" + validChars + "]*";
297 }
298 else
299 {
300 processorType = validChars.substring (0, typIdx);
301 validChars = validChars.substring (typIdx + 1);
302 }
303 if ( ! processorType.equalsIgnoreCase ("regex"))
304 {
305 errors.add (
306 "field " + fd.getName ()
307 + " has an invalid valid chars processor type: a simple list of characters or a regex: is supported");
308 return errors;
309 }
310 try
311 {
312 Pattern pattern = Pattern.compile (validChars);
313 }
314 catch (PatternSyntaxException ex)
315 {
316 errors.add ("field " + fd.getName ()
317 + " has in invalid character pattern for a regular expression: "
318 + validChars);
319 }
320 return errors;
321 }
322
323 private List<String> validateLookup (FieldDefinition fd, LookupConstraint lc)
324 {
325 List<String> errors = new ArrayList ();
326 if (lc.getParams () == null)
327 {
328 errors.add ("field " + fd.getName () + " has a lookup with null parameters");
329 }
330
331 return errors;
332 }
333 public static final String GREATER_THAN_EQUAL = "greater_than_equal";
334 public static final String LESS_THAN_EQUAL = "less_than_equal";
335 public static final String GREATER_THAN = "greater_than";
336 public static final String LESS_THAN = "less_than";
337 public static final String EQUALS = "equals";
338 public static final String NOT_EQUAL = "not_equal";
339 private static final String[] VALID_OPERATORS =
340 {
341 NOT_EQUAL, EQUALS, GREATER_THAN_EQUAL, LESS_THAN_EQUAL, GREATER_THAN, LESS_THAN
342 };
343
344 private List<String> validateCase (FieldDefinition fd, CaseConstraint cc)
345 {
346 List<String> errors = new ArrayList ();
347 if (cc.getOperator () == null)
348 {
349 errors.add ("field " + fd.getName ()
350 + " has a case constraint with no operator");
351 }
352 else
353 {
354 boolean found = false;
355 for (int i = 0; i < VALID_OPERATORS.length; i ++)
356 {
357 if (VALID_OPERATORS[i].equalsIgnoreCase (cc.getOperator ()))
358 {
359 found = true;
360 break;
361 }
362 }
363 if ( ! found)
364 {
365 errors.add ("field " + fd.getName ()
366 + " has a case constraint with an unknown operator "
367 + cc.getOperator ());
368 }
369 }
370 if (cc.getFieldPath () == null)
371 {
372 errors.add (
373 "field " + fd.getName ()
374 + " has a case constraint with a null for the field to use for the comparison");
375 }
376 else if (cc.getFieldPath ().trim ().equals (""))
377 {
378 errors.add (
379 "field " + fd.getName ()
380 + " has a case constraint with blanks for the field to use for the comparison");
381 }
382 if (cc.getWhenConstraint () == null)
383 {
384 errors.add ("field " + fd.getName ()
385 + " has a case constraint but null when statements");
386 return errors;
387 }
388 if (cc.getWhenConstraint ().size () == 0)
389 {
390 errors.add ("field " + fd.getName ()
391 + " has a case constraint but has no when statements");
392 }
393 for (WhenConstraint wc : cc.getWhenConstraint ())
394 {
395 if (wc.getConstraint () == null)
396 {
397 errors.add (
398 "field " + fd.getName ()
399 + " has a as case constraint with a when statement that has no overriding constraints specified");
400 }
401 }
402
403 return errors;
404 }
405 }