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