View Javadoc
1    /*
2    * Copyright 2011 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.osedu.org/licenses/ECL-2.0
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.r2.common.datadictionary.util;
17  
18  import org.kuali.rice.core.api.data.DataType;
19  import org.kuali.student.r2.common.datadictionary.infc.AttributeDefinitionInfc;
20  import org.kuali.student.r2.common.datadictionary.infc.DictionaryEntry;
21  import org.kuali.student.r2.common.exceptions.InvalidParameterException;
22  import org.kuali.student.r2.common.exceptions.OperationFailedException;
23  import org.kuali.student.r2.common.infc.Comparison;
24  import org.kuali.student.r2.common.infc.Criteria;
25  import org.kuali.student.r2.common.util.date.DateFormatters;
26  
27  import java.sql.Timestamp;
28  import java.util.ArrayList;
29  import java.util.Date;
30  import java.util.List;
31  
32   /**
33   * A class that validates the criteria against the dictionary
34   *
35   * It also sets the parsedValues and parsedOperators so they may be used in the implementation
36   * The call sequence is sometehing like this:
37   * <ol>
38   * <li>construct this validator
39   * <li>Configure this validator with criteria to be valiated
40   * <li>Get the dictionary entry of the object to be validated by calling DataDictionaryServiceInfc.getDataDictionaryEntry (the ref object's URI)
41   * <li>Configure this validator with the dictionary entry that you just got from the dictionary
42   * <li>Call the validate () method which will throw exceptions if problems
43   * <li>get the parsed values (@see getParsedValues ()) and use them in the implementation
44   * <li>Get the parsed operators and use in the implementation, if you want
45   * </ol>
46   * @author nwright
47   */
48  public class CriteriaValidatorParser {
49  
50      public enum Operator {
51  
52          EQ, IN, GT, LT, NEQ, GTE, LTE, LIKE, BETWEEN;
53      }
54      private Criteria criteria;
55      private DictionaryEntry dictionaryEntry;
56      private transient List<Object> parsedValues;
57      private transient List<Operator> parsedOperators;
58  
59      public CriteriaValidatorParser() {
60      }
61  
62      public Criteria getCriteria() {
63          return criteria;
64      }
65  
66      public void setCriteria(Criteria criteria) {
67          this.criteria = criteria;
68      }
69  
70      public DictionaryEntry getDictionaryEntry() {
71          return dictionaryEntry;
72      }
73  
74      public void setDictionaryEntry(DictionaryEntry dictionaryEntry) {
75          this.dictionaryEntry = dictionaryEntry;
76      }
77  
78      /**
79       * Get the string operators translated as an Operator enum
80       * @return list of operators in the same ordinal position as the supplied operators in the list of ComparisonInfc objects
81       */
82      public List<Operator> getParsedOperators() {
83          return parsedOperators;
84      }
85  
86      /**
87       * The values parsed as their respective data types, String, Date, Integer, etc.
88       *
89       * Special considerations:
90       * IN operators return a List of one or more parsed values who's order is the samne as the order in the ComparisonInfc
91       * BETWEEN operators return a list of two parsed values who's values coorespond to the from to values of a between operation.
92       *
93       * @return a list of parsed values in the same ordinal positions as the unparsed values in the list of ComparisonInfc objects
94       */
95      public List<Object> getParsedValues() {
96          return parsedValues;
97      }
98  
99  
100 
101     /**
102      * Validate the criteria thowing an InvalidParameterException if there are problems
103      *
104      * TODO: Consider getting all the errors and throw them as a group instead of as we find them
105      *
106      * @throws InvalidParameterException
107      * @throws OperationFailedException
108      */
109     public void validate()
110             throws InvalidParameterException,
111             OperationFailedException {
112         parsedValues = new ArrayList<Object>();
113         parsedOperators = new ArrayList <Operator> ();
114         if (this.criteria.getComparisons() == null) {
115             throw new InvalidParameterException ("Comparisons list is null -- to select all specify an empty list");
116         }
117         int i = 0;
118         for (Comparison comparison : this.criteria.getComparisons()) {
119             this.validate(i, comparison);
120             i++;
121         }
122     }
123 
124     private void validate(int i, Comparison comparison)
125             throws InvalidParameterException,
126             OperationFailedException {
127         String fieldKey = comparison.getFieldKey();
128         String operator = comparison.getOperator();
129         List<String> values = comparison.getValues();
130         AttributeDefinitionInfc ad = this.getAttributeDefinition(fieldKey);
131         if (ad == null) {
132             throw new InvalidParameterException("The " + i + "th comparison's field key " + fieldKey + " is not defined in the dictionary");
133         }
134         if (operator == null) {
135             throw new InvalidParameterException("The " + i + "th comparison's operator is null");
136         }
137         if (operator.equals("=")) {
138             this.parsedOperators.add(Operator.EQ);
139         } else if (operator.equals("<")) {
140             this.parsedOperators.add(Operator.LT);
141         } else if (operator.equals(">")) {
142             this.parsedOperators.add(Operator.GT);
143         } else if (operator.equals("!=")) {
144             this.parsedOperators.add(Operator.NEQ);
145         } else if (operator.equals("<=")) {
146             this.parsedOperators.add(Operator.LTE);
147         } else if (operator.equals(">=")) {
148             this.parsedOperators.add(Operator.GTE);
149         } else if (operator.equals("in")) {
150             this.parsedOperators.add(Operator.IN);
151         } else if (operator.equals("between")) {
152             this.parsedOperators.add(Operator.BETWEEN);
153         } else if (operator.equals("like")) {
154             this.parsedOperators.add(Operator.LIKE);
155             if (ad.getDataType().equals (DataType.STRING)) {
156             throw new InvalidParameterException("The " + i + "th comparison's operator is LIKE which can only be applied to strings, " + ad.getDataType() + " is invalid.");
157             }
158         } else {
159             throw new InvalidParameterException("The " + i + "th comparison's operator, " + operator + ", is invalid.");
160         }
161         if (values == null) {
162             throw new InvalidParameterException("The " + i + "th comparison's values list is required and cannot be null");
163         }
164         if (values.isEmpty()) {
165             throw new InvalidParameterException("The " + i + "th comparison's values list is required and cannot be an empty list");
166         }
167         if (values.get(0) == null) {
168             if (!operator.equals("=") && !operator.equals("!=")) {
169                 throw new InvalidParameterException("The " + i + "th comparison's value is null but the operator " + operator + " is a comparison operator that does not apply");
170             }
171             return;
172         }
173         if (operator.equals("between")) {
174             if (values.size() != 2) {
175                 throw new InvalidParameterException("The " + i + "th comparison is a between operator which requires two values, found " + values.size());
176             }
177             if (values.get(0) == null) {
178                 throw new InvalidParameterException("The " + i + "th comparison is a between operator but the first value is null");
179             }
180             if (values.get(1) == null) {
181                 throw new InvalidParameterException("The " + i + "th comparison is a between operator but the second value is null");
182             }
183         } else if (values.size() > 1) {
184             if (!operator.equals("in")) {
185                 throw new InvalidParameterException("The " + i + "th comparison's value is a list but the operator " + operator + " is a comparison operator that does not apply");
186             }
187         }
188         switch (ad.getDataType()) {
189             case STRING:
190                 break;
191             case DATE:
192             case TRUNCATED_DATE:
193                 break;
194             case BOOLEAN:
195                 if (! operator.equals("=") && !operator.equals("!=")) {
196                     throw new InvalidParameterException("The " + i + "th comparison's operator " + operator + " is a comparison operator that does not apply to the field's boolean data type");
197                 }
198             case INTEGER:
199             case FLOAT:
200             case DOUBLE:
201             case LONG:
202                 break;
203 //            case COMPLEX:
204 //                if (! operator.equals("=") && !operator.equals("!=")) {
205 //                    throw new InvalidParameterException("The " + i + "th comparison's operator " + operator + " is a comparison operator that does not apply to the field's complex data type");
206 //                }
207 //                if (values.get(0) == null) {
208 //                    throw new InvalidParameterException("The " + i + "th comparison's value is not null but attribute type is complex. Complex can only be checked to see if it is null or not null");
209 //                }
210         }
211         parsedValues.add(parseValues(i, ad.getDataType(), comparison.getValues(), comparison.getIsIgnoreCase()));
212     }
213 
214     private Object parseValues(int i, DataType dataType, List<String> values, boolean ignoreCase)
215             throws InvalidParameterException {
216         if (values.size() == 1) {
217             return parseValue(i, dataType, values.get(0), ignoreCase);
218         }
219         List<Object> list = new ArrayList<Object>();
220         for (String value : values) {
221             list.add(parseValue(i, dataType, value, ignoreCase));
222         }
223         return list;
224     }
225 
226     private Object parseValue(int i, DataType dataType, String value, boolean ignoreCase)
227             throws InvalidParameterException {
228         if (value == null) {
229             return null;
230         }
231         switch (dataType) {
232             case STRING:
233                 return parseString(i, value, ignoreCase);
234             case DATE:
235                 return parseDateTime(i, value);
236             case TRUNCATED_DATE:
237                 return parseDate(i, value);
238             case BOOLEAN:
239                 return parseBoolean(i, value);
240             case INTEGER:
241                 return parseInteger(i, value);
242             case FLOAT:
243                 return parseFloat(i, value);
244             case DOUBLE:
245                 return parseDouble(i, value);
246             case LONG:
247                 return parseLong(i, value);
248 //            case COMPLEX:
249 //                throw new InvalidParameterException("The " + i + "th comparison's value is not null but attribute type is complex. Complex can only be checked to see if it is null or not null");
250             default:
251                 throw new IllegalArgumentException("Unknown/unhandled datatype " + dataType);
252         }
253     }
254 
255     private String parseString(int i, String cv, boolean ignoreCase) throws InvalidParameterException {
256         if (cv == null) {
257             return null;
258         }
259         if (ignoreCase) {
260             return cv.toLowerCase();
261         }
262         return cv;
263     }
264 
265     private Timestamp parseDateTime(int i, String cv) throws InvalidParameterException {
266         try {
267             return new Timestamp(DateFormatters.DEFAULT_TIMESTAMP_FORMATTER.parse(cv).getTime());
268         } catch (IllegalArgumentException ex) {
269             throw new InvalidParameterException("The " + i + "th comparison's value " + cv + " cannot be parsed as a dateTime");
270         }
271     }
272 
273     private Date parseDate(int i, String cv) throws InvalidParameterException {
274         try {
275             return DateFormatters.DEFAULT_DATE_FORMATTER.parse(cv);
276         } catch (IllegalArgumentException ex) {
277             throw new InvalidParameterException("The " + i + "th comparison's value " + cv + " cannot be parsed as a date");
278         }
279     }
280 
281     private Integer parseInteger(int i, String cv) throws InvalidParameterException {
282         try {
283             return Integer.parseInt(cv);
284         } catch (NumberFormatException ex) {
285             throw new InvalidParameterException("The " + i + "th comparison's value " + cv + " cannot be parsed as an integer");
286         }
287     }
288 
289     private Long parseLong(int i, String cv) throws InvalidParameterException {
290         try {
291             return Long.parseLong(cv);
292         } catch (NumberFormatException ex) {
293             throw new InvalidParameterException("The " + i + "th comparison's value " + cv + " cannot be parsed as a Long");
294         }
295     }
296 
297     private Boolean parseBoolean(int i, String cv) throws InvalidParameterException {
298         if (cv.equalsIgnoreCase("true")) {
299             return Boolean.TRUE;
300         }
301         if (cv.equalsIgnoreCase("false")) {
302             return Boolean.FALSE;
303         }
304         throw new InvalidParameterException("The " + i + "th comparison's value " + cv + " cannot be parsed as a Boolean");
305 
306     }
307 
308     private Float parseFloat(int i, String cv) throws InvalidParameterException {
309         try {
310             return Float.parseFloat(cv);
311         } catch (NumberFormatException ex) {
312             throw new InvalidParameterException("The " + i + "th comparison's value " + cv + " cannot be parsed as an float");
313         }
314     }
315 
316     private Double parseDouble(int i, String cv) throws InvalidParameterException {
317         try {
318             return Double.parseDouble(cv);
319         } catch (NumberFormatException ex) {
320             throw new InvalidParameterException("The " + i + "th comparison's value " + cv + " cannot be parsed as an double");
321         }
322     }
323 
324     private String initLower(String str) {
325         if (str == null) {
326             return null;
327         }
328         if (str.length() == 0) {
329             return str;
330         }
331         if (str.length() == 1) {
332             return str.toLowerCase();
333         }
334         return str.substring(0, 1).toLowerCase() + str.substring(1);
335     }
336 
337     private boolean calcIsList(AttributeDefinitionInfc ad) {
338         if (ad.getMaxOccurs() == null) {
339             return false;
340         }
341         if (ad.getMaxOccurs() <= 1) {
342             return false;
343         }
344         return true;
345 
346     }
347 
348     private AttributeDefinitionInfc getAttributeDefinition(String fk)
349             throws InvalidParameterException,
350             OperationFailedException {
351         for (AttributeDefinitionInfc ad : this.dictionaryEntry.getAttributes()) {
352             if (ad.getName().equals(fk)) {
353                 return ad;
354             }
355         }
356        return null;
357     }
358 
359 }
360