001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.dao.impl; 017 018import org.apache.commons.beanutils.PropertyUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.core.api.datetime.DateTimeService; 021import org.kuali.rice.core.api.search.SearchOperator; 022import org.kuali.rice.core.api.util.RiceKeyConstants; 023import org.kuali.rice.core.api.util.type.TypeUtils; 024import org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria; 025import org.kuali.rice.core.framework.persistence.jpa.criteria.QueryByCriteria; 026import org.kuali.rice.core.framework.persistence.jpa.metadata.EntityDescriptor; 027import org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor; 028import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager; 029import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion; 030import org.kuali.rice.kns.service.KNSServiceLocator; 031import org.kuali.rice.krad.bo.InactivatableFromTo; 032import org.kuali.rice.krad.bo.PersistableBusinessObject; 033import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension; 034import org.kuali.rice.krad.dao.LookupDao; 035import org.kuali.rice.krad.lookup.CollectionIncomplete; 036import org.kuali.rice.krad.lookup.LookupUtils; 037import org.kuali.rice.krad.service.DataDictionaryService; 038import org.kuali.rice.krad.service.KRADServiceLocatorInternal; 039import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 040import org.kuali.rice.krad.service.PersistenceStructureService; 041import org.kuali.rice.krad.util.GlobalVariables; 042import org.kuali.rice.krad.util.KRADConstants; 043import org.kuali.rice.krad.util.KRADPropertyConstants; 044import org.kuali.rice.krad.util.ObjectUtils; 045import org.springframework.dao.DataIntegrityViolationException; 046 047import javax.persistence.EntityManager; 048import javax.persistence.PersistenceContext; 049import javax.persistence.PersistenceException; 050import java.lang.reflect.Field; 051import java.math.BigDecimal; 052import java.sql.Timestamp; 053import java.text.ParseException; 054import java.util.ArrayList; 055import java.util.Collection; 056import java.util.Iterator; 057import java.util.List; 058import java.util.Map; 059 060/** 061 * This class is the JPA implementation of the LookupDao interface. 062 */ 063public class LookupDaoJpa implements LookupDao { 064 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupDao.class); 065 066 private DateTimeService dateTimeService; 067 private PersistenceStructureService persistenceStructureService; 068 private DataDictionaryService dataDictionaryService; 069 070 @PersistenceContext 071 private EntityManager entityManager; 072 073 public Long findCountByMap(Object example, Map formProps) { 074 Criteria criteria = new Criteria(example.getClass().getName()); 075 076 // iterate through the parameter map for key values search criteria 077 Iterator propsIter = formProps.keySet().iterator(); 078 while (propsIter.hasNext()) { 079 String propertyName = (String) propsIter.next(); 080 String searchValue = (String) formProps.get(propertyName); 081 082 // if searchValue is empty and the key is not a valid property ignore 083 if (StringUtils.isBlank(searchValue) || !(PropertyUtils.isWriteable(example, propertyName))) { 084 continue; 085 } 086 087 // get property type which is used to determine type of criteria 088 Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService); 089 if (propertyType == null) { 090 continue; 091 } 092 Boolean caseInsensitive = Boolean.TRUE; 093 if (KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined(example.getClass(), propertyName)) { 094 // 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 095 // 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 096 // could be mixed case. Thus, caseInsensitive will be the opposite of forceUppercase. 097 caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase(example.getClass(), propertyName); 098 } 099 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, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, 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}