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