View Javadoc
1   /**
2    * Copyright 2004-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.student.datadictionary.util;
17  
18  import java.util.ArrayList;
19  import java.util.Date;
20  import java.util.HashSet;
21  import java.util.List;
22  import java.util.Set;
23  import java.util.regex.Pattern;
24  import java.util.regex.PatternSyntaxException;
25  import org.kuali.rice.core.api.datetime.DateTimeService;
26  import org.kuali.rice.core.api.uif.DataType;
27  import org.kuali.rice.krad.datadictionary.AttributeDefinition;
28  import org.kuali.rice.krad.datadictionary.DataObjectEntry;
29  import org.kuali.rice.krad.datadictionary.validation.ValidationUtils;
30  import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
31  import org.kuali.rice.krad.datadictionary.validation.constraint.LookupConstraint;
32  import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
33  import org.kuali.rice.krad.datadictionary.validation.constraint.WhenConstraint;
34  
35  public class DictionaryValidator {
36  
37      private DateTimeService dateTimeService;
38      private DataObjectEntry doe;
39      private Set<DataObjectEntry> alreadyValidated;
40  
41      public DictionaryValidator(DataObjectEntry doe,
42              Set<DataObjectEntry> alreadyValidated) {
43          this.doe = doe;
44          this.alreadyValidated = alreadyValidated;
45      }
46  
47      public DateTimeService getDateTimeService() {
48          return dateTimeService;
49      }
50  
51      public void setDateTimeService(DateTimeService dateTimeService) {
52          this.dateTimeService = dateTimeService;
53      }
54  
55      public List<String> validate() {
56          List<String> errors = new ArrayList();
57          if (doe.getFullClassName() == null) {
58              errors.add("The class name cannot be be left null");
59          }
60          if (doe.getEntryClass() == null) {
61              errors.add("The entry class should not be left null");
62          }
63          if (!doe.getEntryClass().getName().equals(doe.getFullClassName())) {
64              errors.add("The entry class should match the full class name");
65          }
66  
67  //  else if (this.getClass (ode.getName ()) == null)
68  //  {
69  //   errors.add ("The name does not exist on the class path");
70  //  }
71  
72          if (doe.getAttributes() == null) {
73              errors.add("getAttribues () is null");
74              return errors;
75          }
76          if (doe.getCollections() == null) {
77              errors.add("getCollections () is null");
78              return errors;
79          }
80          if (doe.getComplexAttributes() == null) {
81              errors.add("getComplexAttributes ()");
82              return errors;
83          }
84          if (doe.getCollections().isEmpty()
85                  && doe.getComplexAttributes().isEmpty()
86                  && doe.getAttributes().isEmpty()) {
87              errors.add("No fields of any kind defined for this complex object structure");
88              return errors;
89          }
90  
91          Set<String> fieldNames = new HashSet();
92  
93          for (AttributeDefinition ad : doe.getAttributes()) {
94              if (ad.getName() != null) {
95                  if (!fieldNames.add(ad.getName())) {
96                      errors.add(ad.getName() + " is defined more than once");
97                  }
98              }
99              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 }