View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.dao.impl;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.ojb.broker.query.Criteria;
21  import org.apache.ojb.broker.query.Query;
22  import org.apache.ojb.broker.query.QueryByCriteria;
23  import org.apache.ojb.broker.query.QueryFactory;
24  import org.kuali.rice.core.api.datetime.DateTimeService;
25  import org.kuali.rice.core.api.search.SearchOperator;
26  import org.kuali.rice.core.api.util.RiceKeyConstants;
27  import org.kuali.rice.core.api.util.type.TypeUtils;
28  import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
29  import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb;
30  import org.kuali.rice.kns.service.KNSServiceLocator;
31  import org.kuali.rice.krad.bo.BusinessObject;
32  import org.kuali.rice.krad.bo.InactivatableFromTo;
33  import org.kuali.rice.krad.dao.LookupDao;
34  import org.kuali.rice.krad.lookup.CollectionIncomplete;
35  import org.kuali.rice.krad.lookup.LookupUtils;
36  import org.kuali.rice.krad.service.DataDictionaryService;
37  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
38  import org.kuali.rice.krad.service.PersistenceStructureService;
39  import org.kuali.rice.krad.util.GlobalVariables;
40  import org.kuali.rice.krad.util.KRADConstants;
41  import org.kuali.rice.krad.util.KRADPropertyConstants;
42  import org.kuali.rice.krad.util.ObjectUtils;
43  import org.springframework.dao.DataIntegrityViolationException;
44  import org.springmodules.orm.ojb.OjbOperationException;
45  
46  import java.math.BigDecimal;
47  import java.sql.Timestamp;
48  import java.text.ParseException;
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.Iterator;
52  import java.util.List;
53  import java.util.Map;
54  
55  /**
56   * OJB implementation of the LookupDao interface
57   */
58  public class LookupDaoOjb extends PlatformAwareDaoBaseOjb implements LookupDao {
59      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupDaoOjb.class);
60      private DateTimeService dateTimeService;
61      private PersistenceStructureService persistenceStructureService;
62      private DataDictionaryService dataDictionaryService;
63  
64      public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded, boolean usePrimaryKeyValuesOnly) {
65          BusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
66          if (usePrimaryKeyValuesOnly) {
67          	return executeSearch(businessObjectClass, getCollectionCriteriaFromMapUsingPrimaryKeysOnly(businessObjectClass, formProps), unbounded);
68          }
69          
70  		Criteria crit = getCollectionCriteriaFromMap(businessObject, formProps);
71  		return executeSearch(businessObjectClass, crit, unbounded);
72  	}
73  
74      /**
75       * Builds up criteria object based on the object and map.
76       */
77      public Criteria getCollectionCriteriaFromMap(BusinessObject example, Map formProps) {
78          Criteria criteria = new Criteria();
79          Iterator propsIter = formProps.keySet().iterator();
80          while (propsIter.hasNext()) {
81              String propertyName = (String) propsIter.next();
82              Boolean caseInsensitive = Boolean.TRUE;
83          	if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) {
84          		// 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 
85          		// 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 
86          		// could be mixed case.  Thus, caseInsensitive will be the opposite of forceUppercase. 
87          		caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName );
88          	}
89          	if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; }
90          	boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator
91          			.getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
92          	
93              if (formProps.get(propertyName) instanceof Collection) {
94                  Iterator iter = ((Collection) formProps.get(propertyName)).iterator();
95                  while (iter.hasNext()) {
96                      String searchValue = (String) iter.next();
97              		if (!caseInsensitive) { 
98              			// Verify that the searchValue is uppercased if caseInsensitive is false 
99              			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 }