View Javadoc

1   /*
2    * Copyright 2005-2008 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.kns.dao.impl;
17  
18  import java.lang.reflect.Field;
19  import java.math.BigDecimal;
20  import java.sql.Date;
21  import java.sql.Timestamp;
22  import java.text.ParseException;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import javax.persistence.EntityManager;
30  import javax.persistence.PersistenceContext;
31  import javax.persistence.PersistenceException;
32  
33  import org.apache.commons.beanutils.PropertyUtils;
34  import org.apache.commons.lang.StringUtils;
35  import org.apache.ojb.broker.query.QueryFactory;
36  import org.kuali.rice.core.jpa.criteria.Criteria;
37  import org.kuali.rice.core.jpa.criteria.QueryByCriteria;
38  import org.kuali.rice.core.jpa.metadata.EntityDescriptor;
39  import org.kuali.rice.core.jpa.metadata.FieldDescriptor;
40  import org.kuali.rice.core.jpa.metadata.MetadataManager;
41  import org.kuali.rice.kns.bo.InactivateableFromTo;
42  import org.kuali.rice.kns.bo.PersistableBusinessObject;
43  import org.kuali.rice.kns.bo.PersistableBusinessObjectExtension;
44  import org.kuali.rice.kns.dao.LookupDao;
45  import org.kuali.rice.kns.lookup.CollectionIncomplete;
46  import org.kuali.rice.kns.lookup.LookupUtils;
47  import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
48  import org.kuali.rice.kns.service.DateTimeService;
49  import org.kuali.rice.kns.service.KNSServiceLocator;
50  import org.kuali.rice.kns.service.PersistenceStructureService;
51  import org.kuali.rice.kns.util.GlobalVariables;
52  import org.kuali.rice.kns.util.KNSConstants;
53  import org.kuali.rice.kns.util.KNSPropertyConstants;
54  import org.kuali.rice.kns.util.ObjectUtils;
55  import org.kuali.rice.kns.util.OjbCharBooleanConversion;
56  import org.kuali.rice.kns.util.RiceKeyConstants;
57  import org.kuali.rice.kns.util.TypeUtils;
58  import org.springframework.dao.DataIntegrityViolationException;
59  
60  /**
61   * This class is the OJB implementation of the LookupDao interface.
62   */
63  public class LookupDaoJpa implements LookupDao {
64  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupDao.class);
65  
66  	private DateTimeService dateTimeService;
67  	private PersistenceStructureService persistenceStructureService;
68  	private BusinessObjectDictionaryService businessObjectDictionaryService;
69  
70  	@PersistenceContext
71  	private EntityManager entityManager;
72  
73      public Long findCountByMap(Object example, Map formProps) {
74  		Criteria criteria = new Criteria(example.getClass().getName());
75  
76  		// iterate through the parameter map for key values search criteria
77  		Iterator propsIter = formProps.keySet().iterator();
78  		while (propsIter.hasNext()) {
79  			String propertyName = (String) propsIter.next();
80  			String searchValue = (String) formProps.get(propertyName);
81  
82  			// if searchValue is empty and the key is not a valid property ignore
83  			if (StringUtils.isBlank(searchValue) || !(PropertyUtils.isWriteable(example, propertyName))) {
84  				continue;
85  			}
86  
87  			// get property type which is used to determine type of criteria
88  			Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
89  			if (propertyType == null) {
90  				continue;
91  			}
92  			Boolean caseInsensitive = Boolean.TRUE;
93  			if (KNSServiceLocator.getDataDictionaryService().isAttributeDefined(example.getClass(), propertyName)) {
94  				caseInsensitive = !KNSServiceLocator.getDataDictionaryService().getAttributeForceUppercase(example.getClass(), propertyName);
95  			}
96  			if (caseInsensitive == null) {
97  				caseInsensitive = Boolean.TRUE;
98  			}
99  
100 			boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.
101 					getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName); 
102 			// build criteria
103 			addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria);
104 		}
105 
106 		// execute query and return result list
107 		return (Long) new org.kuali.rice.core.jpa.criteria.QueryByCriteria(entityManager, criteria).toCountQuery().getSingleResult();
108 	}
109 
110 	public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded, boolean usePrimaryKeyValuesOnly) {
111 		return findCollectionBySearchHelper(businessObjectClass, formProps, unbounded, usePrimaryKeyValuesOnly, null);
112 	}
113 
114 	public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded, boolean usePrimaryKeyValuesOnly, Object additionalCriteria) {
115 		PersistableBusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
116 		if (usePrimaryKeyValuesOnly) {
117 			return executeSearch(businessObjectClass, getCollectionCriteriaFromMapUsingPrimaryKeysOnly(businessObjectClass, formProps), unbounded);
118 		} else {
119 			Criteria crit = getCollectionCriteriaFromMap(businessObject, formProps);
120 			if (additionalCriteria != null && additionalCriteria instanceof Criteria) {
121 				crit.and((Criteria) additionalCriteria);
122 			}
123 			return executeSearch(businessObjectClass, crit, unbounded);
124 		}
125 	}
126 
127 	public Criteria getCollectionCriteriaFromMap(PersistableBusinessObject example, Map formProps) {
128 		Criteria criteria = new Criteria(example.getClass().getName());
129 		Iterator propsIter = formProps.keySet().iterator();
130 		while (propsIter.hasNext()) {
131 			String propertyName = (String) propsIter.next();
132 			Boolean caseInsensitive = Boolean.TRUE;
133 			if (KNSServiceLocator.getDataDictionaryService().isAttributeDefined(example.getClass(), propertyName)) {
134 				caseInsensitive = !KNSServiceLocator.getDataDictionaryService().getAttributeForceUppercase(example.getClass(), propertyName);
135 			}
136 			if (caseInsensitive == null) {
137 				caseInsensitive = Boolean.TRUE;
138 			}
139             boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.
140     				getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
141 			if (formProps.get(propertyName) instanceof Collection) {
142 				Iterator iter = ((Collection) formProps.get(propertyName)).iterator();
143 				while (iter.hasNext()) {
144 					if (!createCriteria(example, (String) iter.next(), propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) {
145 						throw new RuntimeException("Invalid value in Collection");
146 					}
147 				}
148 			} else {
149 				if (!createCriteria(example, (String) formProps.get(propertyName), propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) {
150 					continue;
151 				}
152 			}
153 		}
154 		return criteria;
155 	}
156 
157 	public Criteria getCollectionCriteriaFromMapUsingPrimaryKeysOnly(Class businessObjectClass, Map formProps) {
158 		PersistableBusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
159 		Criteria criteria = new Criteria(businessObjectClass.getName());
160 		List pkFields = persistenceStructureService.listPrimaryKeyFieldNames(businessObjectClass);
161 		Iterator pkIter = pkFields.iterator();
162 		while (pkIter.hasNext()) {
163 			String pkFieldName = (String) pkIter.next();
164 			String pkValue = (String) formProps.get(pkFieldName);
165 
166 			if (StringUtils.isBlank(pkValue)) {
167 				throw new RuntimeException("Missing pk value for field " + pkFieldName + " when a search based on PK values only is performed.");
168 			} else if (StringUtils.indexOfAny(pkValue, KNSConstants.QUERY_CHARACTERS) != -1) {
169 				throw new RuntimeException("Value \"" + pkValue + "\" for PK field " + pkFieldName + " contains wildcard/operator characters.");
170 			}
171             boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.
172     				getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, pkFieldName);
173 			createCriteria(businessObject, pkValue, pkFieldName, false, treatWildcardsAndOperatorsAsLiteral, criteria);
174 		}
175 		return criteria;
176 	}
177 
178 	private PersistableBusinessObject checkBusinessObjectClass(Class businessObjectClass) {
179 		if (businessObjectClass == null) {
180 			throw new IllegalArgumentException("BusinessObject class passed to LookupDao findCollectionBySearchHelper... method was null");
181 		}
182 		PersistableBusinessObject businessObject = null;
183 		try {
184 			businessObject = (PersistableBusinessObject) businessObjectClass.newInstance();
185 		} catch (IllegalAccessException e) {
186 			throw new RuntimeException("LookupDao could not get instance of " + businessObjectClass.getName(), e);
187 		} catch (InstantiationException e) {
188 			throw new RuntimeException("LookupDao could not get instance of " + businessObjectClass.getName(), e);
189 		}
190 		return businessObject;
191 	}
192 
193 	private Collection executeSearch(Class businessObjectClass, Criteria criteria, boolean unbounded) {
194 		Collection<PersistableBusinessObject> searchResults = new ArrayList<PersistableBusinessObject>();
195 		Long matchingResultsCount = null;
196 		try {
197 			Integer searchResultsLimit = LookupUtils.getSearchResultsLimit(businessObjectClass);
198 			if (!unbounded && (searchResultsLimit != null)) {
199 				matchingResultsCount = (Long) new org.kuali.rice.core.jpa.criteria.QueryByCriteria(entityManager, criteria).toCountQuery().getSingleResult();
200 				searchResults = new org.kuali.rice.core.jpa.criteria.QueryByCriteria(entityManager, criteria).toQuery().setMaxResults(searchResultsLimit).getResultList();
201 			} else {
202 				searchResults = new org.kuali.rice.core.jpa.criteria.QueryByCriteria(entityManager, criteria).toQuery().getResultList();
203 			}
204 			if ((matchingResultsCount == null) || (matchingResultsCount.intValue() <= searchResultsLimit.intValue())) {
205 				matchingResultsCount = new Long(0);
206 			}
207 			// Temp solution for loading extension objects - need to find a
208 			// better way
209 			// Should look for a JOIN query, for the above query, that will grab
210 			// the PBOEs as well (1+n query problem)
211 			for (PersistableBusinessObject bo : searchResults) {
212 				if (bo.getExtension() != null) {
213 					PersistableBusinessObjectExtension boe = bo.getExtension();
214 					EntityDescriptor entity = MetadataManager.getEntityDescriptor(bo.getExtension().getClass());
215 					Criteria extensionCriteria = new Criteria(boe.getClass().getName());
216 					for (FieldDescriptor fieldDescriptor : entity.getPrimaryKeys()) {
217 						try {
218 							Field field = bo.getClass().getDeclaredField(fieldDescriptor.getName());
219 							field.setAccessible(true);
220 							extensionCriteria.eq(fieldDescriptor.getName(), field.get(bo));
221 						} catch (Exception e) {
222 							LOG.error(e.getMessage(), e);
223 						}
224 					}
225 					try {
226 						boe = (PersistableBusinessObjectExtension) new org.kuali.rice.core.jpa.criteria.QueryByCriteria(entityManager, extensionCriteria).toQuery().getSingleResult();
227 					} catch (PersistenceException e) {}
228 					bo.setExtension(boe);
229 				}
230 			}
231 			// populate Person objects in business objects
232 			List bos = new ArrayList();
233 			bos.addAll(searchResults);
234 			searchResults = bos;
235 		} catch (DataIntegrityViolationException e) {
236 			throw new RuntimeException("LookupDao encountered exception during executeSearch", e);
237 		}
238 		return new CollectionIncomplete(searchResults, matchingResultsCount);
239 	}
240 
241 	/**
242 	 * Return whether or not an attribute is writeable. This method is aware
243 	 * that that Collections may be involved and handles them consistently with
244 	 * the way in which OJB handles specifying the attributes of elements of a
245 	 * Collection.
246 	 * 
247 	 * @param o
248 	 * @param p
249 	 * @return
250 	 * @throws IllegalArgumentException
251 	 */
252 	private boolean isWriteable(Object o, String p) throws IllegalArgumentException {
253 		if (null == o || null == p) {
254 			throw new IllegalArgumentException("Cannot check writeable status with null arguments.");
255 		}
256 
257 		boolean b = false;
258 
259 		// Try the easy way.
260 		if (!(PropertyUtils.isWriteable(o, p))) {
261 
262 			// If that fails lets try to be a bit smarter, understanding that
263 			// Collections may be involved.
264 			if (-1 != p.indexOf('.')) {
265 
266 				String[] parts = p.split("\\.");
267 
268 				// Get the type of the attribute.
269 				Class c = ObjectUtils.getPropertyType(o, parts[0], persistenceStructureService);
270 
271 				Object i = null;
272 
273 				// If the next level is a Collection, look into the collection,
274 				// to find out what type its elements are.
275 				if (Collection.class.isAssignableFrom(c)) {
276 					Map<String, Class> m = persistenceStructureService.listCollectionObjectTypes(o.getClass());
277 					c = m.get(parts[0]);
278 				}
279 
280 				// Look into the attribute class to see if it is writeable.
281 				try {
282 					i = c.newInstance();
283 					StringBuffer sb = new StringBuffer();
284 					for (int x = 1; x < parts.length; x++) {
285 						sb.append(1 == x ? "" : ".").append(parts[x]);
286 					}
287 					b = isWriteable(i, sb.toString());
288 				} catch (InstantiationException ie) {
289 					LOG.info(ie);
290 				} catch (IllegalAccessException iae) {
291 					LOG.info(iae);
292 				}
293 			}
294 		} else {
295 			b = true;
296 		}
297 
298 		return b;
299 	}
300 
301 	public boolean createCriteria(Object example, String searchValue, String propertyName, Object criteria) {
302 		return createCriteria(example, searchValue, propertyName, false, false, criteria);
303 	}
304 	
305     public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria) {
306     	return createCriteria( example, searchValue, propertyName, false, false, criteria, null );
307     }
308 
309 	public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria, Map searchValues) {
310 		// if searchValue is empty and the key is not a valid property ignore
311 		if (!(criteria instanceof Criteria) || StringUtils.isBlank(searchValue) || !isWriteable(example, propertyName)) {
312 			return false;
313 		}
314 
315 		// get property type which is used to determine type of criteria
316 		Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
317 		if (propertyType == null) {
318 			return false;
319 		}
320 
321 		// build criteria
322 		if (example instanceof InactivateableFromTo) {
323 			if (KNSPropertyConstants.ACTIVE.equals(propertyName)) {
324 				addInactivateableFromToActiveCriteria(example, searchValue, (Criteria) criteria, searchValues);
325 			} else if (KNSPropertyConstants.CURRENT.equals(propertyName)) {
326 				addInactivateableFromToCurrentCriteria(example, searchValue, (Criteria) criteria, searchValues);
327 			} else if (!KNSPropertyConstants.ACTIVE_AS_OF_DATE.equals(propertyName)) {
328 				addCriteria(propertyName, searchValue, propertyType, caseInsensitive,
329 						treatWildcardsAndOperatorsAsLiteral, (Criteria) criteria);
330 			}
331 		} else {
332 			addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral,
333 					(Criteria) criteria);
334 		}
335 		
336 		return true;
337 	}
338 
339 	/**
340 	 * @see org.kuali.rice.kns.dao.LookupDao#findObjectByMap(java.lang.Object,
341 	 *      java.util.Map)
342 	 */
343 	public Object findObjectByMap(Object example, Map formProps) {
344 		Criteria jpaCriteria = new Criteria(example.getClass().getName());
345 
346 		Iterator propsIter = formProps.keySet().iterator();
347 		while (propsIter.hasNext()) {
348 			String propertyName = (String) propsIter.next();
349 			String searchValue = "";
350 			if (formProps.get(propertyName) != null) {
351 				searchValue = (formProps.get(propertyName)).toString();
352 			}
353 
354 			if (StringUtils.isNotBlank(searchValue) & PropertyUtils.isWriteable(example, propertyName)) {
355 				Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
356 				if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) {
357 					if (propertyType.equals(Long.class)) {
358 						jpaCriteria.eq(propertyName, new Long(searchValue));
359 					} else {
360 						jpaCriteria.eq(propertyName, new Integer(searchValue));
361 					}
362 				} else if (TypeUtils.isTemporalClass(propertyType)) {
363 					jpaCriteria.eq(propertyName, parseDate(ObjectUtils.clean(searchValue)));
364 				} else {
365 					jpaCriteria.eq(propertyName, searchValue);
366 				}
367 			}
368 		}
369 
370 		return new QueryByCriteria(entityManager, jpaCriteria).toQuery().getSingleResult();
371 	}
372 
373 	private void addCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
374 		if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, KNSConstants.OR_LOGICAL_OPERATOR)) {
375 			addOrCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
376 			return;
377 		}
378 
379 		if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, KNSConstants.AND_LOGICAL_OPERATOR)) {
380 			addAndCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
381 			return;
382 		}
383 
384         if (StringUtils.containsIgnoreCase(propertyValue, KNSConstants.NULL_OPERATOR)) {
385         	if (StringUtils.contains(propertyValue, KNSConstants.NOT_LOGICAL_OPERATOR)) {
386         		criteria.notNull(propertyName);
387         	}
388         	else {
389         		criteria.isNull(propertyName);
390         	}
391         }
392         else if (TypeUtils.isStringClass(propertyType)) {
393 			// KULRICE-85 : made string searches case insensitive - used new
394 			// DBPlatform function to force strings to upper case
395 			if (caseInsensitive) {
396 				// TODO: What to do here now that the JPA version does not extend platform aware?
397 				//propertyName = getDbPlatform().getUpperCaseFunction() + "(__JPA_ALIAS__." + propertyName + ")";
398 				propertyName = "UPPER(__JPA_ALIAS__." + propertyName + ")";
399 				propertyValue = propertyValue.toUpperCase();
400 			}
401 			if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue,
402 					KNSConstants.NOT_LOGICAL_OPERATOR)) {
403 				addNotCriteria(propertyName, propertyValue, propertyType,
404 						caseInsensitive, criteria);
405             } else if (
406             		!treatWildcardsAndOperatorsAsLiteral && propertyValue != null && (
407             				StringUtils.contains(propertyValue, KNSConstants.BETWEEN_OPERATOR) 
408             				|| propertyValue.startsWith(">")
409             				|| propertyValue.startsWith("<") ) ) {
410 				addStringRangeCriteria(propertyName, propertyValue, criteria);
411 			} else {
412 				if (treatWildcardsAndOperatorsAsLiteral) {
413 					propertyValue = StringUtils.replace(propertyValue, "*", "\\*");
414 				}
415 				criteria.like(propertyName, propertyValue);
416 			}
417 		} else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) {
418 			addNumericRangeCriteria(propertyName, propertyValue, criteria);
419 		} else if (TypeUtils.isTemporalClass(propertyType)) {
420 			addDateRangeCriteria(propertyName, propertyValue, criteria);
421 		} else if (TypeUtils.isBooleanClass(propertyType)) {
422 			String temp = ObjectUtils.clean(propertyValue);
423 			criteria.eq(propertyName, ("Y".equalsIgnoreCase(temp) || "T".equalsIgnoreCase(temp) || "1".equalsIgnoreCase(temp) || "true".equalsIgnoreCase(temp)) ? true : false);
424 		} else {
425 			LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue);
426 		}
427 	}
428 	
429     
430     /**
431      * Translates criteria for active status to criteria on the active from and to fields
432      * 
433      * @param example - business object being queried on
434      * @param activeSearchValue - value for the active search field, should convert to boolean
435      * @param criteria - Criteria object being built
436      * @param searchValues - Map containing all search keys and values
437      */
438     protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue, Criteria criteria, Map searchValues) {
439     	Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
440 		
441     	String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue);
442     	if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
443     		// (active from date <= date or active from date is null) and (date < active to date or active to date is null)
444     		Criteria criteriaBeginDate = new Criteria(example.getClass().getName());
445     		criteriaBeginDate.lte(KNSPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
446     		
447     		Criteria criteriaBeginDateNull = new Criteria(example.getClass().getName());
448     		criteriaBeginDateNull.isNull(KNSPropertyConstants.ACTIVE_FROM_DATE);
449     		criteriaBeginDate.or(criteriaBeginDateNull);
450     		
451     		criteria.and(criteriaBeginDate);
452     		
453     		Criteria criteriaEndDate = new Criteria(example.getClass().getName());
454     		criteriaEndDate.gt(KNSPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
455     	
456     		Criteria criteriaEndDateNull = new Criteria(example.getClass().getName());
457     		criteriaEndDateNull.isNull(KNSPropertyConstants.ACTIVE_TO_DATE);
458     		criteriaEndDate.or(criteriaEndDateNull);
459     		
460     		criteria.and(criteriaEndDate);
461     	}
462     	else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
463     		// (date < active from date) or (active from date is null) or (date >= active to date) 
464     		Criteria criteriaNonActive = new Criteria(example.getClass().getName());
465     		criteriaNonActive.gt(KNSPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
466     		
467     		Criteria criteriaBeginDateNull = new Criteria(example.getClass().getName());
468     		criteriaBeginDateNull.isNull(KNSPropertyConstants.ACTIVE_FROM_DATE);
469     		criteriaNonActive.or(criteriaBeginDateNull);
470     		
471     		Criteria criteriaEndDate = new Criteria(example.getClass().getName());
472     		criteriaEndDate.lte(KNSPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
473     		criteriaNonActive.or(criteriaEndDate);
474     		
475     		criteria.and(criteriaNonActive);
476     	}
477     }
478     
479     /**
480      * Translates criteria for current status to a sub-query on active begin date
481      * 
482      * @param example - business object being queried on
483      * @param currentSearchValue - value for the current search field, should convert to boolean
484      * @param criteria - Criteria object being built
485      */
486 	protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue, Criteria criteria, Map searchValues) {
487 		Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
488 		
489 		List<String> groupByFieldList = businessObjectDictionaryService.getGroupByAttributesForEffectiveDating(example
490 				.getClass());
491 		if (groupByFieldList == null) {
492 			return;
493 		}
494 
495 		String alias = "c";
496 
497 		String jpql = " (select max(" + alias + "." + KNSPropertyConstants.ACTIVE_FROM_DATE + ") from "
498 				+ example.getClass().getName() + " as " + alias + " where ";
499 		String activeDateDBStr = KNSServiceLocator.getDatabasePlatform().getDateSQL(dateTimeService.toDateTimeString(activeTimestamp), null);
500 		jpql += alias + "." + KNSPropertyConstants.ACTIVE_FROM_DATE + " <= '" + activeDateDBStr + "'";
501 
502 		// join back to main query with the group by fields
503 		boolean firstGroupBy = true;
504 		String groupByJpql = "";
505 		for (String groupByField : groupByFieldList) {
506 			if (!firstGroupBy) {
507 				groupByJpql += ", ";
508 			}
509 
510 			jpql += " AND " + alias + "." + groupByField + " = " + criteria.getAlias() + "." + groupByField + " ";
511 			groupByJpql += alias + "." + groupByField;
512 			firstGroupBy = false;
513 		}
514 
515 		jpql += " group by " + groupByJpql + " )";
516 
517 		String currentBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(currentSearchValue);
518 		if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
519 			jpql = criteria.getAlias() + "." + KNSPropertyConstants.ACTIVE_FROM_DATE + " in " + jpql;
520 		} else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
521 			jpql = criteria.getAlias() + "." + KNSPropertyConstants.ACTIVE_FROM_DATE + " not in " + jpql;
522 		}
523 
524 		criteria.rawJpql(jpql);
525 	}
526 
527 	private void addOrCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
528 		addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, KNSConstants.OR_LOGICAL_OPERATOR);
529 	}
530 
531 	private void addAndCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
532 		addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, KNSConstants.AND_LOGICAL_OPERATOR);
533 	}
534 
535 	private void addNotCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
536 		String[] splitPropVal = StringUtils.split(propertyValue, KNSConstants.NOT_LOGICAL_OPERATOR);
537 
538 		int strLength = splitPropVal.length;
539 		// if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b)
540 		if (strLength > 1) {
541 			String expandedNot = "!" + StringUtils.join(splitPropVal, KNSConstants.AND_LOGICAL_OPERATOR + KNSConstants.NOT_LOGICAL_OPERATOR);
542 			// we know that since this method is called, treatWildcardsAndOperatorsAsLiteral is false
543 			addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria);
544 		} else {
545 			// only one so add a not like
546 			criteria.notLike(propertyName, splitPropVal[0]);
547 		}
548 	}
549 
550 	private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria, String splitValue) {
551 		String[] splitPropVal = StringUtils.split(propertyValue, splitValue);
552 
553 		Criteria subCriteria = new Criteria("N/A");
554 		for (int i = 0; i < splitPropVal.length; i++) {
555 			Criteria predicate = new Criteria("N/A");
556 			// we know that since this method is called, treatWildcardsAndOperatorsAsLiteral is false
557 			addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate);
558 			if (splitValue == KNSConstants.OR_LOGICAL_OPERATOR) {
559 				subCriteria.or(predicate);
560 			}
561 			if (splitValue == KNSConstants.AND_LOGICAL_OPERATOR) {
562 				subCriteria.and(predicate);
563 			}
564 		}
565 
566 		criteria.and(subCriteria);
567 	}
568 
569 	private java.sql.Date parseDate(String dateString) {
570 		dateString = dateString.trim();
571 		try {
572 			return dateTimeService.convertToSqlDate(dateString);
573 		} catch (ParseException ex) {
574 			return null;
575 		}
576 	}
577 
578 	private void addDateRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
579 
580 		if (StringUtils.contains(propertyValue, KNSConstants.BETWEEN_OPERATOR)) {
581 			String[] rangeValues = StringUtils.split(propertyValue, KNSConstants.BETWEEN_OPERATOR);
582 			criteria.between(propertyName, parseDate(ObjectUtils.clean(rangeValues[0])), parseDate(ObjectUtils.clean(rangeValues[1])));
583 		} else if (propertyValue.startsWith(">=")) {
584 			criteria.gte(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
585 		} else if (propertyValue.startsWith("<=")) {
586 			criteria.lte(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
587 		} else if (propertyValue.startsWith(">")) {
588 			criteria.gt(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
589 		} else if (propertyValue.startsWith("<")) {
590 			criteria.lt(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
591 		} else {
592 			criteria.eq(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
593 		}
594 	}
595 
596 	private BigDecimal cleanNumeric(String value) {
597 		String cleanedValue = value.replaceAll("[^-0-9.]", "");
598 		// ensure only one "minus" at the beginning, if any
599 		if (cleanedValue.lastIndexOf('-') > 0) {
600 			if (cleanedValue.charAt(0) == '-') {
601 				cleanedValue = "-" + cleanedValue.replaceAll("-", "");
602 			} else {
603 				cleanedValue = cleanedValue.replaceAll("-", "");
604 			}
605 		}
606 		// ensure only one decimal in the string
607 		int decimalLoc = cleanedValue.lastIndexOf('.');
608 		if (cleanedValue.indexOf('.') != decimalLoc) {
609 			cleanedValue = cleanedValue.substring(0, decimalLoc).replaceAll("\\.", "") + cleanedValue.substring(decimalLoc);
610 		}
611 		try {
612 			return new BigDecimal(cleanedValue);
613 		} catch (NumberFormatException ex) {
614 			GlobalVariables.getMessageMap().putError(KNSConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM, new String[] { "Invalid Numeric Input: " + value });
615 			return null;
616 		}
617 	}
618 
619 	private void addNumericRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
620 
621 		if (StringUtils.contains(propertyValue, KNSConstants.BETWEEN_OPERATOR)) {
622 			String[] rangeValues = StringUtils.split(propertyValue, KNSConstants.BETWEEN_OPERATOR);
623 			criteria.between(propertyName, cleanNumeric(rangeValues[0]), cleanNumeric(rangeValues[1]));
624 		} else if (propertyValue.startsWith(">=")) {
625 			criteria.gte(propertyName, cleanNumeric(propertyValue));
626 		} else if (propertyValue.startsWith("<=")) {
627 			criteria.lte(propertyName, cleanNumeric(propertyValue));
628 		} else if (propertyValue.startsWith(">")) {
629 			criteria.gt(propertyName, cleanNumeric(propertyValue));
630 		} else if (propertyValue.startsWith("<")) {
631 			criteria.lt(propertyName, cleanNumeric(propertyValue));
632 		} else {
633 			criteria.eq(propertyName, cleanNumeric(propertyValue));
634 		}
635 	}
636 
637 	private void addStringRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
638 
639 		if (StringUtils.contains(propertyValue, KNSConstants.BETWEEN_OPERATOR)) {
640 			String[] rangeValues = StringUtils.split(propertyValue, KNSConstants.BETWEEN_OPERATOR);
641 			criteria.between(propertyName, rangeValues[0], rangeValues[1]);
642 		} else if (propertyValue.startsWith(">=")) {
643 			criteria.gte(propertyName, ObjectUtils.clean(propertyValue));
644 		} else if (propertyValue.startsWith("<=")) {
645 			criteria.lte(propertyName, ObjectUtils.clean(propertyValue));
646 		} else if (propertyValue.startsWith(">")) {
647 			criteria.gt(propertyName, ObjectUtils.clean(propertyValue));
648 		} else if (propertyValue.startsWith("<")) {
649 			criteria.lt(propertyName, ObjectUtils.clean(propertyValue));
650 		}
651 	}
652 
653 	/**
654 	 * @param dateTimeService
655 	 *            the dateTimeService to set
656 	 */
657 	public void setDateTimeService(DateTimeService dateTimeService) {
658 		this.dateTimeService = dateTimeService;
659 	}
660 
661     /**
662      * @return the entityManager
663      */
664     public EntityManager getEntityManager() {
665         return this.entityManager;
666     }
667 
668     /**
669      * @param entityManager the entityManager to set
670      */
671     public void setEntityManager(EntityManager entityManager) {
672         this.entityManager = entityManager;
673     }
674     
675 	public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
676 		this.persistenceStructureService = persistenceStructureService;
677 	}
678 
679 	public void setBusinessObjectDictionaryService(BusinessObjectDictionaryService businessObjectDictionaryService) {
680 		this.businessObjectDictionaryService = businessObjectDictionaryService;
681 	}
682 	
683 }