001/** 002 * Copyright 2005-2018 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.lookup; 017 018import org.apache.commons.beanutils.PropertyUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.core.api.CoreApiServiceLocator; 021import org.kuali.rice.core.api.encryption.EncryptionService; 022import org.kuali.rice.core.api.search.SearchOperator; 023import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 024import org.kuali.rice.krad.bo.ExternalizableBusinessObject; 025import org.kuali.rice.krad.data.KradDataServiceLocator; 026import org.kuali.rice.krad.datadictionary.RelationshipDefinition; 027import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException; 028import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 029import org.kuali.rice.krad.service.ModuleService; 030import org.kuali.rice.krad.uif.UifConstants; 031import org.kuali.rice.krad.uif.UifPropertyPaths; 032import org.kuali.rice.krad.uif.lifecycle.ComponentPostMetadata; 033import org.kuali.rice.krad.uif.lifecycle.ViewPostMetadata; 034import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 035import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils; 036import org.kuali.rice.krad.util.KRADConstants; 037import org.kuali.rice.krad.util.KRADPropertyConstants; 038import org.kuali.rice.krad.util.KRADUtils; 039import org.kuali.rice.krad.web.form.UifFormBase; 040import org.springframework.beans.PropertyAccessorUtils; 041 042import javax.servlet.http.HttpServletRequest; 043 044import java.sql.Date; 045import java.sql.Timestamp; 046import java.text.ParseException; 047import java.util.ArrayList; 048import java.util.Calendar; 049import java.util.Collections; 050import java.util.HashMap; 051import java.util.HashSet; 052import java.util.List; 053import java.util.Map; 054import java.util.Set; 055 056/** 057 * Provides static utility methods for use within the lookup framework. 058 * 059 * @author Kuali Rice Team (rice.collab@kuali.org) 060 */ 061public class LookupUtils { 062 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupUtils.class); 063 private static final String[] searchList = new String[SearchOperator.QUERY_CHARACTERS.size()]; 064 065 static { 066 int index = 0; 067 for (SearchOperator operator : SearchOperator.QUERY_CHARACTERS) { 068 searchList[index++] = operator.op(); 069 } 070 } 071 072 private static final String[] replacementList = Collections.nCopies(searchList.length, "").toArray(new String[0]); 073 074 private LookupUtils() {} 075 076 /** 077 * Retrieves the value for the given parameter name to send as a lookup parameter. 078 * 079 * @param form form instance to retrieve values from 080 * @param request request object to retrieve parameters from 081 * @param lookupObjectClass data object class associated with the lookup, used to check whether the 082 * value needs to be encyrpted 083 * @param propertyName name of the property associated with the parameter, used to check whether the 084 * value needs to be encrypted 085 * @param parameterName name of the parameter to retrieve the value for 086 * @return String parameter value or empty string if no value was found 087 */ 088 public static String retrieveLookupParameterValue(UifFormBase form, HttpServletRequest request, 089 Class<?> lookupObjectClass, String propertyName, String parameterName) { 090 // return a null value if it is secure 091 if (KRADUtils.isSecure(propertyName, lookupObjectClass)) { 092 LOG.warn("field name " + propertyName + " is a secure value and not returned in parameter result value"); 093 return null; 094 } 095 096 String parameterValue = ""; 097 098 // get literal parameter values first 099 if (StringUtils.startsWith(parameterName, "'") && StringUtils.endsWith(parameterName, "'")) { 100 parameterValue = StringUtils.substringBetween(parameterName, "'"); 101 } else if (parameterValue.startsWith(KRADConstants.LOOKUP_PARAMETER_LITERAL_PREFIX 102 + KRADConstants.LOOKUP_PARAMETER_LITERAL_DELIMITER)) { 103 parameterValue = StringUtils.removeStart(parameterValue, KRADConstants.LOOKUP_PARAMETER_LITERAL_PREFIX 104 + KRADConstants.LOOKUP_PARAMETER_LITERAL_DELIMITER); 105 } 106 // check if parameter is in request 107 else if (request.getParameterMap().containsKey(parameterName)) { 108 parameterValue = request.getParameter(parameterName); 109 } 110 // get parameter value from form object 111 else { 112 parameterValue = ObjectPropertyUtils.getPropertyValueAsText(form, parameterName); 113 } 114 115 return parameterValue; 116 } 117 118 /** 119 * Retrieves the default KRAD base lookup URL, used to build lookup URLs in code 120 * 121 * @return String base lookup URL (everything except query string) 122 */ 123 public static String getBaseLookupUrl() { 124 return CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 125 KRADConstants.KRAD_LOOKUP_URL_KEY); 126 } 127 128 /** 129 * Uses the DataDictionary to determine whether to force uppercase the value, and if it should, then it does the 130 * uppercase, and returns the upper-cased value. 131 * 132 * @param dataObjectClass parent DO class that the fieldName is a member of 133 * @param fieldName name of the field to be forced to uppercase 134 * @param fieldValue value of the field that may be uppercased 135 * @return the correctly uppercased fieldValue if it should be uppercased, otherwise fieldValue is returned 136 * unchanged 137 */ 138 public static String forceUppercase(Class<?> dataObjectClass, String fieldName, String fieldValue) { 139 // short-circuit to exit if there isnt enough information to do the forceUppercase 140 if (StringUtils.isBlank(fieldValue)) { 141 return fieldValue; 142 } 143 144 // parameter validation 145 if (dataObjectClass == null) { 146 throw new IllegalArgumentException("Parameter dataObjectClass passed in with null value."); 147 } 148 149 if (StringUtils.isBlank(fieldName)) { 150 throw new IllegalArgumentException("Parameter fieldName passed in with empty value."); 151 } 152 153 if (!KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined(dataObjectClass, fieldName) 154 .booleanValue()) { 155 return fieldValue; 156 } 157 158 boolean forceUpperCase = false; 159 try { 160 forceUpperCase = KRADServiceLocatorWeb.getDataDictionaryService() 161 .getAttributeForceUppercase(dataObjectClass, fieldName).booleanValue(); 162 } catch (UnknownBusinessClassAttributeException ubae) { 163 // do nothing, don't alter the fieldValue 164 } 165 166 if (forceUpperCase && !fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) { 167 return fieldValue.toUpperCase(); 168 } 169 170 return fieldValue; 171 } 172 173 /** 174 * Uses the DataDictionary to determine whether to force uppercase the values, and if it should, then it does the 175 * uppercase, and returns the upper-cased Map of fieldname/fieldValue pairs. 176 * 177 * @param dataObjectClass parent DO class that the fieldName is a member of 178 * @param fieldValues a Map<String,String> where the key is the fieldName and the value is the fieldValue 179 * @return the same Map is returned, with the appropriate values uppercased (if any) 180 */ 181 public static Map<String, String> forceUppercase(Class<?> dataObjectClass, Map<String, String> fieldValues) { 182 if (dataObjectClass == null) { 183 throw new IllegalArgumentException("Parameter boClass passed in with null value."); 184 } 185 186 if (fieldValues == null) { 187 throw new IllegalArgumentException("Parameter fieldValues passed in with null value."); 188 } 189 190 for (String fieldName : fieldValues.keySet()) { 191 fieldValues.put(fieldName, forceUppercase(dataObjectClass, fieldName, fieldValues.get(fieldName))); 192 } 193 194 return fieldValues; 195 } 196 197 /** 198 * Parses and returns the lookup result set limit, checking first for the limit for the specific view, 199 * then the class being looked up, and then the global application limit if there isn't a limit specific 200 * to this data object class. 201 * 202 * @param dataObjectClass class to get limit for 203 * @param lookupForm lookupForm to use. May be null if the form is unknown. If lookupForm is null, only the 204 * dataObjectClass will be used to find the search results set limit 205 * @return result set limit 206 */ 207 public static Integer getSearchResultsLimit(Class dataObjectClass, LookupForm lookupForm) { 208 Integer limit = KRADServiceLocatorWeb.getViewDictionaryService().getResultSetLimitForLookup(dataObjectClass, 209 lookupForm); 210 if (limit == null) { 211 limit = getApplicationSearchResultsLimit(); 212 } 213 214 return limit; 215 } 216 217 /** 218 * Retrieves the default application search limit configured through a system parameter. 219 * 220 * @return default result set limit of the application 221 */ 222 public static Integer getApplicationSearchResultsLimit() { 223 String limitString = CoreFrameworkServiceLocator.getParameterService() 224 .getParameterValueAsString(KRADConstants.KRAD_NAMESPACE, 225 KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE, 226 KRADConstants.SystemGroupParameterNames.LOOKUP_RESULTS_LIMIT); 227 if (limitString != null) { 228 return Integer.valueOf(limitString); 229 } 230 231 return null; 232 } 233 234 /** 235 * Retrieves the default application multiple value search limit configured through a system parameter. 236 * 237 * @return default multiple value result set limit of the application 238 */ 239 public static Integer getApplicationMultipleValueSearchResultsLimit() { 240 String limitString = CoreFrameworkServiceLocator.getParameterService() 241 .getParameterValueAsString(KRADConstants.KRAD_NAMESPACE, 242 KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE, 243 KRADConstants.SystemGroupParameterNames.MULTIPLE_VALUE_LOOKUP_RESULTS_LIMIT); 244 if (limitString != null) { 245 return Integer.valueOf(limitString); 246 } 247 248 return null; 249 } 250 251 /** 252 * Determines what Timestamp should be used for active queries on effective dated records. Determination made as 253 * follows: 254 * 255 * <ul> 256 * <li>Use activeAsOfDate value from search values Map if value is not empty</li> 257 * <li>If search value given, try to convert to sql date, if conversion fails, try to convert to Timestamp</li> 258 * <li>If search value empty, use current Date</li> 259 * <li>If Timestamp value not given, create Timestamp from given Date setting the time as 1 second before midnight 260 * </ul> 261 * 262 * @param searchValues map containing search key/value pairs 263 * @return timestamp to be used for active criteria 264 */ 265 public static Timestamp getActiveDateTimestampForCriteria(Map searchValues) { 266 Date activeDate = CoreApiServiceLocator.getDateTimeService().getCurrentSqlDate(); 267 268 Timestamp activeTimestamp = null; 269 if (searchValues.containsKey(KRADPropertyConstants.ACTIVE_AS_OF_DATE)) { 270 String activeAsOfDate = (String) searchValues.get(KRADPropertyConstants.ACTIVE_AS_OF_DATE); 271 if (StringUtils.isNotBlank(activeAsOfDate)) { 272 try { 273 activeDate = CoreApiServiceLocator.getDateTimeService() 274 .convertToSqlDate(KRADUtils.clean(activeAsOfDate)); 275 } catch (ParseException e) { 276 // try to parse as timestamp 277 try { 278 activeTimestamp = CoreApiServiceLocator.getDateTimeService() 279 .convertToSqlTimestamp(KRADUtils.clean(activeAsOfDate)); 280 } catch (ParseException e1) { 281 throw new RuntimeException("Unable to convert date: " + KRADUtils.clean(activeAsOfDate)); 282 } 283 } 284 } 285 } 286 287 // if timestamp not given set to 1 second before midnight on the given date 288 if (activeTimestamp == null) { 289 Calendar cal = Calendar.getInstance(); 290 291 cal.setTime(activeDate); 292 cal.set(Calendar.HOUR, cal.getMaximum(Calendar.HOUR)); 293 cal.set(Calendar.MINUTE, cal.getMaximum(Calendar.MINUTE)); 294 cal.set(Calendar.SECOND, cal.getMaximum(Calendar.SECOND)); 295 296 activeTimestamp = new Timestamp(cal.getTime().getTime()); 297 } 298 299 return activeTimestamp; 300 } 301 302 /** 303 * Changes from/to dates into the range operators the lookupable dao expects ("..",">" etc) this method modifies 304 * the passed in map and returns an updated search criteria map. 305 * 306 * @param searchCriteria map of criteria currently set for which the date criteria will be adjusted 307 * @return map updated search criteria 308 */ 309 public static Map<String, String> preprocessDateFields(Map<String, String> searchCriteria) { 310 Map<String, String> fieldsToUpdate = new HashMap<String, String>(); 311 Map<String, String> searchCriteriaUpdated = new HashMap<String, String>(searchCriteria); 312 313 Set<String> fieldsForLookup = searchCriteria.keySet(); 314 for (String propName : fieldsForLookup) { 315 if (propName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) { 316 String from_DateValue = searchCriteria.get(propName); 317 String dateFieldName = 318 StringUtils.remove(propName, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX); 319 String to_DateValue = searchCriteria.get(dateFieldName); 320 String newPropValue = to_DateValue; 321 322 if (StringUtils.isNotEmpty(from_DateValue) && StringUtils.isNotEmpty(to_DateValue)) { 323 newPropValue = from_DateValue + SearchOperator.BETWEEN + to_DateValue; 324 } else if (StringUtils.isNotEmpty(from_DateValue) && StringUtils.isEmpty(to_DateValue)) { 325 newPropValue = SearchOperator.GREATER_THAN_EQUAL.op() + from_DateValue; 326 } else if (StringUtils.isNotEmpty(to_DateValue) && StringUtils.isEmpty(from_DateValue)) { 327 newPropValue = SearchOperator.LESS_THAN_EQUAL.op() + to_DateValue; 328 } // could optionally continue on else here 329 330 fieldsToUpdate.put(dateFieldName, newPropValue); 331 } 332 } 333 334 // update lookup values from found date values to update 335 Set<String> keysToUpdate = fieldsToUpdate.keySet(); 336 for (String updateKey : keysToUpdate) { 337 searchCriteriaUpdated.put(updateKey, fieldsToUpdate.get(updateKey)); 338 } 339 340 return searchCriteriaUpdated; 341 } 342 343 /** 344 * Checks whether any of the fieldValues being passed refer to a property within an ExternalizableBusinessObject. 345 * 346 * @param boClass business object class of the lookup 347 * @param fieldValues map of the lookup criteria values 348 * @return true if externalizable business object are contained, false otherwise 349 * @throws IllegalAccessException 350 * @throws InstantiationException 351 */ 352 public static boolean hasExternalBusinessObjectProperty(Class<?> boClass, 353 Map<String, String> fieldValues) throws IllegalAccessException, InstantiationException { 354 Object sampleBo = boClass.newInstance(); 355 for (String key : fieldValues.keySet()) { 356 if (isExternalBusinessObjectProperty(sampleBo, key)) { 357 return true; 358 } 359 } 360 361 return false; 362 } 363 364 /** 365 * Check whether the given property represents a property within an EBO starting with the sampleBo object given. 366 * This is used to determine if a criteria needs to be applied to the EBO first, 367 * before sending to the normal lookup DAO. 368 * 369 * @param sampleBo business object of the property to be tested 370 * @param propertyName property name to be tested 371 * @return true if the property is within an externalizable business object. 372 */ 373 public static boolean isExternalBusinessObjectProperty(Object sampleBo, String propertyName) { 374 if (propertyName.indexOf(".") > 0 && !StringUtils.contains(propertyName, "add.")) { 375 Class<?> propertyClass = 376 ObjectPropertyUtils.getPropertyType(sampleBo, StringUtils.substringBeforeLast(propertyName, ".")); 377 if (propertyClass != null) { 378 return ExternalizableBusinessObjectUtils.isExternalizableBusinessObjectInterface(propertyClass); 379 } 380 } 381 382 return false; 383 } 384 385 /** 386 * Returns a map stripped of any properties which refer to ExternalizableBusinessObjects. These values may not be 387 * passed into the lookup service, since the objects they refer to are not in the 388 * local database. 389 * 390 * @param boClass business object class of the lookup 391 * @param fieldValues map of lookup criteria from which to remove the externalizable business objects 392 * @return map of lookup criteria without externalizable business objects 393 */ 394 public static Map<String, String> removeExternalizableBusinessObjectFieldValues(Class<?> boClass, 395 Map<String, String> fieldValues) throws IllegalAccessException, InstantiationException { 396 Map<String, String> eboFieldValues = new HashMap<String, String>(); 397 Object sampleBo = boClass.newInstance(); 398 for (String key : fieldValues.keySet()) { 399 if (!isExternalBusinessObjectProperty(sampleBo, key)) { 400 eboFieldValues.put(key, fieldValues.get(key)); 401 } 402 } 403 404 return eboFieldValues; 405 } 406 407 /** 408 * Return the EBO fieldValue entries explicitly for the given eboPropertyName. (I.e., any properties with the given 409 * property name as a prefix. 410 * 411 * @param eboPropertyName the externalizable business object property name to retrieve 412 * @param fieldValues map of lookup criteria 413 * return map of lookup criteria for the given eboPropertyName 414 */ 415 public static Map<String, String> getExternalizableBusinessObjectFieldValues(String eboPropertyName, 416 Map<String, String> fieldValues) { 417 Map<String, String> eboFieldValues = new HashMap<String, String>(); 418 for (String key : fieldValues.keySet()) { 419 if (key.startsWith(eboPropertyName + ".")) { 420 eboFieldValues.put(StringUtils.substringAfterLast(key, "."), fieldValues.get(key)); 421 } 422 } 423 424 return eboFieldValues; 425 } 426 427 /** 428 * Get the complete list of all properties referenced in the fieldValues that are ExternalizableBusinessObjects. 429 * 430 * <p> 431 * This is a list of the EBO object references themselves, not of the properties within them. 432 * </p> 433 * 434 * @param boClass business object class of the lookup 435 * @param fieldValues map of lookup criteria from which to return the externalizable business objects 436 * @return map of lookup criteria that are externalizable business objects 437 * @throws IllegalAccessException 438 * @throws InstantiationException 439 */ 440 public static List<String> getExternalizableBusinessObjectProperties(Class<?> boClass, 441 Map<String, String> fieldValues) throws IllegalAccessException, InstantiationException { 442 Set<String> eboPropertyNames = new HashSet<String>(); 443 444 Object sampleBo = boClass.newInstance(); 445 for (String key : fieldValues.keySet()) { 446 if (isExternalBusinessObjectProperty(sampleBo, key)) { 447 eboPropertyNames.add(StringUtils.substringBeforeLast(key, ".")); 448 } 449 } 450 451 return new ArrayList<String>(eboPropertyNames); 452 } 453 454 /** 455 * Given an property on the main BO class, return the defined type of the ExternalizableBusinessObject. This will 456 * be used by other code to determine the correct module service to call for the lookup. 457 * 458 * @param boClass business object class of the lookup 459 * @param propertyName property of which the externalizable business object type is to be determined 460 * @return externalizable business object type 461 * @throws IllegalAccessException 462 * @throws InstantiationException 463 */ 464 public static Class<? extends ExternalizableBusinessObject> getExternalizableBusinessObjectClass(Class<?> boClass, 465 String propertyName) throws IllegalAccessException, InstantiationException { 466 return (Class<? extends ExternalizableBusinessObject>) ObjectPropertyUtils 467 .getPropertyType(boClass.newInstance(), StringUtils.substringBeforeLast(propertyName, ".")); 468 } 469 470 /** 471 * Looks for criteria against nested EBOs and performs a search against that EBO and updates the criteria. 472 * 473 * @param searchCriteria map of criteria currently set 474 * @param unbounded indicates whether the complete result should be returned. When set to false the result is 475 * limited (if necessary) to the max search result limit configured. 476 * @return Map of adjusted criteria for nested EBOs 477 * @throws InstantiationException 478 * @throws IllegalAccessException 479 */ 480 public static Map<String, String> adjustCriteriaForNestedEBOs(Class<?> dataObjectClass, 481 Map<String, String> searchCriteria, 482 boolean unbounded) throws InstantiationException, IllegalAccessException { 483 // remove the EBO criteria 484 Map<String, String> nonEboFieldValues = removeExternalizableBusinessObjectFieldValues( 485 dataObjectClass, searchCriteria); 486 if (LOG.isDebugEnabled()) { 487 LOG.debug("Non EBO properties removed: " + nonEboFieldValues); 488 } 489 490 // get the list of EBO properties attached to this object 491 List<String> eboPropertyNames = getExternalizableBusinessObjectProperties(dataObjectClass, searchCriteria); 492 if (LOG.isDebugEnabled()) { 493 LOG.debug("EBO properties: " + eboPropertyNames); 494 } 495 496 // loop over those properties 497 for (String eboPropertyName : eboPropertyNames) { 498 // extract the properties as known to the EBO 499 Map<String, String> eboFieldValues = LookupUtils.getExternalizableBusinessObjectFieldValues(eboPropertyName, 500 searchCriteria); 501 if (LOG.isDebugEnabled()) { 502 LOG.debug("EBO properties for master EBO property: " + eboPropertyName); 503 LOG.debug("properties: " + eboFieldValues); 504 } 505 506 // run search against attached EBO's module service 507 ModuleService eboModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService( 508 getExternalizableBusinessObjectClass(dataObjectClass, eboPropertyName)); 509 510 // KULRICE-4401 made eboResults an empty list and only filled if service is found. 511 List<?> eboResults = Collections.emptyList(); 512 if (eboModuleService != null) { 513 eboResults = eboModuleService.getExternalizableBusinessObjectsListForLookup( 514 getExternalizableBusinessObjectClass(dataObjectClass, eboPropertyName), 515 (Map) eboFieldValues, unbounded); 516 } else { 517 LOG.debug("EBO ModuleService is null: " + eboPropertyName); 518 } 519 520 // get the parent property type 521 Class<?> eboParentClass; 522 String eboParentPropertyName; 523 if (PropertyAccessorUtils.isNestedOrIndexedProperty(eboPropertyName)) { 524 eboParentPropertyName = StringUtils.substringBeforeLast(eboPropertyName, "."); 525 try { 526 eboParentClass = KradDataServiceLocator.getDataObjectService().wrap(dataObjectClass.newInstance()).getPropertyType( 527 eboParentPropertyName); 528 } catch (Exception ex) { 529 throw new RuntimeException( 530 "Unable to create an instance of the business object class: " + dataObjectClass 531 .getName(), ex); 532 } 533 } else { 534 eboParentClass = dataObjectClass; 535 eboParentPropertyName = null; 536 } 537 538 if (LOG.isDebugEnabled()) { 539 LOG.debug("determined EBO parent class/property name: " + eboParentClass + "/" + eboParentPropertyName); 540 } 541 542 // look that up in the DD (BOMDS) find the appropriate relationship 543 // CHECK THIS: what if eboPropertyName is a nested attribute - need to strip off the 544 // eboParentPropertyName if not null 545 RelationshipDefinition rd = KRADServiceLocatorWeb.getLegacyDataAdapter().getDictionaryRelationship( 546 eboParentClass, eboPropertyName); 547 if (LOG.isDebugEnabled()) { 548 LOG.debug("Obtained RelationshipDefinition for " + eboPropertyName); 549 LOG.debug(rd); 550 } 551 552 // copy the needed properties (primary only) to the field values KULRICE-4446 do 553 // so only if the relationship definition exists 554 // NOTE: this will work only for single-field PK unless the ORM 555 // layer is directly involved 556 // (can't make (field1,field2) in ( (v1,v2),(v3,v4) ) style 557 // queries in the lookup framework 558 if (KRADUtils.isNotNull(rd)) { 559 if (rd.getPrimitiveAttributes().size() > 1) { 560 throw new RuntimeException( 561 "EBO Links don't work for relationships with multiple-field primary keys."); 562 } 563 String boProperty = rd.getPrimitiveAttributes().get(0).getSourceName(); 564 String eboProperty = rd.getPrimitiveAttributes().get(0).getTargetName(); 565 StringBuffer boPropertyValue = new StringBuffer(); 566 567 // loop over the results, making a string that the lookup DAO will convert into an 568 // SQL "IN" clause 569 for (Object ebo : eboResults) { 570 if (boPropertyValue.length() != 0) { 571 boPropertyValue.append(SearchOperator.OR.op()); 572 } 573 try { 574 boPropertyValue.append(PropertyUtils.getProperty(ebo, eboProperty).toString()); 575 } catch (Exception ex) { 576 LOG.warn("Unable to get value for " + eboProperty + " on " + ebo); 577 } 578 } 579 580 if (eboParentPropertyName == null) { 581 // non-nested property containing the EBO 582 nonEboFieldValues.put(boProperty, boPropertyValue.toString()); 583 } else { 584 // property nested within the main searched-for BO that contains the EBO 585 nonEboFieldValues.put(eboParentPropertyName + "." + boProperty, boPropertyValue.toString()); 586 } 587 } 588 } 589 590 return nonEboFieldValues; 591 } 592 593 /** 594 * Removes query characters (such as wildcards) from the given string value. 595 * 596 * @param criteriaValue string to clean 597 * @return string with query characters removed 598 */ 599 public static String scrubQueryCharacters(String criteriaValue) { 600 return StringUtils.replaceEach(criteriaValue, searchList, replacementList); 601 } 602 603 /** 604 * Generates a key string in case of multivalue return. The values are extracted 605 * from the list of properties on the lineDataObject. 606 * 607 * If fieldConversionKeys is empty return the identifier string for the lineDataObject 608 * 609 * @param lineDataObject Object from which to extract values 610 * @param fieldConversionKeys List of keys whose values have to be concatenated 611 * @return string representing the multivalue key 612 */ 613 public static String generateMultiValueKey(Object lineDataObject, List<String> fieldConversionKeys) { 614 String lineIdentifier = ""; 615 616 if(fieldConversionKeys == null || fieldConversionKeys.isEmpty()) { 617 lineIdentifier = 618 KRADServiceLocatorWeb.getLegacyDataAdapter().getDataObjectIdentifierString(lineDataObject); 619 } else { 620 Collections.sort(fieldConversionKeys); 621 for (String fromFieldName : fieldConversionKeys) { 622 Object fromFieldValue = ObjectPropertyUtils.getPropertyValue(lineDataObject, fromFieldName); 623 624 if (fromFieldValue != null) { 625 lineIdentifier += fromFieldValue; 626 } 627 628 lineIdentifier += ":"; 629 } 630 lineIdentifier = StringUtils.removeEnd(lineIdentifier, ":"); 631 } 632 633 return lineIdentifier; 634 } 635 636 /** 637 * Merges the lookup result selections that are part of the request with the selectedLookupResultsCache maintained in 638 * the session. 639 * 640 * @param form lookup form instance containing the selected results and lookup configuration 641 */ 642 public static void refreshLookupResultSelections(LookupForm form) { 643 int displayStart = 0; 644 int displayLength = 0; 645 646 // avoid blowing the stack if the session expired 647 ViewPostMetadata viewPostMetadata = form.getViewPostMetadata(); 648 if (viewPostMetadata != null) { 649 650 // only one concurrent request per view please 651 synchronized (viewPostMetadata) { 652 ComponentPostMetadata oldCollectionGroup = viewPostMetadata.getComponentPostMetadata("uLookupResults"); 653 displayStart = (Integer) oldCollectionGroup.getData(UifConstants.PostMetadata.COLL_DISPLAY_START); 654 displayLength = (Integer) oldCollectionGroup.getData(UifConstants.PostMetadata.COLL_DISPLAY_LENGTH); 655 } 656 } 657 658 List<? extends Object> lookupResults = (List<? extends Object>) form.getLookupResults(); 659 List<String> fromFieldNames = form.getMultiValueReturnFields(); 660 661 Set<String> selectedLines = form.getSelectedCollectionLines().get(UifPropertyPaths.LOOKUP_RESULTS); 662 Set<String> selectedLookupResultsCache = form.getSelectedLookupResultsCache(); 663 664 selectedLines = (selectedLines == null) ? new HashSet<String>() : selectedLines; 665 666 for(int i = displayStart; i < displayStart + displayLength; i++ ) { 667 if(i >= form.getLookupResults().size()) break; 668 669 Object lineItem = lookupResults.get(i); 670 String lineIdentifier = LookupUtils.generateMultiValueKey(lineItem, fromFieldNames); 671 672 if(!selectedLines.contains(lineIdentifier)) { 673 selectedLookupResultsCache.remove(lineIdentifier); 674 } else { 675 selectedLookupResultsCache.add(lineIdentifier); 676 } 677 } 678 679 selectedLines.addAll( selectedLookupResultsCache ); 680 681 form.getSelectedCollectionLines().put(UifPropertyPaths.LOOKUP_RESULTS, selectedLines); 682 } 683 684}