001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.krad.dao.impl;
017    
018    import org.apache.commons.beanutils.PropertyUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.apache.ojb.broker.query.Criteria;
021    import org.apache.ojb.broker.query.Query;
022    import org.apache.ojb.broker.query.QueryByCriteria;
023    import org.apache.ojb.broker.query.QueryFactory;
024    import org.kuali.rice.core.api.datetime.DateTimeService;
025    import org.kuali.rice.core.api.search.SearchOperator;
026    import org.kuali.rice.core.api.util.RiceKeyConstants;
027    import org.kuali.rice.core.api.util.type.TypeUtils;
028    import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
029    import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb;
030    import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
031    import org.kuali.rice.kns.service.KNSServiceLocator;
032    import org.kuali.rice.krad.bo.BusinessObject;
033    import org.kuali.rice.krad.bo.InactivatableFromTo;
034    import org.kuali.rice.krad.dao.LookupDao;
035    import org.kuali.rice.krad.lookup.CollectionIncomplete;
036    import org.kuali.rice.krad.lookup.LookupUtils;
037    import org.kuali.rice.krad.service.DataDictionaryService;
038    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
039    import org.kuali.rice.krad.service.PersistenceStructureService;
040    import org.kuali.rice.krad.util.GlobalVariables;
041    import org.kuali.rice.krad.util.KRADConstants;
042    import org.kuali.rice.krad.util.KRADPropertyConstants;
043    import org.kuali.rice.krad.util.ObjectUtils;
044    import org.springframework.dao.DataIntegrityViolationException;
045    import org.springmodules.orm.ojb.OjbOperationException;
046    
047    import java.math.BigDecimal;
048    import java.sql.Timestamp;
049    import java.text.ParseException;
050    import java.util.ArrayList;
051    import java.util.Collection;
052    import java.util.Iterator;
053    import java.util.List;
054    import java.util.Map;
055    
056    /**
057     * OJB implementation of the LookupDao interface
058     *
059     * @deprecated use new KRAD Data framework {@link org.kuali.rice.krad.data.DataObjectService}
060     */
061    @Deprecated
062    public class LookupDaoOjb extends PlatformAwareDaoBaseOjb implements LookupDao {
063        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupDaoOjb.class);
064        private DateTimeService dateTimeService;
065        private PersistenceStructureService persistenceStructureService;
066        private DataDictionaryService dataDictionaryService;
067    
068        /**
069         * Since 2.3
070         * This version of findCollectionBySearchHelper is needed for version compatibility.   It allows executeSearch
071         * to behave the same way as it did prior to 2.3. The value for searchResultsLimit will be retrieved from the
072         * KNS version of LookupUtils.
073         *
074         * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean,
075         *      boolean)
076         */
077        public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded,
078                boolean usePrimaryKeyValuesOnly) {
079            Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(businessObjectClass);
080            return findCollectionBySearchHelper(businessObjectClass, formProps, unbounded, usePrimaryKeyValuesOnly,
081                    searchResultsLimit);
082        }
083    
084        /**
085         * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean,
086         *      boolean, Integer)
087         *
088         * If searchResultsLimit is null, the search results will not be limited by any other means.
089         */
090        public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded,
091                boolean usePrimaryKeyValuesOnly, Integer searchResultsLimit) {
092            BusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
093            if (usePrimaryKeyValuesOnly) {
094                return executeSearch(businessObjectClass, getCollectionCriteriaFromMapUsingPrimaryKeysOnly(
095                        businessObjectClass, formProps), unbounded, searchResultsLimit);
096            }
097    
098            Criteria crit = getCollectionCriteriaFromMap(businessObject, formProps);
099            return executeSearch(businessObjectClass, crit, unbounded, searchResultsLimit);
100        }
101    
102        /**
103         * Builds up criteria object based on the object and map.
104         */
105        public Criteria getCollectionCriteriaFromMap(BusinessObject example, Map formProps) {
106            Criteria criteria = new Criteria();
107            Iterator propsIter = formProps.keySet().iterator();
108            while (propsIter.hasNext()) {
109                String propertyName = (String) propsIter.next();
110                Boolean caseInsensitive = Boolean.TRUE;
111                    if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) {
112                            // 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 
113                            // 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 
114                            // could be mixed case.  Thus, caseInsensitive will be the opposite of forceUppercase. 
115                            caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName );
116                    }
117                    if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; }
118                    boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator
119                                    .getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
120                    
121                if (formProps.get(propertyName) instanceof Collection) {
122                    Iterator iter = ((Collection) formProps.get(propertyName)).iterator();
123                    while (iter.hasNext()) {
124                        String searchValue = (String) iter.next();
125                            if (!caseInsensitive) { 
126                                    // Verify that the searchValue is uppercased if caseInsensitive is false 
127                                    searchValue = searchValue.toUpperCase(); 
128                            }
129                        if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps )) {
130                            throw new RuntimeException("Invalid value in Collection");
131                        }
132                    }
133                }
134                else {
135                    String searchValue = (String) formProps.get(propertyName);
136                            if (!caseInsensitive) { 
137                                    // Verify that the searchValue is uppercased if caseInsensitive is false 
138                                    searchValue = searchValue.toUpperCase(); 
139                            }
140                    if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) {
141                        continue;
142                    }
143                }
144            }
145            return criteria;
146        }
147        
148        public Criteria getCollectionCriteriaFromMapUsingPrimaryKeysOnly(Class businessObjectClass, Map formProps) {
149            BusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
150            Criteria criteria = new Criteria();
151            List pkFields = KRADServiceLocatorWeb.getLegacyDataAdapter().listPrimaryKeyFieldNames(businessObjectClass);
152            Iterator pkIter = pkFields.iterator();
153            while (pkIter.hasNext()) {
154                String pkFieldName = (String) pkIter.next();
155                String pkValue = (String) formProps.get(pkFieldName);
156    
157                if (StringUtils.isBlank(pkValue)) {
158                    throw new RuntimeException("Missing pk value for field " + pkFieldName + " when a search based on PK values only is performed.");
159                }
160                else {
161                    for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
162                        if (pkValue.contains(op.op())) {
163                            throw new RuntimeException("Value \"" + pkValue + "\" for PK field " + pkFieldName + " contains wildcard/operator characters.");
164                        }
165                    }
166                }
167                boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.
168                            getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, pkFieldName);
169                createCriteria(businessObject, pkValue, pkFieldName, false, treatWildcardsAndOperatorsAsLiteral, criteria);
170            }
171            return criteria;
172        }
173        
174        private BusinessObject checkBusinessObjectClass(Class businessObjectClass) {
175            if (businessObjectClass == null) {
176                throw new IllegalArgumentException("BusinessObject class passed to LookupDaoOjb findCollectionBySearchHelper... method was null");
177            }
178            BusinessObject businessObject = null;
179            try {
180                businessObject = (BusinessObject) businessObjectClass.newInstance();
181            }
182            catch (IllegalAccessException e) {
183                throw new RuntimeException("LookupDaoOjb could not get instance of " + businessObjectClass.getName(), e);
184            }
185            catch (InstantiationException e) {
186                throw new RuntimeException("LookupDaoOjb could not get instance of " + businessObjectClass.getName(), e);
187            }
188            return businessObject;
189        }
190    
191        private Collection executeSearch(Class businessObjectClass, Criteria criteria, boolean unbounded, Integer searchResultsLimit) {
192            Collection searchResults = new ArrayList();
193            Long matchingResultsCount = null;
194            try {
195                if (!unbounded && searchResultsLimit == null) {
196                    searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(businessObjectClass);
197                }
198                    // A negative number in searchResultsLimit means the search results should be unlimited.
199                if (!unbounded && (searchResultsLimit != null) && searchResultsLimit >= 0) {
200                            matchingResultsCount = new Long(getPersistenceBrokerTemplate().getCount(QueryFactory.newQuery(businessObjectClass, criteria)));
201                            applySearchResultsLimit(businessObjectClass, criteria, getDbPlatform(), searchResultsLimit);
202                    }
203                    if ((matchingResultsCount == null) || (matchingResultsCount.intValue() <= searchResultsLimit.intValue())) {
204                            matchingResultsCount = new Long(0);
205                    }
206                    searchResults = getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(businessObjectClass, criteria));
207                    // populate Person objects in business objects
208                    List bos = new ArrayList();
209                    bos.addAll(searchResults);
210                    searchResults = bos;
211    
212    
213            }
214            catch (OjbOperationException e) {
215                    throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e);
216            }
217            catch (DataIntegrityViolationException e) {
218                    throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e);
219            }
220            return new CollectionIncomplete(searchResults, matchingResultsCount);
221        }
222    
223    
224        /**
225         * This method applies the search results limit to the search criteria for this BO
226         *
227         * @param businessObjectClass BO class to search on / get limit for
228         * @param criteria search criteria
229         * @param platform database platform
230         * @param limit limit to use.  If limit is null, getSearchResultsLimit will be called using the businessObjectClass
231         * to see if a limit can be found for this particular businessObjectClass.
232         */
233        protected void applySearchResultsLimit(Class businessObjectClass, Criteria criteria, DatabasePlatform platform,
234                Integer limit) {
235            String limitSql = null;
236            if (limit != null) {
237                limitSql = platform.applyLimitSql(limit);
238            } else {
239                limit = LookupUtils.getSearchResultsLimit(businessObjectClass, null);
240                if (limit != null) {
241                    limitSql = platform.applyLimitSql(limit);
242                }
243            }
244            if (StringUtils.isNotBlank(limitSql)) {
245                criteria.addSql(limitSql);
246            }
247        }
248    
249        public boolean createCriteria(Object example, String searchValue, String propertyName, Object criteria) {
250            return createCriteria( example, searchValue, propertyName, false, false, criteria );
251        }
252        
253        public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria) {
254            return createCriteria( example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, null );
255        }
256    
257        public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria, Map searchValues) {
258            // if searchValue is empty and the key is not a valid property ignore
259            if (!(criteria instanceof Criteria) || StringUtils.isBlank(searchValue) || !ObjectUtils.isWriteable(example, propertyName, persistenceStructureService)) {
260                return false;
261            }
262    
263            // get property type which is used to determine type of criteria
264            Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
265            if (propertyType == null) {
266                return false;
267            }
268    
269                    // build criteria
270                    if (example instanceof InactivatableFromTo) {
271                            if (KRADPropertyConstants.ACTIVE.equals(propertyName)) {
272                                    addInactivateableFromToActiveCriteria(example, searchValue, (Criteria) criteria, searchValues);
273                            } else if (KRADPropertyConstants.CURRENT.equals(propertyName)) {
274                                    addInactivateableFromToCurrentCriteria(example, searchValue, (Criteria) criteria, searchValues);
275                            } else if (!KRADPropertyConstants.ACTIVE_AS_OF_DATE.equals(propertyName)) {
276                                    addCriteria(propertyName, searchValue, propertyType, caseInsensitive,
277                                                    treatWildcardsAndOperatorsAsLiteral, (Criteria) criteria);
278                            }
279                    } else {
280                            addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral,
281                                            (Criteria) criteria);
282                    }
283            
284            return true;
285        }
286    
287        /**
288         * Find count of records meeting criteria based on the object and map.
289         */
290        public Long findCountByMap(Object example, Map formProps) {
291            Criteria criteria = new Criteria();
292            // iterate through the parameter map for key values search criteria
293            Iterator propsIter = formProps.keySet().iterator();
294            while (propsIter.hasNext()) {
295                String propertyName = (String) propsIter.next();
296                String searchValue = (String) formProps.get(propertyName);
297    
298                // if searchValue is empty and the key is not a valid property ignore
299                if (StringUtils.isBlank(searchValue) || !(PropertyUtils.isWriteable(example, propertyName))) {
300                    continue;
301                }
302    
303                // get property type which is used to determine type of criteria
304                Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
305                if (propertyType == null) {
306                    continue;
307                }
308                    Boolean caseInsensitive = Boolean.TRUE;
309                    if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) {
310                            caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName );
311                    }
312                    if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; }
313    
314                    boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator
315                                            .getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
316                    
317                    if (!caseInsensitive) { 
318                            // Verify that the searchValue is uppercased if caseInsensitive is false 
319                            searchValue = searchValue.toUpperCase(); 
320                    }
321                    
322                // build criteria
323                addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria);
324            }
325    
326            // execute query and return result list
327            Query query = QueryFactory.newQuery(example.getClass(), criteria);
328    
329            return new Long(getPersistenceBrokerTemplate().getCount(query));
330        }
331    
332        /**
333         * @see org.kuali.rice.krad.dao.LookupDao#findObjectByMap(java.lang.Object, java.util.Map)
334         */
335        @Override
336        public <T extends Object> T findObjectByMap(Class<T> type, Map<String, String> formProps) {
337            T example = null;
338            try {
339                example = type.newInstance();
340            } catch (IllegalAccessException e) {
341                throw new RuntimeException("Cannot get new instance of " + type.getName(), e);
342            } catch (InstantiationException e) {
343                throw new RuntimeException("Cannot instantiate " + type.getName(), e);
344            }
345    
346            if ( KRADServiceLocatorWeb.getLegacyDataAdapter().isPersistable(example.getClass())) {
347                    Criteria criteria = new Criteria();
348            
349                    // iterate through the parameter map for search criteria
350                for (Map.Entry<String, String> formProp : formProps.entrySet()) {
351    
352                            String propertyName = formProp.getKey();
353                            String searchValue = "";
354                            if (formProp.getValue() != null) {
355                                    searchValue = formProp.getValue();
356                            }
357            
358                            if (StringUtils.isNotBlank(searchValue) & PropertyUtils.isWriteable(example, propertyName)) {
359                                    Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
360                                    if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) {
361                                            criteria.addEqualTo(propertyName, cleanNumeric(searchValue));
362                                    } else if (TypeUtils.isTemporalClass(propertyType)) {
363                                            criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(searchValue) ) );
364                                    } else {
365                                            criteria.addEqualTo(propertyName, searchValue);
366                                    }
367                            }
368                    }
369            
370                    // execute query and return result list
371                    Query query = QueryFactory.newQuery(example.getClass(), criteria);
372                    return (T)getPersistenceBrokerTemplate().getObjectByQuery(query);
373            }
374            return null;
375        }
376    
377    
378        /**
379         * Adds to the criteria object based on the property type and any query characters given.
380         */
381        private void addCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
382            if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.OR.op())) {
383                addOrCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
384                return;
385            }
386    
387            if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.AND.op())) {
388                addAndCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
389                return;
390            }
391    
392            if (StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NULL.op()) || StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NOT_NULL.op())) {
393                // KULRICE-6846 null Lookup criteria causes sql exception
394                    if (StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
395                            criteria.addNotNull(propertyName);
396                    }
397                    else {
398                            criteria.addIsNull(propertyName);
399                    }
400            }
401            else if (TypeUtils.isStringClass(propertyType)) {
402                    // KULRICE-85 : made string searches case insensitive - used new DBPlatform function to force strings to upper case
403                    if ( caseInsensitive ) {
404                            propertyName = getDbPlatform().getUpperCaseFunction() + "(" + propertyName + ")";
405                            propertyValue = propertyValue.toUpperCase();
406                    }
407                if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
408                    addNotCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
409                } else if (
410                            !treatWildcardsAndOperatorsAsLiteral && propertyValue != null && (
411                                            StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())
412                                            || propertyValue.startsWith(">")
413                                            || propertyValue.startsWith("<") ) ) {
414                    addStringRangeCriteria(propertyName, propertyValue, criteria);
415                } else {
416                    if (treatWildcardsAndOperatorsAsLiteral) {
417                            propertyValue = StringUtils.replace(propertyValue, "*", "\\*");
418                    }
419                    criteria.addLike(propertyName, propertyValue);
420                }
421            } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) {
422                addNumericRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
423            } else if (TypeUtils.isTemporalClass(propertyType)) {
424                addDateRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
425            } else if (TypeUtils.isBooleanClass(propertyType)) {
426                criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
427            } else {
428                LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue);
429            }
430        }
431        
432        /**
433         * Translates criteria for active status to criteria on the active from and to fields
434         * 
435         * @param example - business object being queried on
436         * @param activeSearchValue - value for the active search field, should convert to boolean
437         * @param criteria - Criteria object being built
438         * @param searchValues - Map containing all search keys and values
439         */
440        protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue, Criteria criteria, Map searchValues) {
441                    Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
442                    
443            String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue);
444            if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
445                    // (active from date <= date or active from date is null) and (date < active to date or active to date is null)
446                    Criteria criteriaBeginDate = new Criteria();
447                    criteriaBeginDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
448                    
449                    Criteria criteriaBeginDateNull = new Criteria();
450                    criteriaBeginDateNull.addIsNull(KRADPropertyConstants.ACTIVE_FROM_DATE);
451                    criteriaBeginDate.addOrCriteria(criteriaBeginDateNull);
452                    
453                    criteria.addAndCriteria(criteriaBeginDate);
454                    
455                    Criteria criteriaEndDate = new Criteria();
456                    criteriaEndDate.addGreaterThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
457            
458                    Criteria criteriaEndDateNull = new Criteria();
459                    criteriaEndDateNull.addIsNull(KRADPropertyConstants.ACTIVE_TO_DATE);
460                    criteriaEndDate.addOrCriteria(criteriaEndDateNull);
461                    
462                    criteria.addAndCriteria(criteriaEndDate);
463            }
464            else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
465                    // (date < active from date) or (active from date is null) or (date >= active to date) 
466                    Criteria criteriaNonActive = new Criteria();
467                    criteriaNonActive.addGreaterThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
468                    
469                    Criteria criteriaEndDate = new Criteria();
470                    criteriaEndDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
471                    criteriaNonActive.addOrCriteria(criteriaEndDate);
472                    
473                    criteria.addAndCriteria(criteriaNonActive);
474            }
475        }
476        
477        /**
478         * Translates criteria for current status to criteria on the active from field
479         * 
480         * @param example - business object being queried on
481         * @param currentSearchValue - value for the current search field, should convert to boolean
482         * @param criteria - Criteria object being built
483         */
484            protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue, Criteria criteria, Map searchValues) {
485                    Criteria maxBeginDateCriteria = new Criteria();
486                    
487                    Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
488                    
489                    maxBeginDateCriteria.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
490    
491                    List<String> groupByFieldList = dataDictionaryService.getGroupByAttributesForEffectiveDating(example
492                                    .getClass());
493                    if (groupByFieldList == null) {
494                            return;
495                    }
496    
497                    // join back to main query with the group by fields
498                    String[] groupBy = new String[groupByFieldList.size()];
499                    for (int i = 0; i < groupByFieldList.size(); i++) {
500                            String groupByField = groupByFieldList.get(i);
501                            groupBy[i] = groupByField;
502    
503                            maxBeginDateCriteria.addEqualToField(groupByField, Criteria.PARENT_QUERY_PREFIX + groupByField);
504                    }
505    
506                    String[] columns = new String[1];
507                    columns[0] = "max(" + KRADPropertyConstants.ACTIVE_FROM_DATE + ")";
508    
509                    QueryByCriteria query = QueryFactory.newReportQuery(example.getClass(), columns, maxBeginDateCriteria, true);
510                    query.addGroupBy(groupBy);
511    
512                    String currentBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(currentSearchValue);
513                    if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
514                            criteria.addIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
515                    } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
516                            criteria.addNotIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
517                    }
518            }
519    
520        /**
521         * @param propertyName
522         * @param propertyValue
523         * @param propertyType
524         * @param criteria
525         */
526        private void addOrCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
527            addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.OR.op());
528        }
529           
530        /**
531         * @param propertyName
532         * @param propertyValue
533         * @param propertyType
534         * @param criteria
535         */
536        private void addAndCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
537            addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.AND.op());
538        }
539    
540        private void addNotCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
541    
542            String[] splitPropVal = StringUtils.split(propertyValue, SearchOperator.NOT.op());
543    
544            int strLength = splitPropVal.length;
545            // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b)
546            if (strLength > 1) {
547                String expandedNot = SearchOperator.NOT + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op());
548                // we know that since this method was called, treatWildcardsAndOperatorsAsLiteral must be false
549                addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria);
550            }
551            else {
552                // only one so add a not like
553                criteria.addNotLike(propertyName, splitPropVal[0]);
554            }
555        }
556    
557        /**
558         * Builds a sub criteria object joined with an 'AND' or 'OR' (depending on splitValue) using the split values of propertyValue. Then joins back the
559         * sub criteria to the main criteria using an 'AND'.
560         */
561        private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria, String splitValue) {
562            String[] splitPropVal = StringUtils.split(propertyValue, splitValue);
563    
564            Criteria subCriteria = new Criteria();
565            for (int i = 0; i < splitPropVal.length; i++) {
566                    Criteria predicate = new Criteria();
567    
568                addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate);
569                if (splitValue.equals(SearchOperator.OR.op())) {
570                    subCriteria.addOrCriteria(predicate);
571                }
572                if (splitValue.equals(SearchOperator.AND.op())) {
573                    subCriteria.addAndCriteria(predicate);
574                }
575            }
576    
577            criteria.addAndCriteria(subCriteria);
578        }
579        
580        private java.sql.Date parseDate(String dateString) {
581                    dateString = dateString.trim();
582                    try {
583                            return dateTimeService.convertToSqlDate(dateString);
584                    } catch (ParseException ex) {
585                            return null;
586                    }
587            }
588    
589        /**
590             * Adds to the criteria object based on query characters given
591             */
592        private void addDateRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
593    
594            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
595                    if (treatWildcardsAndOperatorsAsLiteral)
596                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
597                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
598                criteria.addBetween(propertyName, parseDate( ObjectUtils.clean(rangeValues[0] ) ), parseDate( ObjectUtils.clean(rangeValues[1] ) ) );
599            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
600                    if (treatWildcardsAndOperatorsAsLiteral)
601                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
602                criteria.addGreaterOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
603            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
604                    if (treatWildcardsAndOperatorsAsLiteral)
605                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
606                criteria.addLessOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
607            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
608                    if (treatWildcardsAndOperatorsAsLiteral)
609                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
610                criteria.addGreaterThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
611            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
612                    if (treatWildcardsAndOperatorsAsLiteral)
613                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
614                criteria.addLessThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
615            } else {
616                criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
617            }
618        }
619    
620        private BigDecimal cleanNumeric( String value ) {
621            String cleanedValue = value.replaceAll( "[^-0-9.]", "" );
622            // ensure only one "minus" at the beginning, if any
623            if ( cleanedValue.lastIndexOf( '-' ) > 0 ) {
624                if ( cleanedValue.charAt( 0 ) == '-' ) {
625                    cleanedValue = "-" + cleanedValue.replaceAll( "-", "" );
626                } else {
627                    cleanedValue = cleanedValue.replaceAll( "-", "" );
628                }
629            }
630            // ensure only one decimal in the string
631            int decimalLoc = cleanedValue.lastIndexOf( '.' );
632            if ( cleanedValue.indexOf( '.' ) != decimalLoc ) {
633                cleanedValue = cleanedValue.substring( 0, decimalLoc ).replaceAll( "\\.", "" ) + cleanedValue.substring( decimalLoc );
634            }
635            try {
636                return new BigDecimal( cleanedValue );
637            } catch ( NumberFormatException ex ) {
638                GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM, new String[] { "Invalid Numeric Input: " + value });
639                return null;
640            }
641        }
642    
643        /**
644         * Adds to the criteria object based on query characters given
645         */
646        private void addNumericRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
647    
648            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
649                    if (treatWildcardsAndOperatorsAsLiteral)
650                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
651                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
652                criteria.addBetween(propertyName, cleanNumeric( rangeValues[0] ), cleanNumeric( rangeValues[1] ));
653            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
654                    if (treatWildcardsAndOperatorsAsLiteral)
655                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
656                criteria.addGreaterOrEqualThan(propertyName, cleanNumeric(propertyValue));
657            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
658                    if (treatWildcardsAndOperatorsAsLiteral)
659                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
660                criteria.addLessOrEqualThan(propertyName, cleanNumeric(propertyValue));
661            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
662                    if (treatWildcardsAndOperatorsAsLiteral)
663                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
664                criteria.addGreaterThan(propertyName, cleanNumeric( propertyValue ) );
665            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
666                    if (treatWildcardsAndOperatorsAsLiteral)
667                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
668                criteria.addLessThan(propertyName, cleanNumeric(propertyValue));
669            } else {
670                criteria.addEqualTo(propertyName, cleanNumeric(propertyValue));
671            }
672        }
673    
674        /**
675         * Adds to the criteria object based on query characters given
676         */
677        private void addStringRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
678    
679            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
680                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
681                criteria.addBetween(propertyName, rangeValues[0], rangeValues[1]);
682            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
683                criteria.addGreaterOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
684            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
685                criteria.addLessOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
686            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
687                criteria.addGreaterThan(propertyName, ObjectUtils.clean(propertyValue));
688            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
689                criteria.addLessThan(propertyName, ObjectUtils.clean(propertyValue));
690            } else {
691                    criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
692            }
693        }
694    
695            public void setDateTimeService(DateTimeService dateTimeService) {
696                    this.dateTimeService = dateTimeService;
697            }
698    
699        public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
700            this.persistenceStructureService = persistenceStructureService;
701        }
702    
703        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
704            this.dataDictionaryService = dataDictionaryService;
705        }
706    }