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