001/** 002 * Copyright 2005-2015 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.apache.ojb.broker.query.Criteria; 021import org.apache.ojb.broker.query.Query; 022import org.apache.ojb.broker.query.QueryByCriteria; 023import org.apache.ojb.broker.query.QueryFactory; 024import org.kuali.rice.core.api.datetime.DateTimeService; 025import org.kuali.rice.core.api.search.SearchOperator; 026import org.kuali.rice.core.api.util.RiceKeyConstants; 027import org.kuali.rice.core.api.util.type.TypeUtils; 028import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion; 029import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb; 030import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform; 031import org.kuali.rice.kns.service.KNSServiceLocator; 032import org.kuali.rice.krad.bo.BusinessObject; 033import org.kuali.rice.krad.bo.InactivatableFromTo; 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.KRADServiceLocatorWeb; 039import org.kuali.rice.krad.service.PersistenceStructureService; 040import org.kuali.rice.krad.util.GlobalVariables; 041import org.kuali.rice.krad.util.KRADConstants; 042import org.kuali.rice.krad.util.KRADPropertyConstants; 043import org.kuali.rice.krad.util.ObjectUtils; 044import org.springframework.dao.DataIntegrityViolationException; 045import org.springmodules.orm.ojb.OjbOperationException; 046 047import java.math.BigDecimal; 048import java.sql.Timestamp; 049import java.text.ParseException; 050import java.util.ArrayList; 051import java.util.Collection; 052import java.util.Iterator; 053import java.util.List; 054import java.util.Map; 055 056/** 057 * OJB implementation of the LookupDao interface 058 * 059 * @deprecated use new KRAD Data framework {@link org.kuali.rice.krad.data.DataObjectService} 060 */ 061@Deprecated 062public class LookupDaoOjb extends PlatformAwareDaoBaseOjb implements LookupDao { 063 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupDaoOjb.class); 064 private DateTimeService dateTimeService; 065 private PersistenceStructureService persistenceStructureService; 066 private DataDictionaryService dataDictionaryService; 067 068 /** 069 * Since 2.3 070 * This version of findCollectionBySearchHelper is needed for version compatibility. It allows executeSearch 071 * to behave the same way as it did prior to 2.3. The value for searchResultsLimit will be retrieved from the 072 * KNS version of LookupUtils. 073 * 074 * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean, 075 * boolean) 076 */ 077 public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded, 078 boolean usePrimaryKeyValuesOnly) { 079 Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(businessObjectClass); 080 return findCollectionBySearchHelper(businessObjectClass, formProps, unbounded, usePrimaryKeyValuesOnly, 081 searchResultsLimit); 082 } 083 084 /** 085 * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean, 086 * boolean, Integer) 087 * 088 * If searchResultsLimit is null, the search results will not be limited by any other means. 089 */ 090 public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded, 091 boolean usePrimaryKeyValuesOnly, Integer searchResultsLimit) { 092 BusinessObject businessObject = checkBusinessObjectClass(businessObjectClass); 093 if (usePrimaryKeyValuesOnly) { 094 return executeSearch(businessObjectClass, getCollectionCriteriaFromMapUsingPrimaryKeysOnly( 095 businessObjectClass, formProps), unbounded, searchResultsLimit); 096 } 097 098 Criteria crit = getCollectionCriteriaFromMap(businessObject, formProps); 099 return executeSearch(businessObjectClass, crit, unbounded, searchResultsLimit); 100 } 101 102 /** 103 * Builds up criteria object based on the object and map. 104 */ 105 public Criteria getCollectionCriteriaFromMap(BusinessObject example, Map formProps) { 106 Criteria criteria = new Criteria(); 107 Iterator propsIter = formProps.keySet().iterator(); 108 while (propsIter.hasNext()) { 109 String propertyName = (String) propsIter.next(); 110 Boolean caseInsensitive = Boolean.TRUE; 111 if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) { 112 // 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 113 // 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 114 // could be mixed case. Thus, caseInsensitive will be the opposite of forceUppercase. 115 caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName ); 116 } 117 if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; } 118 boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator 119 .getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName); 120 121 if (formProps.get(propertyName) instanceof Collection) { 122 Iterator iter = ((Collection) formProps.get(propertyName)).iterator(); 123 while (iter.hasNext()) { 124 String searchValue = (String) iter.next(); 125 if (!caseInsensitive) { 126 // Verify that the searchValue is uppercased if caseInsensitive is false 127 searchValue = searchValue.toUpperCase(); 128 } 129 if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps )) { 130 throw new RuntimeException("Invalid value in Collection"); 131 } 132 } 133 } 134 else { 135 String searchValue = (String) formProps.get(propertyName); 136 if (!caseInsensitive) { 137 // Verify that the searchValue is uppercased if caseInsensitive is false 138 searchValue = searchValue.toUpperCase(); 139 } 140 if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) { 141 continue; 142 } 143 } 144 } 145 return criteria; 146 } 147 148 public Criteria getCollectionCriteriaFromMapUsingPrimaryKeysOnly(Class businessObjectClass, Map formProps) { 149 BusinessObject businessObject = checkBusinessObjectClass(businessObjectClass); 150 Criteria criteria = new Criteria(); 151 List pkFields = KRADServiceLocatorWeb.getLegacyDataAdapter().listPrimaryKeyFieldNames(businessObjectClass); 152 Iterator pkIter = pkFields.iterator(); 153 while (pkIter.hasNext()) { 154 String pkFieldName = (String) pkIter.next(); 155 String pkValue = (String) formProps.get(pkFieldName); 156 157 if (StringUtils.isBlank(pkValue)) { 158 throw new RuntimeException("Missing pk value for field " + pkFieldName + " when a search based on PK values only is performed."); 159 } 160 else { 161 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) { 162 if (pkValue.contains(op.op())) { 163 throw new RuntimeException("Value \"" + pkValue + "\" for PK field " + pkFieldName + " contains wildcard/operator characters."); 164 } 165 } 166 } 167 boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator. 168 getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, pkFieldName); 169 createCriteria(businessObject, pkValue, pkFieldName, false, treatWildcardsAndOperatorsAsLiteral, criteria); 170 } 171 return criteria; 172 } 173 174 private BusinessObject checkBusinessObjectClass(Class businessObjectClass) { 175 if (businessObjectClass == null) { 176 throw new IllegalArgumentException("BusinessObject class passed to LookupDaoOjb findCollectionBySearchHelper... method was null"); 177 } 178 BusinessObject businessObject = null; 179 try { 180 businessObject = (BusinessObject) businessObjectClass.newInstance(); 181 } 182 catch (IllegalAccessException e) { 183 throw new RuntimeException("LookupDaoOjb could not get instance of " + businessObjectClass.getName(), e); 184 } 185 catch (InstantiationException e) { 186 throw new RuntimeException("LookupDaoOjb could not get instance of " + businessObjectClass.getName(), e); 187 } 188 return businessObject; 189 } 190 191 private Collection executeSearch(Class businessObjectClass, Criteria criteria, boolean unbounded, Integer searchResultsLimit) { 192 Collection searchResults = new ArrayList(); 193 Long matchingResultsCount = null; 194 try { 195 if (!unbounded && searchResultsLimit == null) { 196 searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(businessObjectClass); 197 } 198 // A negative number in searchResultsLimit means the search results should be unlimited. 199 if (!unbounded && (searchResultsLimit != null) && searchResultsLimit >= 0) { 200 matchingResultsCount = new Long(getPersistenceBrokerTemplate().getCount(QueryFactory.newQuery(businessObjectClass, criteria))); 201 applySearchResultsLimit(businessObjectClass, criteria, getDbPlatform(), searchResultsLimit); 202 } 203 if ((matchingResultsCount == null) || (matchingResultsCount.intValue() <= searchResultsLimit.intValue())) { 204 matchingResultsCount = new Long(0); 205 } 206 searchResults = getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(businessObjectClass, criteria)); 207 // populate Person objects in business objects 208 List bos = new ArrayList(); 209 bos.addAll(searchResults); 210 searchResults = bos; 211 212 213 } 214 catch (OjbOperationException e) { 215 throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e); 216 } 217 catch (DataIntegrityViolationException e) { 218 throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e); 219 } 220 return new CollectionIncomplete(searchResults, matchingResultsCount); 221 } 222 223 224 /** 225 * This method applies the search results limit to the search criteria for this BO 226 * 227 * @param businessObjectClass BO class to search on / get limit for 228 * @param criteria search criteria 229 * @param platform database platform 230 * @param limit limit to use. If limit is null, getSearchResultsLimit will be called using the businessObjectClass 231 * to see if a limit can be found for this particular businessObjectClass. 232 */ 233 protected void applySearchResultsLimit(Class businessObjectClass, Criteria criteria, DatabasePlatform platform, 234 Integer limit) { 235 String limitSql = null; 236 if (limit != null) { 237 limitSql = platform.applyLimitSql(limit); 238 } else { 239 limit = LookupUtils.getSearchResultsLimit(businessObjectClass, null); 240 if (limit != null) { 241 limitSql = platform.applyLimitSql(limit); 242 } 243 } 244 if (StringUtils.isNotBlank(limitSql)) { 245 criteria.addSql(limitSql); 246 } 247 } 248 249 public boolean createCriteria(Object example, String searchValue, String propertyName, Object criteria) { 250 return createCriteria( example, searchValue, propertyName, false, false, criteria ); 251 } 252 253 public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria) { 254 return createCriteria( example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, null ); 255 } 256 257 public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria, Map searchValues) { 258 // if searchValue is empty and the key is not a valid property ignore 259 if (!(criteria instanceof Criteria) || StringUtils.isBlank(searchValue) || !ObjectUtils.isWriteable(example, propertyName, persistenceStructureService)) { 260 return false; 261 } 262 263 // get property type which is used to determine type of criteria 264 Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService); 265 if (propertyType == null) { 266 return false; 267 } 268 269 // build criteria 270 if (example instanceof InactivatableFromTo) { 271 if (KRADPropertyConstants.ACTIVE.equals(propertyName)) { 272 addInactivateableFromToActiveCriteria(example, searchValue, (Criteria) criteria, searchValues); 273 } else if (KRADPropertyConstants.CURRENT.equals(propertyName)) { 274 addInactivateableFromToCurrentCriteria(example, searchValue, (Criteria) criteria, searchValues); 275 } else if (!KRADPropertyConstants.ACTIVE_AS_OF_DATE.equals(propertyName)) { 276 addCriteria(propertyName, searchValue, propertyType, caseInsensitive, 277 treatWildcardsAndOperatorsAsLiteral, (Criteria) criteria); 278 } 279 } else { 280 addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, 281 (Criteria) criteria); 282 } 283 284 return true; 285 } 286 287 /** 288 * Find count of records meeting criteria based on the object and map. 289 */ 290 public Long findCountByMap(Object example, Map formProps) { 291 Criteria criteria = new Criteria(); 292 // iterate through the parameter map for key values search criteria 293 Iterator propsIter = formProps.keySet().iterator(); 294 while (propsIter.hasNext()) { 295 String propertyName = (String) propsIter.next(); 296 String searchValue = (String) formProps.get(propertyName); 297 298 // if searchValue is empty and the key is not a valid property ignore 299 if (StringUtils.isBlank(searchValue) || !(PropertyUtils.isWriteable(example, propertyName))) { 300 continue; 301 } 302 303 // get property type which is used to determine type of criteria 304 Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService); 305 if (propertyType == null) { 306 continue; 307 } 308 Boolean caseInsensitive = Boolean.TRUE; 309 if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) { 310 caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName ); 311 } 312 if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; } 313 314 boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator 315 .getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName); 316 317 if (!caseInsensitive) { 318 // Verify that the searchValue is uppercased if caseInsensitive is false 319 searchValue = searchValue.toUpperCase(); 320 } 321 322 // build criteria 323 addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria); 324 } 325 326 // execute query and return result list 327 Query query = QueryFactory.newQuery(example.getClass(), criteria); 328 329 return new Long(getPersistenceBrokerTemplate().getCount(query)); 330 } 331 332 /** 333 * @see org.kuali.rice.krad.dao.LookupDao#findObjectByMap(java.lang.Object, java.util.Map) 334 */ 335 @Override 336 public <T extends Object> T findObjectByMap(Class<T> type, Map<String, String> formProps) { 337 T example = null; 338 try { 339 example = type.newInstance(); 340 } catch (IllegalAccessException e) { 341 throw new RuntimeException("Cannot get new instance of " + type.getName(), e); 342 } catch (InstantiationException e) { 343 throw new RuntimeException("Cannot instantiate " + type.getName(), e); 344 } 345 346 if ( KRADServiceLocatorWeb.getLegacyDataAdapter().isPersistable(example.getClass())) { 347 Criteria criteria = new Criteria(); 348 349 // iterate through the parameter map for search criteria 350 for (Map.Entry<String, String> formProp : formProps.entrySet()) { 351 352 String propertyName = formProp.getKey(); 353 String searchValue = ""; 354 if (formProp.getValue() != null) { 355 searchValue = formProp.getValue(); 356 } 357 358 if (StringUtils.isNotBlank(searchValue) & PropertyUtils.isWriteable(example, propertyName)) { 359 Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService); 360 if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) { 361 criteria.addEqualTo(propertyName, cleanNumeric(searchValue)); 362 } else if (TypeUtils.isTemporalClass(propertyType)) { 363 criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(searchValue) ) ); 364 } else { 365 criteria.addEqualTo(propertyName, searchValue); 366 } 367 } 368 } 369 370 // execute query and return result list 371 Query query = QueryFactory.newQuery(example.getClass(), criteria); 372 return (T)getPersistenceBrokerTemplate().getObjectByQuery(query); 373 } 374 return null; 375 } 376 377 378 /** 379 * Adds to the criteria object based on the property type and any query characters given. 380 */ 381 private void addCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) { 382 if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.OR.op())) { 383 addOrCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria); 384 return; 385 } 386 387 if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.AND.op())) { 388 addAndCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria); 389 return; 390 } 391 392 if (StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NULL.op()) || StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NOT_NULL.op())) { 393 // KULRICE-6846 null Lookup criteria causes sql exception 394 if (StringUtils.contains(propertyValue, SearchOperator.NOT.op())) { 395 criteria.addNotNull(propertyName); 396 } 397 else { 398 criteria.addIsNull(propertyName); 399 } 400 } 401 else if (TypeUtils.isStringClass(propertyType)) { 402 // KULRICE-85 : made string searches case insensitive - used new DBPlatform function to force strings to upper case 403 if ( caseInsensitive ) { 404 propertyName = getDbPlatform().getUpperCaseFunction() + "(" + propertyName + ")"; 405 propertyValue = propertyValue.toUpperCase(); 406 } 407 if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.NOT.op())) { 408 addNotCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria); 409 } else if ( 410 !treatWildcardsAndOperatorsAsLiteral && propertyValue != null && ( 411 StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op()) 412 || propertyValue.startsWith(">") 413 || propertyValue.startsWith("<") ) ) { 414 addStringRangeCriteria(propertyName, propertyValue, criteria); 415 } else { 416 if (treatWildcardsAndOperatorsAsLiteral) { 417 propertyValue = StringUtils.replace(propertyValue, "*", "\\*"); 418 } 419 criteria.addLike(propertyName, propertyValue); 420 } 421 } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) { 422 addNumericRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria); 423 } else if (TypeUtils.isTemporalClass(propertyType)) { 424 addDateRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria); 425 } else if (TypeUtils.isBooleanClass(propertyType)) { 426 criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue)); 427 } else { 428 LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue); 429 } 430 } 431 432 /** 433 * Translates criteria for active status to criteria on the active from and to fields 434 * 435 * @param example - business object being queried on 436 * @param activeSearchValue - value for the active search field, should convert to boolean 437 * @param criteria - Criteria object being built 438 * @param searchValues - Map containing all search keys and values 439 */ 440 protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue, Criteria criteria, Map searchValues) { 441 Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues); 442 443 String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue); 444 if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) { 445 // (active from date <= date or active from date is null) and (date < active to date or active to date is null) 446 Criteria criteriaBeginDate = new Criteria(); 447 criteriaBeginDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp); 448 449 Criteria criteriaBeginDateNull = new Criteria(); 450 criteriaBeginDateNull.addIsNull(KRADPropertyConstants.ACTIVE_FROM_DATE); 451 criteriaBeginDate.addOrCriteria(criteriaBeginDateNull); 452 453 criteria.addAndCriteria(criteriaBeginDate); 454 455 Criteria criteriaEndDate = new Criteria(); 456 criteriaEndDate.addGreaterThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp); 457 458 Criteria criteriaEndDateNull = new Criteria(); 459 criteriaEndDateNull.addIsNull(KRADPropertyConstants.ACTIVE_TO_DATE); 460 criteriaEndDate.addOrCriteria(criteriaEndDateNull); 461 462 criteria.addAndCriteria(criteriaEndDate); 463 } 464 else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) { 465 // (date < active from date) or (active from date is null) or (date >= active to date) 466 Criteria criteriaNonActive = new Criteria(); 467 criteriaNonActive.addGreaterThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp); 468 469 Criteria criteriaEndDate = new Criteria(); 470 criteriaEndDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp); 471 criteriaNonActive.addOrCriteria(criteriaEndDate); 472 473 criteria.addAndCriteria(criteriaNonActive); 474 } 475 } 476 477 /** 478 * Translates criteria for current status to criteria on the active from field 479 * 480 * @param example - business object being queried on 481 * @param currentSearchValue - value for the current search field, should convert to boolean 482 * @param criteria - Criteria object being built 483 */ 484 protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue, Criteria criteria, Map searchValues) { 485 Criteria maxBeginDateCriteria = new Criteria(); 486 487 Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues); 488 489 maxBeginDateCriteria.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp); 490 491 List<String> groupByFieldList = dataDictionaryService.getGroupByAttributesForEffectiveDating(example 492 .getClass()); 493 if (groupByFieldList == null) { 494 return; 495 } 496 497 // join back to main query with the group by fields 498 String[] groupBy = new String[groupByFieldList.size()]; 499 for (int i = 0; i < groupByFieldList.size(); i++) { 500 String groupByField = groupByFieldList.get(i); 501 groupBy[i] = groupByField; 502 503 maxBeginDateCriteria.addEqualToField(groupByField, Criteria.PARENT_QUERY_PREFIX + groupByField); 504 } 505 506 String[] columns = new String[1]; 507 columns[0] = "max(" + KRADPropertyConstants.ACTIVE_FROM_DATE + ")"; 508 509 QueryByCriteria query = QueryFactory.newReportQuery(example.getClass(), columns, maxBeginDateCriteria, true); 510 query.addGroupBy(groupBy); 511 512 String currentBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(currentSearchValue); 513 if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) { 514 criteria.addIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query); 515 } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(currentBooleanStr)) { 516 criteria.addNotIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query); 517 } 518 } 519 520 /** 521 * @param propertyName 522 * @param propertyValue 523 * @param propertyType 524 * @param criteria 525 */ 526 private void addOrCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) { 527 addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.OR.op()); 528 } 529 530 /** 531 * @param propertyName 532 * @param propertyValue 533 * @param propertyType 534 * @param criteria 535 */ 536 private void addAndCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) { 537 addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.AND.op()); 538 } 539 540 private void addNotCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) { 541 542 String[] splitPropVal = StringUtils.split(propertyValue, SearchOperator.NOT.op()); 543 try { 544 int strLength = splitPropVal.length; 545 // if Not'ed empty criteria 546 if (strLength == 0) 547 throw new IllegalArgumentException("Improper syntax of NOT operator in " + propertyName); 548 // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b) 549 if (strLength > 1) { 550 String expandedNot = SearchOperator.NOT + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op()); 551 // we know that since this method was called, treatWildcardsAndOperatorsAsLiteral must be false 552 addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria); 553 } 554 else { 555 // only one so add a not like 556 criteria.addNotLike(propertyName, splitPropVal[0]); 557 } 558 } catch (IllegalArgumentException ex) { 559 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_NOT_SYNTAX, propertyName); 560 } 561 } 562 563 /** 564 * Builds a sub criteria object joined with an 'AND' or 'OR' (depending on splitValue) using the split values of propertyValue. Then joins back the 565 * sub criteria to the main criteria using an 'AND'. 566 */ 567 private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria, String splitValue) { 568 String[] splitPropVal = StringUtils.split(propertyValue, splitValue); 569 570 Criteria subCriteria = new Criteria(); 571 for (int i = 0; i < splitPropVal.length; i++) { 572 Criteria predicate = new Criteria(); 573 574 addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate); 575 if (splitValue.equals(SearchOperator.OR.op())) { 576 subCriteria.addOrCriteria(predicate); 577 } 578 if (splitValue.equals(SearchOperator.AND.op())) { 579 subCriteria.addAndCriteria(predicate); 580 } 581 } 582 583 criteria.addAndCriteria(subCriteria); 584 } 585 586 private java.sql.Date parseDate(String dateString) { 587 dateString = dateString.trim(); 588 try { 589 return dateTimeService.convertToSqlDate(dateString); 590 } catch (ParseException ex) { 591 return null; 592 } 593 } 594 595 /** 596 * Adds to the criteria object based on query characters given 597 */ 598 private void addDateRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) { 599 600 try { 601 if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) { 602 if (treatWildcardsAndOperatorsAsLiteral) 603 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 604 String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op()); 605 if (rangeValues.length < 2) 606 throw new IllegalArgumentException("Improper syntax of BETWEEN operator in " + propertyName); 607 608 criteria.addBetween(propertyName, parseDate( ObjectUtils.clean(rangeValues[0] ) ), parseDate( ObjectUtils.clean(rangeValues[1] ) ) ); 609 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) { 610 if (treatWildcardsAndOperatorsAsLiteral) 611 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 612 criteria.addGreaterOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) ); 613 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) { 614 if (treatWildcardsAndOperatorsAsLiteral) 615 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 616 criteria.addLessOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) ); 617 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) { 618 if (treatWildcardsAndOperatorsAsLiteral) 619 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 620 criteria.addGreaterThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) ); 621 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) { 622 if (treatWildcardsAndOperatorsAsLiteral) 623 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName); 624 criteria.addLessThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) ); 625 } else { 626 criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) ); 627 } 628 } catch (IllegalArgumentException ex) { 629 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_BETWEEN_SYNTAX, propertyName); 630 } 631 } 632 633 private BigDecimal cleanNumeric( String value ) { 634 String cleanedValue = value.replaceAll( "[^-0-9.]", "" ); 635 // ensure only one "minus" at the beginning, if any 636 if ( cleanedValue.lastIndexOf( '-' ) > 0 ) { 637 if ( cleanedValue.charAt( 0 ) == '-' ) { 638 cleanedValue = "-" + cleanedValue.replaceAll( "-", "" ); 639 } else { 640 cleanedValue = cleanedValue.replaceAll( "-", "" ); 641 } 642 } 643 // ensure only one decimal in the string 644 int decimalLoc = cleanedValue.lastIndexOf( '.' ); 645 if ( cleanedValue.indexOf( '.' ) != decimalLoc ) { 646 cleanedValue = cleanedValue.substring( 0, decimalLoc ).replaceAll( "\\.", "" ) + cleanedValue.substring( decimalLoc ); 647 } 648 try { 649 return new BigDecimal( cleanedValue ); 650 } catch ( NumberFormatException ex ) { 651 GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM, new String[] { "Invalid Numeric Input: " + value }); 652 return null; 653 } 654 } 655 656 /** 657 * Adds to the criteria object based on query characters given 658 */ 659 private void addNumericRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) { 660 661 try { 662 if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) { 663 if (treatWildcardsAndOperatorsAsLiteral) 664 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 665 String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op()); 666 if (rangeValues.length < 2) 667 throw new IllegalArgumentException("Improper syntax of BETWEEN operator in " + propertyName); 668 669 criteria.addBetween(propertyName, cleanNumeric( rangeValues[0] ), cleanNumeric( rangeValues[1] )); 670 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) { 671 if (treatWildcardsAndOperatorsAsLiteral) 672 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 673 criteria.addGreaterOrEqualThan(propertyName, cleanNumeric(propertyValue)); 674 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) { 675 if (treatWildcardsAndOperatorsAsLiteral) 676 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 677 criteria.addLessOrEqualThan(propertyName, cleanNumeric(propertyValue)); 678 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) { 679 if (treatWildcardsAndOperatorsAsLiteral) 680 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 681 criteria.addGreaterThan(propertyName, cleanNumeric( propertyValue ) ); 682 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) { 683 if (treatWildcardsAndOperatorsAsLiteral) 684 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName); 685 criteria.addLessThan(propertyName, cleanNumeric(propertyValue)); 686 } else { 687 criteria.addEqualTo(propertyName, cleanNumeric(propertyValue)); 688 } 689 } catch (IllegalArgumentException ex) { 690 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_BETWEEN_SYNTAX, propertyName); 691 } 692 } 693 694 /** 695 * Adds to the criteria object based on query characters given 696 */ 697 private void addStringRangeCriteria(String propertyName, String propertyValue, Criteria criteria) { 698 699 try { 700 if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) { 701 String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op()); 702 if (rangeValues.length < 2) 703 throw new IllegalArgumentException("Improper syntax of BETWEEN operator in " + propertyName); 704 705 criteria.addBetween(propertyName, rangeValues[0], rangeValues[1]); 706 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) { 707 criteria.addGreaterOrEqualThan(propertyName, ObjectUtils.clean(propertyValue)); 708 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) { 709 criteria.addLessOrEqualThan(propertyName, ObjectUtils.clean(propertyValue)); 710 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) { 711 criteria.addGreaterThan(propertyName, ObjectUtils.clean(propertyValue)); 712 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) { 713 criteria.addLessThan(propertyName, ObjectUtils.clean(propertyValue)); 714 } else { 715 criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue)); 716 } 717 } catch (IllegalArgumentException ex) { 718 GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_BETWEEN_SYNTAX, propertyName); 719 } 720 } 721 722 public void setDateTimeService(DateTimeService dateTimeService) { 723 this.dateTimeService = dateTimeService; 724 } 725 726 public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) { 727 this.persistenceStructureService = persistenceStructureService; 728 } 729 730 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 731 this.dataDictionaryService = dataDictionaryService; 732 } 733}