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}