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