001    /**
002     * Copyright 2005-2012 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.kns.service.KNSServiceLocator;
031    import org.kuali.rice.krad.bo.BusinessObject;
032    import org.kuali.rice.krad.bo.InactivatableFromTo;
033    import org.kuali.rice.krad.dao.LookupDao;
034    import org.kuali.rice.krad.lookup.CollectionIncomplete;
035    import org.kuali.rice.krad.lookup.LookupUtils;
036    import org.kuali.rice.krad.service.DataDictionaryService;
037    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
038    import org.kuali.rice.krad.service.PersistenceStructureService;
039    import org.kuali.rice.krad.util.GlobalVariables;
040    import org.kuali.rice.krad.util.KRADConstants;
041    import org.kuali.rice.krad.util.KRADPropertyConstants;
042    import org.kuali.rice.krad.util.ObjectUtils;
043    import org.springframework.dao.DataIntegrityViolationException;
044    import org.springmodules.orm.ojb.OjbOperationException;
045    
046    import java.math.BigDecimal;
047    import java.sql.Timestamp;
048    import java.text.ParseException;
049    import java.util.ArrayList;
050    import java.util.Collection;
051    import java.util.Iterator;
052    import java.util.List;
053    import java.util.Map;
054    
055    /**
056     * OJB implementation of the LookupDao interface
057     */
058    public 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, false, false, 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                    if (StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
329                            criteria.addColumnNotNull(propertyName);
330                    }
331                    else {
332                            criteria.addColumnIsNull(propertyName);
333                    }
334            }
335            else if (TypeUtils.isStringClass(propertyType)) {
336                    // KULRICE-85 : made string searches case insensitive - used new DBPlatform function to force strings to upper case
337                    if ( caseInsensitive ) {
338                            propertyName = getDbPlatform().getUpperCaseFunction() + "(" + propertyName + ")";
339                            propertyValue = propertyValue.toUpperCase();
340                    }
341                if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
342                    addNotCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
343                } else if (
344                            !treatWildcardsAndOperatorsAsLiteral && propertyValue != null && (
345                                            StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())
346                                            || propertyValue.startsWith(">")
347                                            || propertyValue.startsWith("<") ) ) {
348                    addStringRangeCriteria(propertyName, propertyValue, criteria);
349                } else {
350                    if (treatWildcardsAndOperatorsAsLiteral) {
351                            propertyValue = StringUtils.replace(propertyValue, "*", "\\*");
352                    }
353                    criteria.addLike(propertyName, propertyValue);
354                }
355            } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) {
356                addNumericRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
357            } else if (TypeUtils.isTemporalClass(propertyType)) {
358                addDateRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
359            } else if (TypeUtils.isBooleanClass(propertyType)) {
360                criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
361            } else {
362                LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue);
363            }
364        }
365        
366        /**
367         * Translates criteria for active status to criteria on the active from and to fields
368         * 
369         * @param example - business object being queried on
370         * @param activeSearchValue - value for the active search field, should convert to boolean
371         * @param criteria - Criteria object being built
372         * @param searchValues - Map containing all search keys and values
373         */
374        protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue, Criteria criteria, Map searchValues) {
375                    Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
376                    
377            String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue);
378            if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
379                    // (active from date <= date or active from date is null) and (date < active to date or active to date is null)
380                    Criteria criteriaBeginDate = new Criteria();
381                    criteriaBeginDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
382                    
383                    Criteria criteriaBeginDateNull = new Criteria();
384                    criteriaBeginDateNull.addIsNull(KRADPropertyConstants.ACTIVE_FROM_DATE);
385                    criteriaBeginDate.addOrCriteria(criteriaBeginDateNull);
386                    
387                    criteria.addAndCriteria(criteriaBeginDate);
388                    
389                    Criteria criteriaEndDate = new Criteria();
390                    criteriaEndDate.addGreaterThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
391            
392                    Criteria criteriaEndDateNull = new Criteria();
393                    criteriaEndDateNull.addIsNull(KRADPropertyConstants.ACTIVE_TO_DATE);
394                    criteriaEndDate.addOrCriteria(criteriaEndDateNull);
395                    
396                    criteria.addAndCriteria(criteriaEndDate);
397            }
398            else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
399                    // (date < active from date) or (active from date is null) or (date >= active to date) 
400                    Criteria criteriaNonActive = new Criteria();
401                    criteriaNonActive.addGreaterThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
402                    
403                    Criteria criteriaEndDate = new Criteria();
404                    criteriaEndDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
405                    criteriaNonActive.addOrCriteria(criteriaEndDate);
406                    
407                    criteria.addAndCriteria(criteriaNonActive);
408            }
409        }
410        
411        /**
412         * Translates criteria for current status to criteria on the active from field
413         * 
414         * @param example - business object being queried on
415         * @param currentSearchValue - value for the current search field, should convert to boolean
416         * @param criteria - Criteria object being built
417         */
418            protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue, Criteria criteria, Map searchValues) {
419                    Criteria maxBeginDateCriteria = new Criteria();
420                    
421                    Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
422                    
423                    maxBeginDateCriteria.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
424    
425                    List<String> groupByFieldList = dataDictionaryService.getGroupByAttributesForEffectiveDating(example
426                                    .getClass());
427                    if (groupByFieldList == null) {
428                            return;
429                    }
430    
431                    // join back to main query with the group by fields
432                    String[] groupBy = new String[groupByFieldList.size()];
433                    for (int i = 0; i < groupByFieldList.size(); i++) {
434                            String groupByField = groupByFieldList.get(i);
435                            groupBy[i] = groupByField;
436    
437                            maxBeginDateCriteria.addEqualToField(groupByField, Criteria.PARENT_QUERY_PREFIX + groupByField);
438                    }
439    
440                    String[] columns = new String[1];
441                    columns[0] = "max(" + KRADPropertyConstants.ACTIVE_FROM_DATE + ")";
442    
443                    QueryByCriteria query = QueryFactory.newReportQuery(example.getClass(), columns, maxBeginDateCriteria, true);
444                    query.addGroupBy(groupBy);
445    
446                    String currentBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(currentSearchValue);
447                    if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
448                            criteria.addIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
449                    } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
450                            criteria.addNotIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
451                    }
452            }
453    
454        /**
455         * @param propertyName
456         * @param propertyValue
457         * @param propertyType
458         * @param criteria
459         */
460        private void addOrCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
461            addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.OR.op());
462        }
463           
464        /**
465         * @param propertyName
466         * @param propertyValue
467         * @param propertyType
468         * @param criteria
469         */
470        private void addAndCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
471            addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.AND.op());
472        }
473    
474        private void addNotCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
475    
476            String[] splitPropVal = StringUtils.split(propertyValue, SearchOperator.NOT.op());
477    
478            int strLength = splitPropVal.length;
479            // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b)
480            if (strLength > 1) {
481                String expandedNot = SearchOperator.NOT + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op());
482                // we know that since this method was called, treatWildcardsAndOperatorsAsLiteral must be false
483                addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria);
484            }
485            else {
486                // only one so add a not like
487                criteria.addNotLike(propertyName, splitPropVal[0]);
488            }
489        }
490    
491        /**
492         * Builds a sub criteria object joined with an 'AND' or 'OR' (depending on splitValue) using the split values of propertyValue. Then joins back the
493         * sub criteria to the main criteria using an 'AND'.
494         */
495        private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria, String splitValue) {
496            String[] splitPropVal = StringUtils.split(propertyValue, splitValue);
497    
498            Criteria subCriteria = new Criteria();
499            for (int i = 0; i < splitPropVal.length; i++) {
500                    Criteria predicate = new Criteria();
501    
502                addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate);
503                if (splitValue.equals(SearchOperator.OR.op())) {
504                    subCriteria.addOrCriteria(predicate);
505                }
506                if (splitValue.equals(SearchOperator.AND.op())) {
507                    subCriteria.addAndCriteria(predicate);
508                }
509            }
510    
511            criteria.addAndCriteria(subCriteria);
512        }
513        
514        private java.sql.Date parseDate(String dateString) {
515                    dateString = dateString.trim();
516                    try {
517                            return dateTimeService.convertToSqlDate(dateString);
518                    } catch (ParseException ex) {
519                            return null;
520                    }
521            }
522    
523        /**
524             * Adds to the criteria object based on query characters given
525             */
526        private void addDateRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
527    
528            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
529                    if (treatWildcardsAndOperatorsAsLiteral)
530                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
531                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
532                criteria.addBetween(propertyName, parseDate( ObjectUtils.clean(rangeValues[0] ) ), parseDate( ObjectUtils.clean(rangeValues[1] ) ) );
533            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
534                    if (treatWildcardsAndOperatorsAsLiteral)
535                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
536                criteria.addGreaterOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
537            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
538                    if (treatWildcardsAndOperatorsAsLiteral)
539                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
540                criteria.addLessOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
541            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
542                    if (treatWildcardsAndOperatorsAsLiteral)
543                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
544                criteria.addGreaterThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
545            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
546                    if (treatWildcardsAndOperatorsAsLiteral)
547                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
548                criteria.addLessThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
549            } else {
550                criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
551            }
552        }
553    
554        private BigDecimal cleanNumeric( String value ) {
555            String cleanedValue = value.replaceAll( "[^-0-9.]", "" );
556            // ensure only one "minus" at the beginning, if any
557            if ( cleanedValue.lastIndexOf( '-' ) > 0 ) {
558                if ( cleanedValue.charAt( 0 ) == '-' ) {
559                    cleanedValue = "-" + cleanedValue.replaceAll( "-", "" );
560                } else {
561                    cleanedValue = cleanedValue.replaceAll( "-", "" );
562                }
563            }
564            // ensure only one decimal in the string
565            int decimalLoc = cleanedValue.lastIndexOf( '.' );
566            if ( cleanedValue.indexOf( '.' ) != decimalLoc ) {
567                cleanedValue = cleanedValue.substring( 0, decimalLoc ).replaceAll( "\\.", "" ) + cleanedValue.substring( decimalLoc );
568            }
569            try {
570                return new BigDecimal( cleanedValue );
571            } catch ( NumberFormatException ex ) {
572                GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM, new String[] { "Invalid Numeric Input: " + value });
573                return null;
574            }
575        }
576    
577        /**
578         * Adds to the criteria object based on query characters given
579         */
580        private void addNumericRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
581    
582            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
583                    if (treatWildcardsAndOperatorsAsLiteral)
584                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
585                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
586                criteria.addBetween(propertyName, cleanNumeric( rangeValues[0] ), cleanNumeric( rangeValues[1] ));
587            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
588                    if (treatWildcardsAndOperatorsAsLiteral)
589                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
590                criteria.addGreaterOrEqualThan(propertyName, cleanNumeric(propertyValue));
591            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
592                    if (treatWildcardsAndOperatorsAsLiteral)
593                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
594                criteria.addLessOrEqualThan(propertyName, cleanNumeric(propertyValue));
595            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
596                    if (treatWildcardsAndOperatorsAsLiteral)
597                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
598                criteria.addGreaterThan(propertyName, cleanNumeric( propertyValue ) );
599            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
600                    if (treatWildcardsAndOperatorsAsLiteral)
601                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
602                criteria.addLessThan(propertyName, cleanNumeric(propertyValue));
603            } else {
604                criteria.addEqualTo(propertyName, cleanNumeric(propertyValue));
605            }
606        }
607    
608        /**
609         * Adds to the criteria object based on query characters given
610         */
611        private void addStringRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
612    
613            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
614                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
615                criteria.addBetween(propertyName, rangeValues[0], rangeValues[1]);
616            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
617                criteria.addGreaterOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
618            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
619                criteria.addLessOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
620            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
621                criteria.addGreaterThan(propertyName, ObjectUtils.clean(propertyValue));
622            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
623                criteria.addLessThan(propertyName, ObjectUtils.clean(propertyValue));
624            } else {
625                    criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
626            }
627        }
628    
629            public void setDateTimeService(DateTimeService dateTimeService) {
630                    this.dateTimeService = dateTimeService;
631            }
632    
633        public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
634            this.persistenceStructureService = persistenceStructureService;
635        }
636    
637        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
638            this.dataDictionaryService = dataDictionaryService;
639        }
640    }