View Javadoc
1   /**
2    * Copyright 2005-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.rice.krad.service.impl;
17  
18  import java.sql.Timestamp;
19  import java.text.ParseException;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.beanutils.PropertyUtils;
27  import org.apache.commons.lang.StringUtils;
28  import org.apache.commons.lang.Validate;
29  import org.apache.log4j.Logger;
30  import org.kuali.rice.core.api.criteria.Predicate;
31  import org.kuali.rice.core.api.criteria.PredicateFactory;
32  import org.kuali.rice.core.api.criteria.QueryByCriteria;
33  import org.kuali.rice.core.api.datetime.DateTimeService;
34  import org.kuali.rice.core.api.search.SearchOperator;
35  import org.kuali.rice.core.api.util.RiceKeyConstants;
36  import org.kuali.rice.core.api.util.type.TypeUtils;
37  import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
38  import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
39  import org.kuali.rice.krad.bo.InactivatableFromTo;
40  import org.kuali.rice.krad.data.DataObjectService;
41  import org.kuali.rice.krad.lookup.LookupInputField;
42  import org.kuali.rice.krad.lookup.LookupUtils;
43  import org.kuali.rice.krad.lookup.LookupView;
44  import org.kuali.rice.krad.service.DataDictionaryService;
45  import org.kuali.rice.krad.uif.UifConstants;
46  import org.kuali.rice.krad.uif.UifParameters;
47  import org.kuali.rice.krad.uif.component.Component;
48  import org.kuali.rice.krad.uif.view.View;
49  import org.kuali.rice.krad.util.GlobalVariables;
50  import org.kuali.rice.krad.util.KRADPropertyConstants;
51  import org.kuali.rice.krad.util.KRADUtils;
52  
53  /**
54   * Base LookupCriteriaGenerator into which logic common to both OJB and JPA for criteria construction
55   * has been extracted.  Subclasses implement backend-specific criteria translation/generation details.
56   */
57  public class LookupCriteriaGeneratorImpl implements LookupCriteriaGenerator {
58  
59      private static final Logger LOG = Logger.getLogger(LookupCriteriaGeneratorImpl.class);
60  
61      private DateTimeService dateTimeService;
62      private DataDictionaryService dataDictionaryService;
63      private DatabasePlatform dbPlatform;
64      private DataObjectService dataObjectService;
65  
66      public DateTimeService getDateTimeService() {
67          return dateTimeService;
68      }
69  
70      public void setDateTimeService(DateTimeService dateTimeService) {
71          this.dateTimeService = dateTimeService;
72      }
73  
74      public DataDictionaryService getDataDictionaryService() {
75          return dataDictionaryService;
76      }
77  
78      public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
79          this.dataDictionaryService = dataDictionaryService;
80      }
81  
82      public DatabasePlatform getDbPlatform() {
83          return dbPlatform;
84      }
85  
86      public void setDbPlatform(DatabasePlatform dbPlatform) {
87          this.dbPlatform = dbPlatform;
88      }
89  
90      public DataObjectService getDataObjectService() {
91          return dataObjectService;
92      }
93  
94      public void setDataObjectService(DataObjectService dataObjectService) {
95          this.dataObjectService = dataObjectService;
96      }
97  
98      @Override
99      @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 }