001 /* 002 * Copyright 2011 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.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.osedu.org/licenses/ECL-2.0 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 */ 016package org.kuali.student.r2.common.datadictionary.util; 017 018import org.kuali.rice.core.api.uif.DataType; 019import org.kuali.student.r2.common.datadictionary.infc.AttributeDefinitionInfc; 020import org.kuali.student.r2.common.datadictionary.infc.DictionaryEntry; 021import org.kuali.student.r2.common.exceptions.InvalidParameterException; 022import org.kuali.student.r2.common.exceptions.OperationFailedException; 023import org.kuali.student.r2.common.infc.Comparison; 024import org.kuali.student.r2.common.infc.Criteria; 025import org.kuali.student.r2.common.util.date.DateFormatters; 026 027import java.sql.Timestamp; 028import java.util.ArrayList; 029import java.util.Date; 030import java.util.List; 031 032 /** 033 * A class that validates the criteria against the dictionary 034 * 035 * It also sets the parsedValues and parsedOperators so they may be used in the implementation 036 * The call sequence is sometehing like this: 037 * <ol> 038 * <li>construct this validator 039 * <li>Configure this validator with criteria to be valiated 040 * <li>Get the dictionary entry of the object to be validated by calling DataDictionaryServiceInfc.getDataDictionaryEntry (the ref object's URI) 041 * <li>Configure this validator with the dictionary entry that you just got from the dictionary 042 * <li>Call the validate () method which will throw exceptions if problems 043 * <li>get the parsed values (@see getParsedValues ()) and use them in the implementation 044 * <li>Get the parsed operators and use in the implementation, if you want 045 * </ol> 046 * @author nwright 047 */ 048public class CriteriaValidatorParser { 049 050 public enum Operator { 051 052 EQ, IN, GT, LT, NEQ, GTE, LTE, LIKE, BETWEEN; 053 } 054 private Criteria criteria; 055 private DictionaryEntry dictionaryEntry; 056 private transient List<Object> parsedValues; 057 private transient List<Operator> parsedOperators; 058 059 public CriteriaValidatorParser() { 060 } 061 062 public Criteria getCriteria() { 063 return criteria; 064 } 065 066 public void setCriteria(Criteria criteria) { 067 this.criteria = criteria; 068 } 069 070 public DictionaryEntry getDictionaryEntry() { 071 return dictionaryEntry; 072 } 073 074 public void setDictionaryEntry(DictionaryEntry dictionaryEntry) { 075 this.dictionaryEntry = dictionaryEntry; 076 } 077 078 /** 079 * Get the string operators translated as an Operator enum 080 * @return list of operators in the same ordinal position as the supplied operators in the list of ComparisonInfc objects 081 */ 082 public List<Operator> getParsedOperators() { 083 return parsedOperators; 084 } 085 086 /** 087 * The values parsed as their respective data types, String, Date, Integer, etc. 088 * 089 * Special considerations: 090 * IN operators return a List of one or more parsed values who's order is the samne as the order in the ComparisonInfc 091 * BETWEEN operators return a list of two parsed values who's values coorespond to the from to values of a between operation. 092 * 093 * @return a list of parsed values in the same ordinal positions as the unparsed values in the list of ComparisonInfc objects 094 */ 095 public List<Object> getParsedValues() { 096 return parsedValues; 097 } 098 099 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