001/** 002 * Copyright 2005-2016 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.opensource.org/licenses/ecl2.php 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.rice.krad.service.impl; 017 018import java.sql.Timestamp; 019import java.text.ParseException; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.commons.beanutils.PropertyUtils; 027import org.apache.commons.lang.StringUtils; 028import org.apache.commons.lang.Validate; 029import org.apache.log4j.Logger; 030import org.kuali.rice.core.api.criteria.Predicate; 031import org.kuali.rice.core.api.criteria.PredicateFactory; 032import org.kuali.rice.core.api.criteria.QueryByCriteria; 033import org.kuali.rice.core.api.datetime.DateTimeService; 034import org.kuali.rice.core.api.search.SearchOperator; 035import org.kuali.rice.core.api.util.RiceKeyConstants; 036import org.kuali.rice.core.api.util.type.TypeUtils; 037import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion; 038import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform; 039import org.kuali.rice.krad.bo.InactivatableFromTo; 040import org.kuali.rice.krad.data.DataObjectService; 041import org.kuali.rice.krad.lookup.LookupInputField; 042import org.kuali.rice.krad.lookup.LookupUtils; 043import org.kuali.rice.krad.lookup.LookupView; 044import org.kuali.rice.krad.service.DataDictionaryService; 045import org.kuali.rice.krad.uif.UifConstants; 046import org.kuali.rice.krad.uif.UifParameters; 047import org.kuali.rice.krad.uif.component.Component; 048import org.kuali.rice.krad.uif.view.View; 049import org.kuali.rice.krad.util.GlobalVariables; 050import org.kuali.rice.krad.util.KRADPropertyConstants; 051import org.kuali.rice.krad.util.KRADUtils; 052 053/** 054 * Base LookupCriteriaGenerator into which logic common to both OJB and JPA for criteria construction 055 * has been extracted. Subclasses implement backend-specific criteria translation/generation details. 056 */ 057public class LookupCriteriaGeneratorImpl implements LookupCriteriaGenerator { 058 059 private static final Logger LOG = Logger.getLogger(LookupCriteriaGeneratorImpl.class); 060 061 private DateTimeService dateTimeService; 062 private DataDictionaryService dataDictionaryService; 063 private DatabasePlatform dbPlatform; 064 private DataObjectService dataObjectService; 065 066 public DateTimeService getDateTimeService() { 067 return dateTimeService; 068 } 069 070 public void setDateTimeService(DateTimeService dateTimeService) { 071 this.dateTimeService = dateTimeService; 072 } 073 074 public DataDictionaryService getDataDictionaryService() { 075 return dataDictionaryService; 076 } 077 078 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 079 this.dataDictionaryService = dataDictionaryService; 080 } 081 082 public DatabasePlatform getDbPlatform() { 083 return dbPlatform; 084 } 085 086 public void setDbPlatform(DatabasePlatform dbPlatform) { 087 this.dbPlatform = dbPlatform; 088 } 089 090 public DataObjectService getDataObjectService() { 091 return dataObjectService; 092 } 093 094 public void setDataObjectService(DataObjectService dataObjectService) { 095 this.dataObjectService = dataObjectService; 096 } 097 098 @Override 099 @Deprecated 100 public QueryByCriteria.Builder generateCriteria(Class<?> type, Map<String, String> formProps, boolean usePrimaryKeysOnly) { 101 if (usePrimaryKeysOnly) { 102 return getCollectionCriteriaFromMapUsingPrimaryKeysOnly(type, instantiateLookupDataObject(type), formProps).toQueryBuilder(); 103 } else { 104 return getCollectionCriteriaFromMap(type, instantiateLookupDataObject(type), formProps).toQueryBuilder(); 105 } 106 } 107 108 @Override 109 public QueryByCriteria.Builder generateCriteria(Class<?> type, Map<String, String> formProps, 110 List<String> wildcardAsLiteralPropertyNames, boolean usePrimaryKeysOnly) { 111 if (usePrimaryKeysOnly) { 112 return getCollectionCriteriaFromMapUsingPrimaryKeysOnly(type, instantiateLookupDataObject(type), formProps, 113 wildcardAsLiteralPropertyNames).toQueryBuilder(); 114 } else { 115 return getCollectionCriteriaFromMap(type, instantiateLookupDataObject(type), formProps, wildcardAsLiteralPropertyNames).toQueryBuilder(); 116 } 117 } 118 119 @Override 120 public QueryByCriteria.Builder createObjectCriteriaFromMap(Object example, Map<String, String> formProps) { 121 Predicates criteria = new Predicates(); 122 123 // iterate through the parameter map for search criteria 124 for (Map.Entry<String, String> formProp : formProps.entrySet()) { 125 126 String propertyName = formProp.getKey(); 127 String searchValue = ""; 128 if (formProp.getValue() != null) { 129 searchValue = formProp.getValue(); 130 } 131 132 Object instanObject = instantiateLookupDataObject((Class<?>)example); 133 if (StringUtils.isNotBlank(searchValue) & PropertyUtils.isWriteable(instanObject, propertyName)) { 134 Class<?> propertyType = getPropertyType(instanObject, propertyName); 135 if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) { 136 addEqualNumeric(criteria, propertyName, propertyType, searchValue); 137 } else if (TypeUtils.isTemporalClass(propertyType)) { 138 addEqualTemporal(criteria, propertyName, searchValue); 139 } else { 140 addEqual(criteria, propertyName, searchValue); 141 } 142 } 143 } 144 145 return criteria.toQueryBuilder(); 146 } 147 148 /** 149 * Instantiates a new instance of the data object for the given type. 150 * 151 * @param type the type of the data object to pass, must not be null 152 * @return new instance of the given data object 153 */ 154 protected Object instantiateLookupDataObject(Class<?> type) { 155 Validate.notNull(type, "DataObject type passed to lookup was null"); 156 try { 157 return type.newInstance(); 158 } catch (IllegalAccessException e) { 159 throw new RuntimeException("Could not create instance of " + type, e); 160 } catch (InstantiationException e) { 161 throw new RuntimeException("Could not create instance of " + type, e); 162 } 163 } 164 165 protected boolean createCriteria(Object example, String searchValue, String propertyName, Predicates criteria) { 166 return createCriteria(example, searchValue, propertyName, false, false, criteria); 167 } 168 169 public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Predicates criteria) { 170 return createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, 171 criteria, null); 172 } 173 174 @Deprecated 175 protected boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Predicates criteria, Map<String, String> searchValues) { 176 // if searchValue is empty and the key is not a valid property ignore 177 if (StringUtils.isBlank(searchValue) || !isWriteable(example, propertyName)) { 178 return false; 179 } 180 181 // get property type which is used to determine type of criteria 182 Class<?> propertyType = getPropertyType(example, propertyName); 183 if (propertyType == null) { 184 // Instead of skipping the property if we can't determine a type, assume it's a String 185 // so that the criteria does not get dropped 186 propertyType = String.class; 187 } 188 189 // build criteria 190 if (example instanceof InactivatableFromTo) { 191 if (KRADPropertyConstants.ACTIVE.equals(propertyName)) { 192 addInactivateableFromToActiveCriteria(example, searchValue, criteria, searchValues); 193 } else if (KRADPropertyConstants.CURRENT.equals(propertyName)) { 194 addInactivateableFromToCurrentCriteria(example, searchValue, criteria, searchValues); 195 } else if (!KRADPropertyConstants.ACTIVE_AS_OF_DATE.equals(propertyName)) { 196 addCriteria(propertyName, searchValue, propertyType, caseInsensitive, 197 treatWildcardsAndOperatorsAsLiteral, criteria); 198 } 199 } else { 200 addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, 201 criteria); 202 } 203 204 return true; 205 } 206 207 /** 208 * Adds a criteria Predicate for each property contained in the map. 209 * 210 * @param type class name of the Data Object being looked up 211 * @param example sample object instance of the class type 212 * @param formProps Map of search criteria properties 213 * @return Predicates built from criteria map 214 * @deprecated please use {@link #getCollectionCriteriaFromMap(Class, Object, java.util.Map, java.util.List)} instead 215 */ 216 @Deprecated 217 protected Predicates getCollectionCriteriaFromMap(Class<?> type, Object example, Map<String, String> formProps) { 218 Predicates criteria = new Predicates(); 219 for (String propertyName : formProps.keySet()) { 220 boolean caseInsensitive = determineIfAttributeSearchShouldBeCaseInsensitive(type, propertyName); 221 boolean treatWildcardsAndOperatorsAsLiteral = doesLookupFieldTreatWildcardsAndOperatorsAsLiteral(type, propertyName); 222 String searchValue = formProps.get(propertyName); 223 addCriteriaForPropertyValues(example, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps, searchValue); 224 } 225 226 return criteria; 227 } 228 229 /** 230 * Adds a criteria Predicate for each property contained in the map. 231 * 232 * <p> 233 * Checks for case sensitivity for the search parameter, and whether or not wildcard characters are allowed. 234 * 235 * This implementation further separates the UIFramework from the LookupService and should be used in place 236 * of the deprecated method. 237 * </p> 238 * 239 * @param type class name of the Data Object being looked up 240 * @param example sample object instance of the class type 241 * @param formProps Map of search criteria properties 242 * @param wildcardAsLiteralPropertyNames List of search criteria properties with wildcard characters disabled. 243 * @return Predicates built from criteria map 244 */ 245 protected Predicates getCollectionCriteriaFromMap(Class<?> type, Object example, Map<String, String> formProps, List<String> wildcardAsLiteralPropertyNames) { 246 Predicates criteria = new Predicates(); 247 for (String propertyName : formProps.keySet()) { 248 boolean caseInsensitive = determineIfAttributeSearchShouldBeCaseInsensitive(type, propertyName); 249 boolean treatWildcardsAndOperatorsAsLiteral = wildcardAsLiteralPropertyNames.contains(propertyName); 250 String searchValue = formProps.get(propertyName); 251 addCriteriaForPropertyValues(example, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps, searchValue); 252 } 253 254 return criteria; 255 } 256 257 /** 258 * Returns whether we should perform comparisons in a case-insensitive manner for this attribute. 259 * By default comparisons are case-INsensitive, however, if the attribute is marked as "forceUppercase" in the DD, 260 * then the comparison is case-SENSITIVE. 261 * NOTE: The assumption is that for forceUppercase-d attributes, the DB data is already uppercased, so we can perform a case-sensitive search 262 * @param type the type of the data object 263 * @param propertyName the business object property 264 * @return whether we should perform comparisons in a case-insensitive manner for this attribute 265 */ 266 protected boolean determineIfAttributeSearchShouldBeCaseInsensitive(Class<?> type, String propertyName) { 267 Boolean caseInsensitive = Boolean.TRUE; 268 if (dataDictionaryService.isAttributeDefined(type, propertyName)) { 269 // If forceUppercase is true, both the database value and the user entry should be converted to Uppercase -- so change the caseInsensitive to false since we don't need to 270 // worry about the values not matching. However, if forceUppercase is false, make sure to do a caseInsensitive search because the database value and user entry 271 // could be mixed case. Thus, caseInsensitive will be the opposite of forceUppercase. 272 caseInsensitive = !dataDictionaryService.getAttributeForceUppercase(type, propertyName); 273 } 274 if (caseInsensitive == null) { 275 caseInsensitive = Boolean.TRUE; 276 } 277 278 return caseInsensitive.booleanValue(); 279 } 280 281 /** 282 * Adds a criteria for the property for each search value, handling search value case 283 * @param example the example search object 284 * @param propertyName the object property 285 * @param caseInsensitive case sensitivity determination 286 * @param treatWildcardsAndOperatorsAsLiteral whether to treat wildcards and operators as literal 287 * @param criteria the criteria we are modifying 288 * @param formProps the search form properties 289 * @param searchValues the property search values 290 * @return whether all criteria were successfully added, false if any were invalid and loop was short-circuited 291 */ 292 protected boolean addCriteriaForPropertyValues(Object example, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Predicates criteria, Map formProps, String... searchValues) { 293 for (String searchValue: searchValues) { 294 if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) { 295 return false; 296 } 297 } 298 299 return true; 300 } 301 302 @Deprecated 303 protected Predicates getCollectionCriteriaFromMapUsingPrimaryKeysOnly(Class<?> type, Object dataObject, Map<String, String> formProps) { 304 Predicates criteria = new Predicates(); 305 List<String> pkFields = listPrimaryKeyFieldNames(type); 306 for (String pkFieldName : pkFields) { 307 String pkValue = formProps.get(pkFieldName); 308 if (StringUtils.isBlank(pkValue)) { 309 throw new RuntimeException("Missing pk value for field " + pkFieldName + " when a search based on PK values only is performed."); 310 } 311 else { 312 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) { 313 if (pkValue.contains(op.op())) { 314 throw new RuntimeException("Value \"" + pkValue + "\" for PK field " + pkFieldName + " contains wildcard/operator characters."); 315 } 316 } 317 } 318 319 boolean treatWildcardsAndOperatorsAsLiteral = doesLookupFieldTreatWildcardsAndOperatorsAsLiteral(type, 320 pkFieldName); 321 createCriteria(dataObject, pkValue, pkFieldName, false, treatWildcardsAndOperatorsAsLiteral, criteria); 322 } 323 324 return criteria; 325 } 326 327 protected Predicates getCollectionCriteriaFromMapUsingPrimaryKeysOnly(Class<?> type, Object dataObject, Map<String, String> formProps, List<String> wildcardAsLiteralPropertyNames) { 328 Predicates criteria = new Predicates(); 329 List<String> pkFields = listPrimaryKeyFieldNames(type); 330 for (String pkFieldName : pkFields) { 331 String pkValue = formProps.get(pkFieldName); 332 if (StringUtils.isBlank(pkValue)) { 333 throw new RuntimeException("Missing pk value for field " + pkFieldName + " when a search based on PK values only is performed."); 334 } 335 else { 336 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) { 337 if (pkValue.contains(op.op())) { 338 throw new RuntimeException("Value \"" + pkValue + "\" for PK field " + pkFieldName + " contains wildcard/operator characters."); 339 } 340 } 341 } 342 boolean treatWildcardsAndOperatorsAsLiteral = wildcardAsLiteralPropertyNames.contains(pkFieldName); 343 createCriteria(dataObject, pkValue, pkFieldName, false, treatWildcardsAndOperatorsAsLiteral, criteria); 344 } 345 346 return criteria; 347 } 348 349 @Deprecated 350 protected boolean doesLookupFieldTreatWildcardsAndOperatorsAsLiteral(Class<?> type, String fieldName) { 351 // determine the LookupInputField for the field and use isDisableWildcardsAndOperators 352 Map<String, String> indexKey = new HashMap<String, String>(); 353 indexKey.put(UifParameters.VIEW_NAME, UifConstants.DEFAULT_VIEW_NAME); 354 indexKey.put(UifParameters.DATA_OBJECT_CLASS_NAME, type.getName()); 355 356 // obtain the Lookup View for the data object 357 View view = getDataDictionaryService().getDataDictionary().getViewByTypeIndex(UifConstants.ViewType.LOOKUP, indexKey); 358 if (view != null && view instanceof LookupView) { 359 LookupView lookupView = (LookupView) view; 360 // iterate through the criteria fields to find the lookup field for the given property 361 List<Component> criteriaFields = lookupView.getCriteriaFields(); 362 for (Component criteriaField: criteriaFields) { 363 if (criteriaField instanceof LookupInputField) { 364 LookupInputField lookupInputField = (LookupInputField) criteriaField; 365 if (fieldName.equals(lookupInputField.getPropertyName())) { 366 // this is the droid we're looking for 367 return lookupInputField.isDisableWildcardsAndOperators(); 368 } 369 } 370 } 371 } 372 373 return false; 374 } 375 376 /** 377 * @throws NumberFormatException if {@code value} is not a valid 378 * representation of a {@code Number}. 379 */ 380 protected Number cleanNumeric(String value, Class<?> propertyType) { 381 String cleanedValue = value.replaceAll("[^-0-9.]", ""); 382 // ensure only one "minus" at the beginning, if any 383 if (cleanedValue.lastIndexOf('-') > 0) { 384 if (cleanedValue.charAt(0) == '-') { 385 cleanedValue = "-" + cleanedValue.replaceAll("-", ""); 386 } else { 387 cleanedValue = cleanedValue.replaceAll("-", ""); 388 } 389 } 390 // ensure only one decimal in the string 391 int decimalLoc = cleanedValue.lastIndexOf('.'); 392 if (cleanedValue.indexOf('.') != decimalLoc) { 393 cleanedValue = cleanedValue.substring(0, decimalLoc).replaceAll("\\.", "") + cleanedValue.substring(decimalLoc); 394 } 395 Object rv = KRADUtils.hydrateAttributeValue(propertyType, cleanedValue); 396 397 if( !(rv instanceof Number)) { 398 throw new NumberFormatException("Value: " + cleanedValue + " cannot be converted into number type"); 399 } 400 401 return (Number) rv; 402 } 403 404 405 406 protected void addOrCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Predicates criteria) { 407 addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.OR.op()); 408 } 409 410 protected void addAndCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Predicates criteria) { 411 addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.AND.op()); 412 } 413 414 /** 415 * Adds to the criteria object based on the property type and any query characters given. 416 */ 417 protected void addCriteria(String propertyName, String propertyValue, Class<?> propertyType, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Predicates criteria) { 418 propertyName = parsePropertyName(criteria, propertyName); 419 420 if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.OR.op())) { 421 addOrCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria); 422 return; 423 } 424 425 if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.AND.op())) { 426 addAndCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria); 427 return; 428 } 429 430 if (StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NULL.op()) || StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NOT_NULL.op())) { 431 // KULRICE-6846 null Lookup criteria causes sql exception 432 if (StringUtils.contains(propertyValue, SearchOperator.NOT.op())) { 433 addIsNotNull(criteria, propertyName); 434 } 435 else { 436 addIsNull(criteria, propertyName); 437 } 438 } 439 else if (TypeUtils.isStringClass(propertyType)) { 440 441 // XXX TODO: handle case insensitivity for native jpa queries! don't just UPPER(column) 442 443 // // KULRICE-85 : made string searches case insensitive - used new DBPlatform function to force strings to upper case 444 // if (caseInsensitive) { 445 // propertyName = uppercasePropertyName(propertyName); 446 // propertyValue = propertyValue.toUpperCase(); 447 // } 448 if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, 449 SearchOperator.NOT.op())) { 450 addNotCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria); 451 } else if ( 452 !treatWildcardsAndOperatorsAsLiteral && propertyValue != null && ( 453 StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op()) 454 || propertyValue.startsWith(">") 455 || propertyValue.startsWith("<") ) ) { 456 addStringRangeCriteria(propertyName, propertyValue, caseInsensitive, criteria); 457 } else { 458 if (treatWildcardsAndOperatorsAsLiteral) { 459 propertyValue = StringUtils.replace(propertyValue, "*", "\\*"); 460 propertyValue = StringUtils.replace(propertyValue, "%", "\\%"); 461 propertyValue = StringUtils.replace(propertyValue, "?", "\\?"); 462 propertyValue = StringUtils.replace(propertyValue, "_", "\\_"); 463 } 464 addLike(criteria, propertyName, propertyValue, caseInsensitive); 465 } 466 } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) { 467 addNumericRangeCriteria(propertyName, propertyValue, propertyType, treatWildcardsAndOperatorsAsLiteral, criteria); 468 } else if (TypeUtils.isTemporalClass(propertyType)) { 469 addDateRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria); 470 } else if (TypeUtils.isBooleanClass(propertyType)) { 471 addEqualToBoolean(criteria, propertyName, propertyValue); 472 } else { 473 LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue); 474 } 475 } 476 477 protected void addNotCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Predicates criteria) { 478 String[] splitPropVal = StringUtils.split(propertyValue, SearchOperator.NOT.op()); 479 480 try { 481 int strLength = splitPropVal.length; 482 // if Not'ed empty criteria 483 if (strLength == 0) { 484 throw new IllegalArgumentException("Improper syntax of NOT operator in " + propertyName); 485 } 486 // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b) 487 if (strLength > 1) { 488 String expandedNot = SearchOperator.NOT + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op()); 489 // we know that since this method was called, treatWildcardsAndOperatorsAsLiteral must be false 490 addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria); 491 } else { 492 // only one so add a not like 493 addNotLike(criteria, propertyName, splitPropVal[0], caseInsensitive); 494 } 495 } catch (IllegalArgumentException ex) { 496 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_NOT_SYNTAX, propertyName); 497 } 498 } 499 500 /** 501 * Adds to the criteria object based on query characters given 502 */ 503 protected void addDateRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Predicates criteria) { 504 try { 505 if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) { 506 if (treatWildcardsAndOperatorsAsLiteral) { 507 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 508 } 509 String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op()); 510 if (rangeValues.length < 2) { 511 throw new IllegalArgumentException("Improper syntax of BETWEEN operator in " + propertyName); 512 } 513 514 addBetween(criteria, propertyName, parseDate(LookupUtils.scrubQueryCharacters(rangeValues[0])), parseDateUpperBound(LookupUtils.scrubQueryCharacters(rangeValues[1]))); 515 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) { 516 if (treatWildcardsAndOperatorsAsLiteral) { 517 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 518 } 519 addGreaterThanOrEqual(criteria, propertyName, parseDate(LookupUtils.scrubQueryCharacters(propertyValue))); 520 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) { 521 if (treatWildcardsAndOperatorsAsLiteral) { 522 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 523 } 524 addLessThanOrEqual(criteria, propertyName, parseDateUpperBound(LookupUtils.scrubQueryCharacters(propertyValue))); 525 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) { 526 if (treatWildcardsAndOperatorsAsLiteral) { 527 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 528 } 529 addGreaterThan(criteria, propertyName, parseDate(LookupUtils.scrubQueryCharacters(propertyValue))); 530 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) { 531 if (treatWildcardsAndOperatorsAsLiteral) { 532 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 533 } 534 addLessThan(criteria, propertyName, parseDate(LookupUtils.scrubQueryCharacters(propertyValue))); 535 } else { 536 // matches date between midnight to 11:59pm - does not take time into account 537 addBetween(criteria, propertyName, parseDate(LookupUtils.scrubQueryCharacters(propertyValue)), 538 parseDateUpperBound(LookupUtils.scrubQueryCharacters(propertyValue))); 539 } 540 } catch (ParseException ex) { 541 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_DATE, propertyValue); 542 } catch (IllegalArgumentException ex) { 543 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_BETWEEN_SYNTAX, propertyName); 544 } 545 } 546 547 /** 548 * Adds to the criteria object based on query characters given 549 */ 550 protected void addNumericRangeCriteria(String propertyName, String propertyValue, Class<?> propertyType, boolean treatWildcardsAndOperatorsAsLiteral, Predicates criteria) { 551 try { 552 if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) { 553 if (treatWildcardsAndOperatorsAsLiteral) { 554 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 555 } 556 String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op()); 557 if (rangeValues.length < 2) { 558 throw new IllegalArgumentException("Improper syntax of BETWEEN operator in " + propertyName); 559 } 560 561 addBetween(criteria, propertyName, cleanNumeric(rangeValues[0], propertyType), cleanNumeric(rangeValues[1], propertyType)); 562 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) { 563 if (treatWildcardsAndOperatorsAsLiteral) { 564 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 565 } 566 addGreaterThanOrEqual(criteria, propertyName, cleanNumeric(propertyValue, propertyType)); 567 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) { 568 if (treatWildcardsAndOperatorsAsLiteral) { 569 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 570 } 571 addLessThanOrEqual(criteria, propertyName, cleanNumeric(propertyValue,propertyType)); 572 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) { 573 if (treatWildcardsAndOperatorsAsLiteral) { 574 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 575 } 576 addGreaterThan(criteria, propertyName, cleanNumeric(propertyValue, propertyType)); 577 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) { 578 if (treatWildcardsAndOperatorsAsLiteral) { 579 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 580 } 581 addLessThan(criteria, propertyName, cleanNumeric(propertyValue, propertyType)); 582 } else { 583 addEqual(criteria, propertyName, cleanNumeric(propertyValue,propertyType)); 584 } 585 } catch (NumberFormatException ex) { 586 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_NUMBER, propertyValue); 587 } catch (IllegalArgumentException ex) { 588 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_BETWEEN_SYNTAX, propertyName); 589 } 590 } 591 592 /** 593 * Adds to the criteria object based on query characters given 594 */ 595 protected void addStringRangeCriteria(String propertyName, String propertyValue, boolean caseInsensitive, Predicates criteria) { 596 try { 597 if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) { 598 String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op()); 599 if (rangeValues.length < 2) { 600 throw new IllegalArgumentException("Improper syntax of BETWEEN operator in " + propertyName); 601 } 602 603 addBetween(criteria, propertyName, rangeValues[0], rangeValues[1], caseInsensitive); 604 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) { 605 addGreaterThanOrEqual(criteria, propertyName, LookupUtils.scrubQueryCharacters(propertyValue), caseInsensitive); 606 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) { 607 addLessThanOrEqual(criteria, propertyName, LookupUtils.scrubQueryCharacters(propertyValue), caseInsensitive); 608 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) { 609 addGreaterThan(criteria, propertyName, LookupUtils.scrubQueryCharacters(propertyValue), caseInsensitive); 610 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) { 611 addLessThan(criteria, propertyName, LookupUtils.scrubQueryCharacters(propertyValue), caseInsensitive); 612 } else { 613 addEqual(criteria, propertyName, LookupUtils.scrubQueryCharacters(propertyValue), caseInsensitive); 614 } 615 } catch (IllegalArgumentException ex) { 616 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_BETWEEN_SYNTAX, propertyName); 617 } 618 } 619 620 /** 621 * Translates criteria for active status to criteria on the active from and to fields 622 * 623 * @param example - business object being queried on 624 * @param activeSearchValue - value for the active search field, should convert to boolean 625 * @param criteria - Criteria object being built 626 * @param searchValues - Map containing all search keys and values 627 */ 628 protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue, Predicates criteria, Map<String, String> searchValues) { 629 Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues); 630 631 String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue); 632 if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) { 633 // (active from date <= date or active from date is null) and (date < active to date or active to date is null) 634 Predicates criteriaBeginDate = new Predicates(); 635 addLessThanOrEqual(criteriaBeginDate, KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp); 636 637 Predicates criteriaBeginDateNull = new Predicates(); 638 addIsNull(criteriaBeginDateNull, KRADPropertyConstants.ACTIVE_FROM_DATE); 639 addOr(criteriaBeginDate, criteriaBeginDateNull); 640 641 addAnd(criteria, criteriaBeginDate); 642 643 Predicates criteriaEndDate = new Predicates(); 644 addGreaterThan(criteriaEndDate, KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp); 645 646 Predicates criteriaEndDateNull = new Predicates(); 647 addIsNull(criteriaEndDateNull, KRADPropertyConstants.ACTIVE_TO_DATE); 648 addOr(criteriaEndDate, criteriaEndDateNull); 649 650 addAnd(criteria, criteriaEndDate); 651 } 652 else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) { 653 // (date < active from date) or (active from date is null) or (date >= active to date) 654 Predicates criteriaNonActive = new Predicates(); 655 addGreaterThan(criteriaNonActive, KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp); 656 657 // NOTE: Ojb and Jpa implementations of LookupDao disagreed on the content of this query 658 // OJB omitted this (active from date is null) clause, meaning the OJB lookup dao would not return 659 // records without an active_from_date as "not active", i.e. they were considered active. 660 // The opposed was true of JPA, this clause would be added, and those records would be matched and returned 661 // as inactive. 662 // this has ramifications for existing tests which appear to use the OJB implementation semantics 663 // so we conform to the OJB behavior 664 // Predicates criteriaBeginDateNull = createCriteria(example.getClass()); 665 // addIsNull(criteriaBeginDateNull, KRADPropertyConstants.ACTIVE_FROM_DATE); 666 // addOr(criteriaNonActive, criteriaBeginDateNull); 667 668 Predicates criteriaEndDate = new Predicates(); 669 addLessThanOrEqual(criteriaEndDate, KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp); 670 addOr(criteriaNonActive, criteriaEndDate); 671 672 addAnd(criteria, criteriaNonActive); 673 } 674 } 675 676 /** 677 * Builds a sub criteria object joined with an 'AND' or 'OR' (depending on splitValue) using the split values of propertyValue. Then joins back the 678 * sub criteria to the main criteria using an 'AND'. 679 */ 680 protected void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class<?> propertyType, boolean caseInsensitive, Predicates criteria, String splitValue) { 681 String[] splitPropVal = StringUtils.split(propertyValue, splitValue); 682 683 Predicates subCriteria; 684 if (SearchOperator.OR.op().equals(splitValue)) { 685 subCriteria = new OrPredicates(); 686 } else if (SearchOperator.AND.op().equals(splitValue)) { 687 subCriteria = new Predicates(); 688 } else { 689 throw new IllegalArgumentException("Invalid split value: " + splitValue); 690 } 691 for (int i = 0; i < splitPropVal.length; i++) { 692 Predicates predicate = new Predicates(); 693 // we know that since this method is called, treatWildcardsAndOperatorsAsLiteral is false 694 addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, subCriteria); 695 } 696 addAnd(criteria, subCriteria); 697 } 698 699 // protected void addBetween(Predicates criteria, String propertyName, String value1, String value2, boolean caseInsensitive) { 700 // if (caseInsensitive) { 701 // propertyName = uppercasePropertyName(propertyName); 702 // value1 = value1.toUpperCase(); 703 // value2 = value2.toUpperCase(); 704 // } 705 // addBetween(criteria, propertyName, value1, value2); 706 // } 707 // 708 // protected void addEqual(Predicates criteria, String propertyName, String searchValue, boolean caseInsensitive) { 709 // if (caseInsensitive) { 710 // propertyName = uppercasePropertyName(propertyName); 711 // searchValue = searchValue.toUpperCase(); 712 // } 713 // addEqual(criteria, propertyName, searchValue); 714 // } 715 // 716 // protected void addLessThan(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 717 // if (caseInsensitive) { 718 // propertyName = uppercasePropertyName(propertyName); 719 // propertyValue = propertyValue.toUpperCase(); 720 // } 721 // addLessThan(criteria, propertyName, propertyValue); 722 // } 723 // 724 // protected void addLessThanOrEqual(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 725 // if (caseInsensitive) { 726 // propertyName = uppercasePropertyName(propertyName); 727 // propertyValue = propertyValue.toUpperCase(); 728 // } 729 // addLessThanOrEqual(criteria, propertyName, propertyValue); 730 // } 731 // 732 // protected void addGreaterThan(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 733 // if (caseInsensitive) { 734 // propertyName = uppercasePropertyName(propertyName); 735 // propertyValue = propertyValue.toUpperCase(); 736 // } 737 // addGreaterThan(criteria, propertyName, propertyValue); 738 // } 739 // 740 // protected void addGreaterThanOrEqual(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 741 // if (caseInsensitive) { 742 // propertyName = uppercasePropertyName(propertyName); 743 // propertyValue = propertyValue.toUpperCase(); 744 // } 745 // addGreaterThanOrEqual(criteria, propertyName, propertyValue); 746 // } 747 // 748 // protected void addLike(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 749 // if (caseInsensitive) { 750 // propertyName = uppercasePropertyName(propertyName); 751 // propertyValue = propertyValue.toUpperCase(); 752 // } 753 // addLike(criteria, propertyName, propertyValue); 754 // } 755 // 756 // protected void addNotLike(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 757 // if (caseInsensitive) { 758 // propertyName = uppercasePropertyName(propertyName); 759 // propertyValue = propertyValue.toUpperCase(); 760 // } 761 // addNotLike(criteria, propertyName, propertyValue); 762 // } 763 764 765 protected java.sql.Date parseDate(String dateString) throws ParseException { 766 dateString = dateString.trim(); 767 return dateTimeService.convertToSqlDate(dateString); 768 } 769 770 protected java.sql.Date parseDateUpperBound(String dateString) throws ParseException { 771 dateString = dateString.trim(); 772 return dateTimeService.convertToSqlDateUpperBound(dateString); 773 } 774 775 protected List<String> listPrimaryKeyFieldNames(Class<?> type) { 776 return getDataObjectService().getMetadataRepository().getMetadata(type).getPrimaryKeyAttributeNames(); 777 } 778 779 protected Class<?> getPropertyType(Object example, String propertyName) { 780 return getDataObjectService().wrap(example).getPropertyType(propertyName); 781 } 782 783 /** 784 * Return whether or not an attribute is writeable. This method is aware 785 * that that Collections may be involved and handles them consistently with 786 * the way in which OJB handles specifying the attributes of elements of a 787 * Collection. 788 * 789 * @param o 790 * @param p 791 * @return 792 * @throws IllegalArgumentException 793 */ 794 protected boolean isWriteable(Object o, String p) throws IllegalArgumentException { 795 if (null == o || null == p) { 796 throw new IllegalArgumentException("Cannot check writable status with null arguments."); 797 } 798 799 boolean b = false; 800 801 // Try the easy way. 802 if (!(PropertyUtils.isWriteable(o, p))) { 803 804 // If that fails lets try to be a bit smarter, understanding that 805 // Collections may be involved. 806 if (-1 != p.indexOf('.')) { 807 808 String[] parts = p.split("\\."); 809 810 // Get the type of the attribute. 811 Class<?> c = getPropertyType(o, parts[0]); 812 813 Object i = null; 814 815 // If the next level is a Collection, look into the collection, 816 // to find out what type its elements are. 817 if (Collection.class.isAssignableFrom(c)) { 818 c = getDataObjectService().getMetadataRepository().getMetadata(o.getClass()).getCollection(parts[0]).getRelatedType(); 819 } 820 821 // Look into the attribute class to see if it is writeable. 822 try { 823 i = c.newInstance(); 824 StringBuffer sb = new StringBuffer(); 825 for (int x = 1; x < parts.length; x++) { 826 sb.append(1 == x ? "" : ".").append(parts[x]); 827 } 828 b = isWriteable(i, sb.toString()); 829 } catch (InstantiationException ie) { 830 LOG.info(ie); 831 } catch (IllegalAccessException iae) { 832 LOG.info(iae); 833 } 834 } 835 } else { 836 b = true; 837 } 838 839 return b; 840 } 841 842 protected void addEqualNumeric(Predicates criteria, String propertyName, Class<?> propertyClass, String searchValue) { 843 Predicate pred; 844 if (propertyClass.equals(Long.class)) { 845 pred = PredicateFactory.equal(propertyName, new Long(searchValue)); 846 } else { 847 pred = PredicateFactory.equal(propertyName, new Integer(searchValue)); 848 } 849 850 criteria.addPredicate(pred); 851 } 852 853 protected void addEqualTemporal(Predicates criteria, String propertyName, String searchValue) { 854 try { 855 criteria.addPredicate(PredicateFactory.equal(propertyName, parseDate(LookupUtils.scrubQueryCharacters( 856 searchValue)))); 857 } catch (ParseException ex) { 858 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_DATE, searchValue); 859 } 860 } 861 862 protected void addEqual(Predicates criteria, String propertyName, Object searchValue) { 863 criteria.addPredicate(PredicateFactory.equal(propertyName, searchValue)); 864 } 865 866 protected void addIsNull(Predicates criteria, String propertyName) { 867 criteria.addPredicate(PredicateFactory.isNull(propertyName)); 868 } 869 870 protected void addIsNotNull(Predicates criteria, String propertyName) { 871 criteria.addPredicate(PredicateFactory.isNotNull(propertyName)); 872 } 873 874 protected void addLike(Predicates criteria, String propertyName, String propertyValue) { 875 criteria.addPredicate(PredicateFactory.like(propertyName, propertyValue)); 876 } 877 878 protected void addNotLike(Predicates criteria, String propertyName, String propertyValue) { 879 criteria.addPredicate(PredicateFactory.notLike(propertyName, propertyValue)); 880 } 881 882 protected void addEqualToBoolean(Predicates criteria, String propertyName, String propertyValue) { 883 String temp = LookupUtils.scrubQueryCharacters(propertyValue); 884 criteria.addPredicate(PredicateFactory.equal(propertyName, ("Y".equalsIgnoreCase(temp) || "T".equalsIgnoreCase( 885 temp) || "1".equalsIgnoreCase(temp) || "true".equalsIgnoreCase(temp)))); 886 } 887 888 /** 889 * Should return a string which is a server-side identifier for the uppercased property, 890 * that is, this is not the uppercased version of the property name, but rather the property value uppercased 891 * this is typically a builtin SQL function 892 * @param propertyName the property/column name 893 * @return expression that represents the uppercased value of the property 894 */ 895 protected String uppercasePropertyName(String propertyName) { 896 // return a SQL expression and hope everything goes to plan... 897 return dbPlatform.getUpperCaseFunction() + "(" + propertyName + ")"; 898 } 899 900 protected void addAnd(Predicates criteria, Predicates criteria2) { 901 criteria.and(criteria2); 902 } 903 904 protected void addLessThan(Predicates criteria, String propertyName, Object propertyValue) { 905 criteria.addPredicate(PredicateFactory.lessThan(propertyName, propertyValue)); 906 } 907 908 protected void addLessThanOrEqual(Predicates criteria, String propertyName, Object propertyValue) { 909 criteria.addPredicate(PredicateFactory.lessThanOrEqual(propertyName, propertyValue)); 910 } 911 912 protected void addGreaterThan(Predicates criteria, String propertyName, Object propertyValue) { 913 criteria.addPredicate(PredicateFactory.greaterThan(propertyName, propertyValue)); 914 } 915 916 protected void addGreaterThanOrEqual(Predicates criteria, String propertyName, Object propertyValue) { 917 criteria.addPredicate(PredicateFactory.greaterThanOrEqual(propertyName, propertyValue)); 918 } 919 920 protected void addBetween(Predicates criteria, String propertyName, Object value1, Object value2) { 921 criteria.addPredicate(PredicateFactory.between(propertyName, value1, value2)); 922 } 923 924 protected void addOr(Predicates criteria, Predicates criteria2) { 925 criteria.or(criteria2); 926 } 927 928 protected void addEqual(Predicates criteria, String propertyName, String searchValue, boolean caseInsensitive) { 929 if (caseInsensitive) { 930 criteria.addPredicate(PredicateFactory.equalIgnoreCase(propertyName, searchValue)); 931 } else { 932 addEqual(criteria, propertyName, searchValue); 933 } 934 } 935 936 protected void addGreaterThan(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 937 // XXX: QBC does not support case sensitivity for GT 938 addGreaterThan(criteria, propertyName, propertyValue); 939 } 940 941 protected void addGreaterThanOrEqual(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 942 // XXX: QBC does not support case sensitivity for GTE 943 addGreaterThanOrEqual(criteria, propertyName, propertyValue); 944 } 945 946 protected void addLessThan(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 947 // XXX: QBC does not support case sensitivity for LT 948 addLessThan(criteria, propertyName, propertyValue); 949 } 950 951 protected void addLessThanOrEqual(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 952 // XXX: QBC does not support case sensitivity for LTE 953 addLessThanOrEqual(criteria, propertyName, propertyValue); 954 } 955 956 protected void addLike(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 957 if ( caseInsensitive ) { 958 criteria.addPredicate(PredicateFactory.likeIgnoreCase(propertyName, propertyValue)); 959 } else { 960 addLike(criteria, propertyName, propertyValue); 961 } 962 } 963 964 protected void addBetween(Predicates criteria, String propertyName, String value1, String value2, boolean caseInsensitive) { 965 // XXX: QBC does not support case sensitivity for between 966 addBetween(criteria, propertyName, value1, value2); 967 } 968 969 protected void addNotLike(Predicates criteria, String propertyName, String propertyValue, boolean caseInsensitive) { 970 // XXX: QBC does not support case sensitivity for notlike 971 addNotLike(criteria, propertyName, propertyValue); 972 } 973 974 protected String parsePropertyName(Predicates criteria, String fullyQualifiedPropertyName) { 975 return fullyQualifiedPropertyName; 976 } 977 978 protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue, Predicates criteria, Map searchValues) { 979 // do nothing. QueryByCriteria In and NotIn predicates do not support sub-queries, which means this type of query cannot be 980 // forwarded down to the provider, and that the caller is responsible for filtering in/out "current" data objects from the results 981 } 982 983 /** 984 * Encapsulates our list of Predicates by default we explicitly AND top level predicates ORing requires oring the 985 * existing ANDed predicates, with a new list of ANDed predicates. 986 */ 987 static class Predicates { 988 // top level predicates will be anded by query by default 989 protected List<Predicate> predicates = new ArrayList<Predicate>(); 990 991 void addPredicate(Predicate predicate) { 992 predicates.add(predicate); 993 } 994 995 void or(Predicates pred) { 996 List<Predicate> newpredicates = new ArrayList<Predicate>(); 997 newpredicates.add(PredicateFactory.or(getCriteriaPredicate(), pred.getCriteriaPredicate())); 998 predicates = newpredicates; 999 } 1000 1001 void and(Predicates pred) { 1002 addPredicate(pred.getCriteriaPredicate()); 1003 } 1004 1005 protected Predicate getCriteriaPredicate() { 1006 if (predicates.size() == 1) { 1007 return predicates.get(0); 1008 } 1009 1010 return PredicateFactory.and(predicates.toArray(new Predicate[predicates.size()])); 1011 } 1012 1013 QueryByCriteria.Builder toQueryBuilder() { 1014 QueryByCriteria.Builder qbc = QueryByCriteria.Builder.create(); 1015 qbc.setPredicates(getCriteriaPredicate()); 1016 return qbc; 1017 } 1018 } 1019 1020 static class OrPredicates extends Predicates { 1021 1022 @Override 1023 protected Predicate getCriteriaPredicate() { 1024 if (predicates.size() == 1) { 1025 return predicates.get(0); 1026 } 1027 1028 return PredicateFactory.or(predicates.toArray(new Predicate[predicates.size()])); 1029 } 1030 1031 } 1032 1033}