View Javadoc
1   /**
2    * Copyright 2005-2015 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      /**
65       * Since 2.3
66       * This version of findCollectionBySearchHelper is needed for version compatibility.   It allows executeSearch
67       * to behave the same way as it did prior to 2.3. The value for searchResultsLimit will be retrieved from the
68       * KNS version of LookupUtils.
69       *
70       * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean,
71       *      boolean)
72       */
73      public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded,
74              boolean usePrimaryKeyValuesOnly) {
75          Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(businessObjectClass);
76          return findCollectionBySearchHelper(businessObjectClass, formProps, unbounded, usePrimaryKeyValuesOnly,
77                  searchResultsLimit);
78      }
79  
80      /**
81       * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean,
82       *      boolean, Integer)
83       *
84       * If searchResultsLimit is null, the search results will not be limited by any other means.
85       */
86      public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded,
87              boolean usePrimaryKeyValuesOnly, Integer searchResultsLimit) {
88          BusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
89          if (usePrimaryKeyValuesOnly) {
90              return executeSearch(businessObjectClass, getCollectionCriteriaFromMapUsingPrimaryKeysOnly(
91                      businessObjectClass, formProps), unbounded, searchResultsLimit);
92          }
93  
94          Criteria crit = getCollectionCriteriaFromMap(businessObject, formProps);
95          return executeSearch(businessObjectClass, crit, unbounded, searchResultsLimit);
96      }
97  
98      /**
99       * Builds up criteria object based on the object and map.
100      */
101     public Criteria getCollectionCriteriaFromMap(BusinessObject example, Map formProps) {
102         Criteria criteria = new Criteria();
103         Iterator propsIter = formProps.keySet().iterator();
104         while (propsIter.hasNext()) {
105             String propertyName = (String) propsIter.next();
106             Boolean caseInsensitive = Boolean.TRUE;
107         	if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) {
108         		// 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 
109         		// 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 
110         		// could be mixed case.  Thus, caseInsensitive will be the opposite of forceUppercase. 
111         		caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName );
112         	}
113         	if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; }
114         	boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator
115         			.getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
116         	
117             if (formProps.get(propertyName) instanceof Collection) {
118                 Iterator iter = ((Collection) formProps.get(propertyName)).iterator();
119                 while (iter.hasNext()) {
120                     String searchValue = (String) iter.next();
121             		if (!caseInsensitive) { 
122             			// Verify that the searchValue is uppercased if caseInsensitive is false 
123             			searchValue = searchValue.toUpperCase(); 
124             		}
125                     if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps )) {
126                         throw new RuntimeException("Invalid value in Collection");
127                     }
128                 }
129             }
130             else {
131                 String searchValue = (String) formProps.get(propertyName);
132         		if (!caseInsensitive) { 
133         			// Verify that the searchValue is uppercased if caseInsensitive is false 
134         			searchValue = searchValue.toUpperCase(); 
135         		}
136                 if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) {
137                     continue;
138                 }
139             }
140         }
141         return criteria;
142     }
143     
144     public Criteria getCollectionCriteriaFromMapUsingPrimaryKeysOnly(Class businessObjectClass, Map formProps) {
145         BusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
146         Criteria criteria = new Criteria();
147         List pkFields = KRADServiceLocatorWeb.getDataObjectMetaDataService().listPrimaryKeyFieldNames(businessObjectClass);
148         Iterator pkIter = pkFields.iterator();
149         while (pkIter.hasNext()) {
150             String pkFieldName = (String) pkIter.next();
151             String pkValue = (String) formProps.get(pkFieldName);
152 
153             if (StringUtils.isBlank(pkValue)) {
154                 throw new RuntimeException("Missing pk value for field " + pkFieldName + " when a search based on PK values only is performed.");
155             }
156             else {
157                 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
158                     if (pkValue.contains(op.op())) {
159                         throw new RuntimeException("Value \"" + pkValue + "\" for PK field " + pkFieldName + " contains wildcard/operator characters.");
160                     }
161                 }
162             }
163             boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.
164             		getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, pkFieldName);
165             createCriteria(businessObject, pkValue, pkFieldName, false, treatWildcardsAndOperatorsAsLiteral, criteria);
166         }
167         return criteria;
168     }
169     
170     private BusinessObject checkBusinessObjectClass(Class businessObjectClass) {
171         if (businessObjectClass == null) {
172             throw new IllegalArgumentException("BusinessObject class passed to LookupDaoOjb findCollectionBySearchHelper... method was null");
173         }
174         BusinessObject businessObject = null;
175         try {
176             businessObject = (BusinessObject) businessObjectClass.newInstance();
177         }
178         catch (IllegalAccessException e) {
179             throw new RuntimeException("LookupDaoOjb could not get instance of " + businessObjectClass.getName(), e);
180         }
181         catch (InstantiationException e) {
182             throw new RuntimeException("LookupDaoOjb could not get instance of " + businessObjectClass.getName(), e);
183         }
184         return businessObject;
185     }
186 
187     private Collection executeSearch(Class businessObjectClass, Criteria criteria, boolean unbounded, Integer searchResultsLimit) {
188     	Collection searchResults = new ArrayList();
189     	Long matchingResultsCount = null;
190     	try {
191     		// A negative number in searchResultsLimit means the search results should be unlimited.
192             if (!unbounded && (searchResultsLimit != null) && searchResultsLimit >= 0) {
193     			matchingResultsCount = new Long(getPersistenceBrokerTemplate().getCount(QueryFactory.newQuery(businessObjectClass, criteria)));
194     			LookupUtils.applySearchResultsLimit(businessObjectClass, criteria, getDbPlatform(), searchResultsLimit);
195     		}
196     		if ((matchingResultsCount == null) || (matchingResultsCount.intValue() <= searchResultsLimit.intValue())) {
197     			matchingResultsCount = new Long(0);
198     		}
199     		searchResults = getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(businessObjectClass, criteria));
200     		// populate Person objects in business objects
201     		List bos = new ArrayList();
202     		bos.addAll(searchResults);
203     		searchResults = bos;
204 
205 
206     	}
207     	catch (OjbOperationException e) {
208     		throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e);
209     	}
210     	catch (DataIntegrityViolationException e) {
211     		throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e);
212     	}
213     	return new CollectionIncomplete(searchResults, matchingResultsCount);
214     }
215 
216     public boolean createCriteria(Object example, String searchValue, String propertyName, Object criteria) {
217     	return createCriteria( example, searchValue, propertyName, false, false, criteria );
218     }
219     
220     public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria) {
221     	return createCriteria( example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, null );
222     }
223 
224     public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria, Map searchValues) {
225         // if searchValue is empty and the key is not a valid property ignore
226         if (!(criteria instanceof Criteria) || StringUtils.isBlank(searchValue) || !ObjectUtils.isWriteable(example, propertyName, persistenceStructureService)) {
227             return false;
228         }
229 
230         // get property type which is used to determine type of criteria
231         Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
232         if (propertyType == null) {
233             return false;
234         }
235 
236 		// build criteria
237 		if (example instanceof InactivatableFromTo) {
238 			if (KRADPropertyConstants.ACTIVE.equals(propertyName)) {
239 				addInactivateableFromToActiveCriteria(example, searchValue, (Criteria) criteria, searchValues);
240 			} else if (KRADPropertyConstants.CURRENT.equals(propertyName)) {
241 				addInactivateableFromToCurrentCriteria(example, searchValue, (Criteria) criteria, searchValues);
242 			} else if (!KRADPropertyConstants.ACTIVE_AS_OF_DATE.equals(propertyName)) {
243 				addCriteria(propertyName, searchValue, propertyType, caseInsensitive,
244 						treatWildcardsAndOperatorsAsLiteral, (Criteria) criteria);
245 			}
246 		} else {
247 			addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral,
248 					(Criteria) criteria);
249 		}
250         
251         return true;
252     }
253 
254     /**
255      * Find count of records meeting criteria based on the object and map.
256      */
257     public Long findCountByMap(Object example, Map formProps) {
258         Criteria criteria = new Criteria();
259         // iterate through the parameter map for key values search criteria
260         Iterator propsIter = formProps.keySet().iterator();
261         while (propsIter.hasNext()) {
262             String propertyName = (String) propsIter.next();
263             String searchValue = (String) formProps.get(propertyName);
264 
265             // if searchValue is empty and the key is not a valid property ignore
266             if (StringUtils.isBlank(searchValue) || !(PropertyUtils.isWriteable(example, propertyName))) {
267                 continue;
268             }
269 
270             // get property type which is used to determine type of criteria
271             Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
272             if (propertyType == null) {
273                 continue;
274             }
275         	Boolean caseInsensitive = Boolean.TRUE;
276         	if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) {
277         		caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName );
278         	}
279         	if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; }
280 
281         	boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator
282 					.getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
283         	
284     		if (!caseInsensitive) { 
285     			// Verify that the searchValue is uppercased if caseInsensitive is false 
286     			searchValue = searchValue.toUpperCase(); 
287     		}
288         	
289             // build criteria
290             addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria);
291         }
292 
293         // execute query and return result list
294         Query query = QueryFactory.newQuery(example.getClass(), criteria);
295 
296         return new Long(getPersistenceBrokerTemplate().getCount(query));
297     }
298 
299     /**
300      * @see org.kuali.rice.krad.dao.LookupDao#findObjectByMap(java.lang.Object, java.util.Map)
301      */
302     @Override
303     public <T extends Object> T findObjectByMap(T example, Map<String, String> formProps) {
304     	if ( persistenceStructureService.isPersistable(example.getClass())) {
305 	    	Criteria criteria = new Criteria();
306 	
307 	    	// iterate through the parameter map for search criteria
308             for (Map.Entry<String, String> formProp : formProps.entrySet()) {
309 
310 	    		String propertyName = formProp.getKey();
311 	    		String searchValue = "";
312 	    		if (formProp.getValue() != null) {
313 	    			searchValue = formProp.getValue();
314 	    		}
315 	
316 	    		if (StringUtils.isNotBlank(searchValue) & PropertyUtils.isWriteable(example, propertyName)) {
317 	    			Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
318 	    			if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) {
319 	    				criteria.addEqualTo(propertyName, cleanNumeric(searchValue));
320 	    			} else if (TypeUtils.isTemporalClass(propertyType)) {
321 	    				criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(searchValue) ) );
322 	    			} else {
323 	    				criteria.addEqualTo(propertyName, searchValue);
324 	    			}
325 	    		}
326 	    	}
327 	
328 	    	// execute query and return result list
329 	    	Query query = QueryFactory.newQuery(example.getClass(), criteria);
330 	    	return (T)getPersistenceBrokerTemplate().getObjectByQuery(query);
331     	}
332     	return null;
333     }
334 
335 
336     /**
337      * Adds to the criteria object based on the property type and any query characters given.
338      */
339     private void addCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
340         if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.OR.op())) {
341             addOrCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
342             return;
343         }
344 
345         if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.AND.op())) {
346             addAndCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
347             return;
348         }
349 
350         if (StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NULL.op()) || StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NOT_NULL.op())) {
351             // KULRICE-6846 null Lookup criteria causes sql exception
352         	if (StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
353         		criteria.addNotNull(propertyName);
354         	}
355         	else {
356         		criteria.addIsNull(propertyName);
357         	}
358         }
359         else if (TypeUtils.isStringClass(propertyType)) {
360         	// KULRICE-85 : made string searches case insensitive - used new DBPlatform function to force strings to upper case
361         	if ( caseInsensitive ) {
362         		propertyName = getDbPlatform().getUpperCaseFunction() + "(" + propertyName + ")";
363         		propertyValue = propertyValue.toUpperCase();
364         	}
365             if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
366                 addNotCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
367             } else if (
368             		!treatWildcardsAndOperatorsAsLiteral && propertyValue != null && (
369             				StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())
370             				|| propertyValue.startsWith(">")
371             				|| propertyValue.startsWith("<") ) ) {
372                 addStringRangeCriteria(propertyName, propertyValue, criteria);
373             } else {
374             	if (treatWildcardsAndOperatorsAsLiteral) {
375             		propertyValue = StringUtils.replace(propertyValue, "*", "\\*");
376             	}
377             	criteria.addLike(propertyName, propertyValue);
378             }
379         } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) {
380             addNumericRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
381         } else if (TypeUtils.isTemporalClass(propertyType)) {
382             addDateRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
383         } else if (TypeUtils.isBooleanClass(propertyType)) {
384             criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
385         } else {
386             LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue);
387         }
388     }
389     
390     /**
391      * Translates criteria for active status to criteria on the active from and to fields
392      * 
393      * @param example - business object being queried on
394      * @param activeSearchValue - value for the active search field, should convert to boolean
395      * @param criteria - Criteria object being built
396      * @param searchValues - Map containing all search keys and values
397      */
398     protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue, Criteria criteria, Map searchValues) {
399 		Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
400 		
401     	String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue);
402     	if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
403     		// (active from date <= date or active from date is null) and (date < active to date or active to date is null)
404     		Criteria criteriaBeginDate = new Criteria();
405     		criteriaBeginDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
406     		
407     		Criteria criteriaBeginDateNull = new Criteria();
408     		criteriaBeginDateNull.addIsNull(KRADPropertyConstants.ACTIVE_FROM_DATE);
409     		criteriaBeginDate.addOrCriteria(criteriaBeginDateNull);
410     		
411     		criteria.addAndCriteria(criteriaBeginDate);
412     		
413     		Criteria criteriaEndDate = new Criteria();
414     		criteriaEndDate.addGreaterThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
415     	
416     		Criteria criteriaEndDateNull = new Criteria();
417     		criteriaEndDateNull.addIsNull(KRADPropertyConstants.ACTIVE_TO_DATE);
418     		criteriaEndDate.addOrCriteria(criteriaEndDateNull);
419     		
420     		criteria.addAndCriteria(criteriaEndDate);
421     	}
422     	else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
423     		// (date < active from date) or (active from date is null) or (date >= active to date) 
424     		Criteria criteriaNonActive = new Criteria();
425     		criteriaNonActive.addGreaterThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
426     		
427     		Criteria criteriaEndDate = new Criteria();
428     		criteriaEndDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
429     		criteriaNonActive.addOrCriteria(criteriaEndDate);
430     		
431     		criteria.addAndCriteria(criteriaNonActive);
432     	}
433     }
434     
435     /**
436      * Translates criteria for current status to criteria on the active from field
437      * 
438      * @param example - business object being queried on
439      * @param currentSearchValue - value for the current search field, should convert to boolean
440      * @param criteria - Criteria object being built
441      */
442 	protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue, Criteria criteria, Map searchValues) {
443 		Criteria maxBeginDateCriteria = new Criteria();
444 		
445 		Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
446 		
447 		maxBeginDateCriteria.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
448 
449 		List<String> groupByFieldList = dataDictionaryService.getGroupByAttributesForEffectiveDating(example
450 				.getClass());
451 		if (groupByFieldList == null) {
452 			return;
453 		}
454 
455 		// join back to main query with the group by fields
456 		String[] groupBy = new String[groupByFieldList.size()];
457 		for (int i = 0; i < groupByFieldList.size(); i++) {
458 			String groupByField = groupByFieldList.get(i);
459 			groupBy[i] = groupByField;
460 
461 			maxBeginDateCriteria.addEqualToField(groupByField, Criteria.PARENT_QUERY_PREFIX + groupByField);
462 		}
463 
464 		String[] columns = new String[1];
465 		columns[0] = "max(" + KRADPropertyConstants.ACTIVE_FROM_DATE + ")";
466 
467 		QueryByCriteria query = QueryFactory.newReportQuery(example.getClass(), columns, maxBeginDateCriteria, true);
468 		query.addGroupBy(groupBy);
469 
470 		String currentBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(currentSearchValue);
471 		if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
472 			criteria.addIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
473 		} else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
474 			criteria.addNotIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
475 		}
476 	}
477 
478     /**
479      * @param propertyName
480      * @param propertyValue
481      * @param propertyType
482      * @param criteria
483      */
484     private void addOrCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
485         addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.OR.op());
486     }
487        
488     /**
489      * @param propertyName
490      * @param propertyValue
491      * @param propertyType
492      * @param criteria
493      */
494     private void addAndCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
495         addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.AND.op());
496     }
497 
498     private void addNotCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
499 
500         String[] splitPropVal = StringUtils.split(propertyValue, SearchOperator.NOT.op());
501 
502         int strLength = splitPropVal.length;
503         // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b)
504         if (strLength > 1) {
505             String expandedNot = SearchOperator.NOT + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op());
506             // we know that since this method was called, treatWildcardsAndOperatorsAsLiteral must be false
507             addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria);
508         }
509         else {
510             // only one so add a not like
511             criteria.addNotLike(propertyName, splitPropVal[0]);
512         }
513     }
514 
515     /**
516      * Builds a sub criteria object joined with an 'AND' or 'OR' (depending on splitValue) using the split values of propertyValue. Then joins back the
517      * sub criteria to the main criteria using an 'AND'.
518      */
519     private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria, String splitValue) {
520         String[] splitPropVal = StringUtils.split(propertyValue, splitValue);
521 
522         Criteria subCriteria = new Criteria();
523         for (int i = 0; i < splitPropVal.length; i++) {
524         	Criteria predicate = new Criteria();
525 
526             addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate);
527             if (splitValue.equals(SearchOperator.OR.op())) {
528             	subCriteria.addOrCriteria(predicate);
529             }
530             if (splitValue.equals(SearchOperator.AND.op())) {
531             	subCriteria.addAndCriteria(predicate);
532             }
533         }
534 
535         criteria.addAndCriteria(subCriteria);
536     }
537     
538     private java.sql.Date parseDate(String dateString) {
539 		dateString = dateString.trim();
540 		try {
541 			return dateTimeService.convertToSqlDate(dateString);
542 		} catch (ParseException ex) {
543 			return null;
544 		}
545 	}
546 
547     /**
548 	 * Adds to the criteria object based on query characters given
549 	 */
550     private void addDateRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
551 
552         if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
553         	if (treatWildcardsAndOperatorsAsLiteral)
554         		throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
555             String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
556             criteria.addBetween(propertyName, parseDate( ObjectUtils.clean(rangeValues[0] ) ), parseDate( ObjectUtils.clean(rangeValues[1] ) ) );
557         } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
558         	if (treatWildcardsAndOperatorsAsLiteral)
559         		throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
560             criteria.addGreaterOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
561         } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
562         	if (treatWildcardsAndOperatorsAsLiteral)
563         		throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
564             criteria.addLessOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
565         } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
566         	if (treatWildcardsAndOperatorsAsLiteral)
567         		throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
568             criteria.addGreaterThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
569         } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
570         	if (treatWildcardsAndOperatorsAsLiteral)
571         		throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
572             criteria.addLessThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
573         } else {
574             criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
575         }
576     }
577 
578     private BigDecimal cleanNumeric( String value ) {
579         String cleanedValue = value.replaceAll( "[^-0-9.]", "" );
580         // ensure only one "minus" at the beginning, if any
581         if ( cleanedValue.lastIndexOf( '-' ) > 0 ) {
582             if ( cleanedValue.charAt( 0 ) == '-' ) {
583                 cleanedValue = "-" + cleanedValue.replaceAll( "-", "" );
584             } else {
585                 cleanedValue = cleanedValue.replaceAll( "-", "" );
586             }
587         }
588         // ensure only one decimal in the string
589         int decimalLoc = cleanedValue.lastIndexOf( '.' );
590         if ( cleanedValue.indexOf( '.' ) != decimalLoc ) {
591             cleanedValue = cleanedValue.substring( 0, decimalLoc ).replaceAll( "\\.", "" ) + cleanedValue.substring( decimalLoc );
592         }
593         try {
594             return new BigDecimal( cleanedValue );
595         } catch ( NumberFormatException ex ) {
596             GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM, new String[] { "Invalid Numeric Input: " + value });
597             return null;
598         }
599     }
600 
601     /**
602      * Adds to the criteria object based on query characters given
603      */
604     private void addNumericRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
605 
606         if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
607         	if (treatWildcardsAndOperatorsAsLiteral)
608         		throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
609             String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
610             criteria.addBetween(propertyName, cleanNumeric( rangeValues[0] ), cleanNumeric( rangeValues[1] ));
611         } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
612         	if (treatWildcardsAndOperatorsAsLiteral)
613         		throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
614             criteria.addGreaterOrEqualThan(propertyName, cleanNumeric(propertyValue));
615         } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
616         	if (treatWildcardsAndOperatorsAsLiteral)
617         		throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
618             criteria.addLessOrEqualThan(propertyName, cleanNumeric(propertyValue));
619         } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
620         	if (treatWildcardsAndOperatorsAsLiteral)
621         		throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
622             criteria.addGreaterThan(propertyName, cleanNumeric( propertyValue ) );
623         } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
624         	if (treatWildcardsAndOperatorsAsLiteral)
625         		throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
626             criteria.addLessThan(propertyName, cleanNumeric(propertyValue));
627         } else {
628             criteria.addEqualTo(propertyName, cleanNumeric(propertyValue));
629         }
630     }
631 
632     /**
633      * Adds to the criteria object based on query characters given
634      */
635     private void addStringRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
636 
637         if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
638             String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
639             criteria.addBetween(propertyName, rangeValues[0], rangeValues[1]);
640         } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
641             criteria.addGreaterOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
642         } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
643             criteria.addLessOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
644         } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
645             criteria.addGreaterThan(propertyName, ObjectUtils.clean(propertyValue));
646         } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
647             criteria.addLessThan(propertyName, ObjectUtils.clean(propertyValue));
648         } else {
649         	criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
650         }
651     }
652 
653 	public void setDateTimeService(DateTimeService dateTimeService) {
654 		this.dateTimeService = dateTimeService;
655 	}
656 
657     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
658         this.persistenceStructureService = persistenceStructureService;
659     }
660 
661     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
662         this.dataDictionaryService = dataDictionaryService;
663     }
664 }