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    
185            }
186            catch (OjbOperationException e) {
187                    throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e);
188            }
189            catch (DataIntegrityViolationException e) {
190                    throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e);
191            }
192            return new CollectionIncomplete(searchResults, matchingResultsCount);
193        }
194    
195        public boolean createCriteria(Object example, String searchValue, String propertyName, Object criteria) {
196            return createCriteria( example, searchValue, propertyName, false, false, criteria );
197        }
198        
199        public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria) {
200            return createCriteria( example, searchValue, propertyName, false, false, criteria, null );
201        }
202    
203        public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria, Map searchValues) {
204            // if searchValue is empty and the key is not a valid property ignore
205            if (!(criteria instanceof Criteria) || StringUtils.isBlank(searchValue) || !ObjectUtils.isWriteable(example, propertyName, persistenceStructureService)) {
206                return false;
207            }
208    
209            // get property type which is used to determine type of criteria
210            Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
211            if (propertyType == null) {
212                return false;
213            }
214    
215                    // build criteria
216                    if (example instanceof InactivatableFromTo) {
217                            if (KRADPropertyConstants.ACTIVE.equals(propertyName)) {
218                                    addInactivateableFromToActiveCriteria(example, searchValue, (Criteria) criteria, searchValues);
219                            } else if (KRADPropertyConstants.CURRENT.equals(propertyName)) {
220                                    addInactivateableFromToCurrentCriteria(example, searchValue, (Criteria) criteria, searchValues);
221                            } else if (!KRADPropertyConstants.ACTIVE_AS_OF_DATE.equals(propertyName)) {
222                                    addCriteria(propertyName, searchValue, propertyType, caseInsensitive,
223                                                    treatWildcardsAndOperatorsAsLiteral, (Criteria) criteria);
224                            }
225                    } else {
226                            addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral,
227                                            (Criteria) criteria);
228                    }
229            
230            return true;
231        }
232    
233        /**
234         * Find count of records meeting criteria based on the object and map.
235         */
236        public Long findCountByMap(Object example, Map formProps) {
237            Criteria criteria = new Criteria();
238            // iterate through the parameter map for key values search criteria
239            Iterator propsIter = formProps.keySet().iterator();
240            while (propsIter.hasNext()) {
241                String propertyName = (String) propsIter.next();
242                String searchValue = (String) formProps.get(propertyName);
243    
244                // if searchValue is empty and the key is not a valid property ignore
245                if (StringUtils.isBlank(searchValue) || !(PropertyUtils.isWriteable(example, propertyName))) {
246                    continue;
247                }
248    
249                // get property type which is used to determine type of criteria
250                Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
251                if (propertyType == null) {
252                    continue;
253                }
254                    Boolean caseInsensitive = Boolean.TRUE;
255                    if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) {
256                            caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName );
257                    }
258                    if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; }
259    
260                    boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator
261                                            .getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
262                    
263                    if (!caseInsensitive) { 
264                            // Verify that the searchValue is uppercased if caseInsensitive is false 
265                            searchValue = searchValue.toUpperCase(); 
266                    }
267                    
268                // build criteria
269                addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria);
270            }
271    
272            // execute query and return result list
273            Query query = QueryFactory.newQuery(example.getClass(), criteria);
274    
275            return new Long(getPersistenceBrokerTemplate().getCount(query));
276        }
277    
278        /**
279         * @see org.kuali.rice.krad.dao.LookupDao#findObjectByMap(java.lang.Object, java.util.Map)
280         */
281        @Override
282        public <T extends Object> T findObjectByMap(T example, Map<String, String> formProps) {
283            if ( persistenceStructureService.isPersistable(example.getClass())) {
284                    Criteria criteria = new Criteria();
285            
286                    // iterate through the parameter map for search criteria
287                for (Map.Entry<String, String> formProp : formProps.entrySet()) {
288    
289                            String propertyName = formProp.getKey();
290                            String searchValue = "";
291                            if (formProp.getValue() != null) {
292                                    searchValue = formProp.getValue();
293                            }
294            
295                            if (StringUtils.isNotBlank(searchValue) & PropertyUtils.isWriteable(example, propertyName)) {
296                                    Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
297                                    if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) {
298                                            criteria.addEqualTo(propertyName, cleanNumeric(searchValue));
299                                    } else if (TypeUtils.isTemporalClass(propertyType)) {
300                                            criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(searchValue) ) );
301                                    } else {
302                                            criteria.addEqualTo(propertyName, searchValue);
303                                    }
304                            }
305                    }
306            
307                    // execute query and return result list
308                    Query query = QueryFactory.newQuery(example.getClass(), criteria);
309                    return (T)getPersistenceBrokerTemplate().getObjectByQuery(query);
310            }
311            return null;
312        }
313    
314    
315        /**
316         * Adds to the criteria object based on the property type and any query characters given.
317         */
318        private void addCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
319            if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.OR.op())) {
320                addOrCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
321                return;
322            }
323    
324            if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.AND.op())) {
325                addAndCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
326                return;
327            }
328    
329            if (StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NULL.op()) || StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NOT_NULL.op())) {
330                // KULRICE-6846 null Lookup criteria causes sql exception
331                    if (StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
332                            criteria.addNotNull(propertyName);
333                    }
334                    else {
335                            criteria.addIsNull(propertyName);
336                    }
337            }
338            else if (TypeUtils.isStringClass(propertyType)) {
339                    // KULRICE-85 : made string searches case insensitive - used new DBPlatform function to force strings to upper case
340                    if ( caseInsensitive ) {
341                            propertyName = getDbPlatform().getUpperCaseFunction() + "(" + propertyName + ")";
342                            propertyValue = propertyValue.toUpperCase();
343                    }
344                if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
345                    addNotCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
346                } else if (
347                            !treatWildcardsAndOperatorsAsLiteral && propertyValue != null && (
348                                            StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())
349                                            || propertyValue.startsWith(">")
350                                            || propertyValue.startsWith("<") ) ) {
351                    addStringRangeCriteria(propertyName, propertyValue, criteria);
352                } else {
353                    if (treatWildcardsAndOperatorsAsLiteral) {
354                            propertyValue = StringUtils.replace(propertyValue, "*", "\\*");
355                    }
356                    criteria.addLike(propertyName, propertyValue);
357                }
358            } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) {
359                addNumericRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
360            } else if (TypeUtils.isTemporalClass(propertyType)) {
361                addDateRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
362            } else if (TypeUtils.isBooleanClass(propertyType)) {
363                criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
364            } else {
365                LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue);
366            }
367        }
368        
369        /**
370         * Translates criteria for active status to criteria on the active from and to fields
371         * 
372         * @param example - business object being queried on
373         * @param activeSearchValue - value for the active search field, should convert to boolean
374         * @param criteria - Criteria object being built
375         * @param searchValues - Map containing all search keys and values
376         */
377        protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue, Criteria criteria, Map searchValues) {
378                    Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
379                    
380            String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue);
381            if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
382                    // (active from date <= date or active from date is null) and (date < active to date or active to date is null)
383                    Criteria criteriaBeginDate = new Criteria();
384                    criteriaBeginDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
385                    
386                    Criteria criteriaBeginDateNull = new Criteria();
387                    criteriaBeginDateNull.addIsNull(KRADPropertyConstants.ACTIVE_FROM_DATE);
388                    criteriaBeginDate.addOrCriteria(criteriaBeginDateNull);
389                    
390                    criteria.addAndCriteria(criteriaBeginDate);
391                    
392                    Criteria criteriaEndDate = new Criteria();
393                    criteriaEndDate.addGreaterThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
394            
395                    Criteria criteriaEndDateNull = new Criteria();
396                    criteriaEndDateNull.addIsNull(KRADPropertyConstants.ACTIVE_TO_DATE);
397                    criteriaEndDate.addOrCriteria(criteriaEndDateNull);
398                    
399                    criteria.addAndCriteria(criteriaEndDate);
400            }
401            else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
402                    // (date < active from date) or (active from date is null) or (date >= active to date) 
403                    Criteria criteriaNonActive = new Criteria();
404                    criteriaNonActive.addGreaterThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
405                    
406                    Criteria criteriaEndDate = new Criteria();
407                    criteriaEndDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
408                    criteriaNonActive.addOrCriteria(criteriaEndDate);
409                    
410                    criteria.addAndCriteria(criteriaNonActive);
411            }
412        }
413        
414        /**
415         * Translates criteria for current status to criteria on the active from field
416         * 
417         * @param example - business object being queried on
418         * @param currentSearchValue - value for the current search field, should convert to boolean
419         * @param criteria - Criteria object being built
420         */
421            protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue, Criteria criteria, Map searchValues) {
422                    Criteria maxBeginDateCriteria = new Criteria();
423                    
424                    Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
425                    
426                    maxBeginDateCriteria.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
427    
428                    List<String> groupByFieldList = dataDictionaryService.getGroupByAttributesForEffectiveDating(example
429                                    .getClass());
430                    if (groupByFieldList == null) {
431                            return;
432                    }
433    
434                    // join back to main query with the group by fields
435                    String[] groupBy = new String[groupByFieldList.size()];
436                    for (int i = 0; i < groupByFieldList.size(); i++) {
437                            String groupByField = groupByFieldList.get(i);
438                            groupBy[i] = groupByField;
439    
440                            maxBeginDateCriteria.addEqualToField(groupByField, Criteria.PARENT_QUERY_PREFIX + groupByField);
441                    }
442    
443                    String[] columns = new String[1];
444                    columns[0] = "max(" + KRADPropertyConstants.ACTIVE_FROM_DATE + ")";
445    
446                    QueryByCriteria query = QueryFactory.newReportQuery(example.getClass(), columns, maxBeginDateCriteria, true);
447                    query.addGroupBy(groupBy);
448    
449                    String currentBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(currentSearchValue);
450                    if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
451                            criteria.addIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
452                    } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
453                            criteria.addNotIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
454                    }
455            }
456    
457        /**
458         * @param propertyName
459         * @param propertyValue
460         * @param propertyType
461         * @param criteria
462         */
463        private void addOrCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
464            addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.OR.op());
465        }
466           
467        /**
468         * @param propertyName
469         * @param propertyValue
470         * @param propertyType
471         * @param criteria
472         */
473        private void addAndCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
474            addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.AND.op());
475        }
476    
477        private void addNotCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
478    
479            String[] splitPropVal = StringUtils.split(propertyValue, SearchOperator.NOT.op());
480    
481            int strLength = splitPropVal.length;
482            // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b)
483            if (strLength > 1) {
484                String expandedNot = SearchOperator.NOT + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op());
485                // we know that since this method was called, treatWildcardsAndOperatorsAsLiteral must be false
486                addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria);
487            }
488            else {
489                // only one so add a not like
490                criteria.addNotLike(propertyName, splitPropVal[0]);
491            }
492        }
493    
494        /**
495         * Builds a sub criteria object joined with an 'AND' or 'OR' (depending on splitValue) using the split values of propertyValue. Then joins back the
496         * sub criteria to the main criteria using an 'AND'.
497         */
498        private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria, String splitValue) {
499            String[] splitPropVal = StringUtils.split(propertyValue, splitValue);
500    
501            Criteria subCriteria = new Criteria();
502            for (int i = 0; i < splitPropVal.length; i++) {
503                    Criteria predicate = new Criteria();
504    
505                addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate);
506                if (splitValue.equals(SearchOperator.OR.op())) {
507                    subCriteria.addOrCriteria(predicate);
508                }
509                if (splitValue.equals(SearchOperator.AND.op())) {
510                    subCriteria.addAndCriteria(predicate);
511                }
512            }
513    
514            criteria.addAndCriteria(subCriteria);
515        }
516        
517        private java.sql.Date parseDate(String dateString) {
518                    dateString = dateString.trim();
519                    try {
520                            return dateTimeService.convertToSqlDate(dateString);
521                    } catch (ParseException ex) {
522                            return null;
523                    }
524            }
525    
526        /**
527             * Adds to the criteria object based on query characters given
528             */
529        private void addDateRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
530    
531            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
532                    if (treatWildcardsAndOperatorsAsLiteral)
533                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
534                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
535                criteria.addBetween(propertyName, parseDate( ObjectUtils.clean(rangeValues[0] ) ), parseDate( ObjectUtils.clean(rangeValues[1] ) ) );
536            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
537                    if (treatWildcardsAndOperatorsAsLiteral)
538                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
539                criteria.addGreaterOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
540            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
541                    if (treatWildcardsAndOperatorsAsLiteral)
542                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
543                criteria.addLessOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
544            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
545                    if (treatWildcardsAndOperatorsAsLiteral)
546                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
547                criteria.addGreaterThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
548            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
549                    if (treatWildcardsAndOperatorsAsLiteral)
550                            throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
551                criteria.addLessThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
552            } else {
553                criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
554            }
555        }
556    
557        private BigDecimal cleanNumeric( String value ) {
558            String cleanedValue = value.replaceAll( "[^-0-9.]", "" );
559            // ensure only one "minus" at the beginning, if any
560            if ( cleanedValue.lastIndexOf( '-' ) > 0 ) {
561                if ( cleanedValue.charAt( 0 ) == '-' ) {
562                    cleanedValue = "-" + cleanedValue.replaceAll( "-", "" );
563                } else {
564                    cleanedValue = cleanedValue.replaceAll( "-", "" );
565                }
566            }
567            // ensure only one decimal in the string
568            int decimalLoc = cleanedValue.lastIndexOf( '.' );
569            if ( cleanedValue.indexOf( '.' ) != decimalLoc ) {
570                cleanedValue = cleanedValue.substring( 0, decimalLoc ).replaceAll( "\\.", "" ) + cleanedValue.substring( decimalLoc );
571            }
572            try {
573                return new BigDecimal( cleanedValue );
574            } catch ( NumberFormatException ex ) {
575                GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM, new String[] { "Invalid Numeric Input: " + value });
576                return null;
577            }
578        }
579    
580        /**
581         * Adds to the criteria object based on query characters given
582         */
583        private void addNumericRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
584    
585            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
586                    if (treatWildcardsAndOperatorsAsLiteral)
587                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
588                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
589                criteria.addBetween(propertyName, cleanNumeric( rangeValues[0] ), cleanNumeric( rangeValues[1] ));
590            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
591                    if (treatWildcardsAndOperatorsAsLiteral)
592                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
593                criteria.addGreaterOrEqualThan(propertyName, cleanNumeric(propertyValue));
594            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
595                    if (treatWildcardsAndOperatorsAsLiteral)
596                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
597                criteria.addLessOrEqualThan(propertyName, cleanNumeric(propertyValue));
598            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
599                    if (treatWildcardsAndOperatorsAsLiteral)
600                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
601                criteria.addGreaterThan(propertyName, cleanNumeric( propertyValue ) );
602            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
603                    if (treatWildcardsAndOperatorsAsLiteral)
604                            throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
605                criteria.addLessThan(propertyName, cleanNumeric(propertyValue));
606            } else {
607                criteria.addEqualTo(propertyName, cleanNumeric(propertyValue));
608            }
609        }
610    
611        /**
612         * Adds to the criteria object based on query characters given
613         */
614        private void addStringRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
615    
616            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
617                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
618                criteria.addBetween(propertyName, rangeValues[0], rangeValues[1]);
619            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
620                criteria.addGreaterOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
621            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
622                criteria.addLessOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
623            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
624                criteria.addGreaterThan(propertyName, ObjectUtils.clean(propertyValue));
625            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
626                criteria.addLessThan(propertyName, ObjectUtils.clean(propertyValue));
627            } else {
628                    criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
629            }
630        }
631    
632            public void setDateTimeService(DateTimeService dateTimeService) {
633                    this.dateTimeService = dateTimeService;
634            }
635    
636        public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
637            this.persistenceStructureService = persistenceStructureService;
638        }
639    
640        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
641            this.dataDictionaryService = dataDictionaryService;
642        }
643    }