View Javadoc

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  //  else if (this.getClass (os.getName ()) == null)
51  //  {
52  //   errors.add ("The name does not exist on the class path");
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  // private Class getClass (String className)
81  // {
82  //  try
83  //  {
84  //   return Class.forName (className);
85  //  }
86  //  catch (ClassNotFoundException ex)
87  //  {
88  //   return null;
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   //TODO: Cross compare to make sure min is not greater than max and that default value is valid itself
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 //    case DATE, TRUNCATED_DATE, BOOLEAN, INTEGER, FLOAT, DOUBLE, LONG, COMPLEX
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      // TODO: make the date parser configurable like the validator is
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   //TODO: more validation
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   //TODO: more validation
403   return errors;
404  }
405 }