View Javadoc

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