View Javadoc

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