View Javadoc

1   /**
2    * Copyright 2005-2012 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, 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 }