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.kns.lookup; 017 018import java.security.GeneralSecurityException; 019import java.sql.Date; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027import java.util.Properties; 028import java.util.Set; 029 030import org.apache.commons.lang.StringUtils; 031import org.kuali.rice.core.api.CoreApiServiceLocator; 032import org.kuali.rice.core.api.config.property.ConfigContext; 033import org.kuali.rice.core.api.config.property.ConfigurationService; 034import org.kuali.rice.core.api.encryption.EncryptionService; 035import org.kuali.rice.core.api.search.SearchOperator; 036import org.kuali.rice.core.api.util.RiceKeyConstants; 037import org.kuali.rice.core.api.util.cache.CopiedObject; 038import org.kuali.rice.core.api.util.type.TypeUtils; 039import org.kuali.rice.core.web.format.DateFormatter; 040import org.kuali.rice.core.web.format.Formatter; 041import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 042import org.kuali.rice.coreservice.framework.parameter.ParameterService; 043import org.kuali.rice.kim.api.identity.Person; 044import org.kuali.rice.kns.document.authorization.BusinessObjectRestrictions; 045import org.kuali.rice.kns.document.authorization.FieldRestriction; 046import org.kuali.rice.kns.inquiry.Inquirable; 047import org.kuali.rice.kns.service.BusinessObjectAuthorizationService; 048import org.kuali.rice.kns.service.BusinessObjectDictionaryService; 049import org.kuali.rice.kns.service.BusinessObjectMetaDataService; 050import org.kuali.rice.kns.service.KNSServiceLocator; 051import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService; 052import org.kuali.rice.kns.util.FieldUtils; 053import org.kuali.rice.kns.util.KNSConstants; 054import org.kuali.rice.kns.util.WebUtils; 055import org.kuali.rice.kns.web.comparator.CellComparatorHelper; 056import org.kuali.rice.kns.web.struts.form.LookupForm; 057import org.kuali.rice.kns.web.struts.form.MultipleValueLookupForm; 058import org.kuali.rice.kns.web.ui.Column; 059import org.kuali.rice.kns.web.ui.Field; 060import org.kuali.rice.kns.web.ui.ResultRow; 061import org.kuali.rice.kns.web.ui.Row; 062import org.kuali.rice.krad.bo.BusinessObject; 063import org.kuali.rice.krad.bo.PersistableBusinessObject; 064import org.kuali.rice.krad.datadictionary.AttributeSecurity; 065import org.kuali.rice.krad.datadictionary.mask.MaskFormatter; 066import org.kuali.rice.krad.exception.ValidationException; 067import org.kuali.rice.krad.service.BusinessObjectService; 068import org.kuali.rice.krad.service.DataDictionaryService; 069import org.kuali.rice.krad.service.KRADServiceLocator; 070import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 071import org.kuali.rice.krad.service.LookupService; 072import org.kuali.rice.krad.service.PersistenceStructureService; 073import org.kuali.rice.krad.service.SequenceAccessorService; 074import org.kuali.rice.krad.util.GlobalVariables; 075import org.kuali.rice.krad.util.KRADConstants; 076import org.kuali.rice.krad.util.ObjectUtils; 077import org.kuali.rice.krad.util.UrlFactory; 078 079/** 080 * This class declares many of the common spring injected properties, the get/set-ers for them, 081 * and some common util methods that require the injected services 082 */ 083public abstract class AbstractLookupableHelperServiceImpl implements LookupableHelperService { 084 085 protected static final String TITLE_RETURN_URL_PREPENDTEXT_PROPERTY = "title.return.url.value.prependtext"; 086 protected static final String TITLE_ACTION_URL_PREPENDTEXT_PROPERTY = "title.action.url.value.prependtext"; 087 protected static final String ACTION_URLS_CHILDREN_SEPARATOR = " | "; 088 protected static final String ACTION_URLS_CHILDREN_STARTER = " ["; 089 protected static final String ACTION_URLS_CHILDREN_END = "]"; 090 protected static final String ACTION_URLS_SEPARATOR = " "; 091 protected static final String ACTION_URLS_EMPTY = " "; 092 093 protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AbstractLookupableHelperServiceImpl.class); 094 095 protected Class businessObjectClass; 096 protected Map<String, String[]> parameters; 097 protected BusinessObjectDictionaryService businessObjectDictionaryService; 098 protected BusinessObjectMetaDataService businessObjectMetaDataService; 099 protected DataDictionaryService dataDictionaryService; 100 protected PersistenceStructureService persistenceStructureService; 101 protected EncryptionService encryptionService; 102 protected List<String> readOnlyFieldsList; 103 protected String backLocation; 104 protected String docFormKey; 105 protected Map fieldConversions; 106 protected LookupService lookupService; 107 protected List<Row> rows; 108 protected String referencesToRefresh; 109 protected SequenceAccessorService sequenceAccessorService; 110 protected BusinessObjectService businessObjectService; 111 protected LookupResultsService lookupResultsService; 112 protected String docNum; 113 protected ConfigurationService configurationService; 114 protected ParameterService parameterService; 115 protected BusinessObjectAuthorizationService businessObjectAuthorizationService; 116 117 /** 118 * @return the docNum 119 */ 120 public String getDocNum() { 121 return this.docNum; 122 } 123 124 /** 125 * @param docNum the docNum to set 126 */ 127 public void setDocNum(String docNum) { 128 this.docNum = docNum; 129 } 130 131 public AbstractLookupableHelperServiceImpl() { 132 rows = null; 133 } 134 135 /** 136 * This implementation always returns false. 137 * 138 * @see LookupableHelperService#checkForAdditionalFields(java.util.Map) 139 */ 140 public boolean checkForAdditionalFields(Map<String, String> fieldValues) { 141 return false; 142 } 143 144 /** 145 * @see LookupableHelperService#getBusinessObjectClass() 146 */ 147 public Class getBusinessObjectClass() { 148 return businessObjectClass; 149 } 150 151 /** 152 * @see LookupableHelperService#setBusinessObjectClass(java.lang.Class) 153 */ 154 public void setBusinessObjectClass(Class businessObjectClass) { 155 this.businessObjectClass = businessObjectClass; 156 setRows(); 157 } 158 159 /** 160 * @see LookupableHelperService#getParameters() 161 */ 162 public Map<String, String[]> getParameters() { 163 return parameters; 164 } 165 166 /** 167 * @see LookupableHelperService#setParameters(java.util.Map) 168 */ 169 public void setParameters(Map<String, String[]> parameters) { 170 this.parameters = parameters; 171 } 172 173 /** 174 * Gets the dataDictionaryService attribute. 175 * 176 * @return Returns the dataDictionaryService. 177 */ 178 public DataDictionaryService getDataDictionaryService() { 179 return dataDictionaryService != null ? dataDictionaryService : KRADServiceLocatorWeb.getDataDictionaryService(); 180 } 181 182 /** 183 * Sets the dataDictionaryService attribute value. 184 * 185 * @param dataDictionaryService The dataDictionaryService to set. 186 */ 187 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 188 this.dataDictionaryService = dataDictionaryService; 189 } 190 191 /** 192 * Gets the businessObjectDictionaryService attribute. 193 * 194 * @return Returns the businessObjectDictionaryService. 195 */ 196 public BusinessObjectDictionaryService getBusinessObjectDictionaryService() { 197 return businessObjectDictionaryService != null ? businessObjectDictionaryService : KNSServiceLocator 198 .getBusinessObjectDictionaryService(); 199 } 200 201 /** 202 * Sets the businessObjectDictionaryService attribute value. 203 * 204 * @param businessObjectDictionaryService 205 * The businessObjectDictionaryService to set. 206 */ 207 public void setBusinessObjectDictionaryService(BusinessObjectDictionaryService businessObjectDictionaryService) { 208 this.businessObjectDictionaryService = businessObjectDictionaryService; 209 } 210 211 /** 212 * Gets the businessObjectMetaDataService attribute. 213 * 214 * @return Returns the businessObjectMetaDataService. 215 */ 216 public BusinessObjectMetaDataService getBusinessObjectMetaDataService() { 217 return businessObjectMetaDataService != null ? businessObjectMetaDataService : KNSServiceLocator 218 .getBusinessObjectMetaDataService(); 219 } 220 221 /** 222 * Sets the businessObjectMetaDataService attribute value. 223 * 224 * @param businessObjectMetaDataService The businessObjectMetaDataService to set. 225 */ 226 public void setBusinessObjectMetaDataService(BusinessObjectMetaDataService businessObjectMetaDataService) { 227 this.businessObjectMetaDataService = businessObjectMetaDataService; 228 } 229 230 /** 231 * Gets the persistenceStructureService attribute. 232 * 233 * @return Returns the persistenceStructureService. 234 */ 235 protected PersistenceStructureService getPersistenceStructureService() { 236 return persistenceStructureService != null ? persistenceStructureService : KRADServiceLocator 237 .getPersistenceStructureService(); 238 } 239 240 /** 241 * Sets the persistenceStructureService attribute value. 242 * 243 * @param persistenceStructureService The persistenceStructureService to set. 244 */ 245 public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) { 246 this.persistenceStructureService = persistenceStructureService; 247 } 248 249 /** 250 * Gets the encryptionService attribute. 251 * 252 * @return Returns the encryptionService. 253 */ 254 protected EncryptionService getEncryptionService() { 255 return encryptionService != null ? encryptionService : CoreApiServiceLocator.getEncryptionService(); 256 } 257 258 /** 259 * Sets the encryptionService attribute value. 260 * 261 * @param encryptionService The encryptionService to set. 262 */ 263 public void setEncryptionService(EncryptionService encryptionService) { 264 this.encryptionService = encryptionService; 265 } 266 267 protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService; 268 269 public MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() { 270 if (maintenanceDocumentDictionaryService == null) { 271 maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService(); 272 } 273 return maintenanceDocumentDictionaryService; 274 } 275 276 277 public BusinessObjectAuthorizationService getBusinessObjectAuthorizationService() { 278 if (businessObjectAuthorizationService == null) { 279 businessObjectAuthorizationService = KNSServiceLocator.getBusinessObjectAuthorizationService(); 280 } 281 return businessObjectAuthorizationService; 282 } 283 284 protected Inquirable kualiInquirable; 285 286 public Inquirable getKualiInquirable() { 287 if (kualiInquirable == null) { 288 kualiInquirable = KNSServiceLocator.getKualiInquirable(); 289 } 290 return kualiInquirable; 291 } 292 293 public void setMaintenanceDocumentDictionaryService(MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService) { 294 this.maintenanceDocumentDictionaryService = maintenanceDocumentDictionaryService; 295 } 296 297 public void setKualiInquirable(Inquirable kualiInquirable) { 298 this.kualiInquirable = kualiInquirable; 299 } 300 301 302 public ConfigurationService getKualiConfigurationService() { 303 if (configurationService == null) { 304 configurationService = CoreApiServiceLocator.getKualiConfigurationService(); 305 } 306 return configurationService; 307 } 308 309 public void setParameterService(ConfigurationService configurationService) { 310 this.configurationService = configurationService; 311 } 312 313 314 public ParameterService getParameterService() { 315 if (parameterService == null) { 316 parameterService = CoreFrameworkServiceLocator.getParameterService(); 317 } 318 return parameterService; 319 } 320 321 public void setParameterService(ParameterService parameterService) { 322 this.parameterService = parameterService; 323 } 324 325 /** 326 * Determines if underlying lookup bo has associated maintenance document that allows new or copy maintenance actions. 327 * 328 * @return true if bo has maint doc that allows new or copy actions 329 */ 330 public boolean allowsMaintenanceNewOrCopyAction() { 331 boolean allowsNewOrCopy = false; 332 333 String maintDocTypeName = getMaintenanceDocumentTypeName(); 334 Class boClass = this.getBusinessObjectClass(); 335 336 if (StringUtils.isNotBlank(maintDocTypeName)) { 337 allowsNewOrCopy = getBusinessObjectAuthorizationService().canCreate(boClass, GlobalVariables.getUserSession().getPerson(), maintDocTypeName); 338 } 339 return allowsNewOrCopy; 340 } 341 342 protected boolean allowsMaintenanceEditAction(BusinessObject businessObject) { 343 boolean allowsEdit = false; 344 345 String maintDocTypeName = getMaintenanceDocumentTypeName(); 346 347 if (StringUtils.isNotBlank(maintDocTypeName)) { 348 allowsEdit = getBusinessObjectAuthorizationService().canMaintain(businessObject, GlobalVariables.getUserSession().getPerson(), maintDocTypeName); 349 } 350 return allowsEdit; 351 } 352 353 354 /** 355 * Build a maintenance url. 356 * 357 * @param bo - business object representing the record for maint. 358 * @param methodToCall - maintenance action 359 * @return 360 */ 361 final public String getMaintenanceUrl(BusinessObject businessObject, HtmlData htmlData, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) { 362 htmlData.setTitle(getActionUrlTitleText(businessObject, htmlData.getDisplayText(), pkNames, businessObjectRestrictions)); 363 return htmlData.constructCompleteHtmlTag(); 364 } 365 366 /** 367 * This method is called by performLookup method to generate action urls. 368 * It calls the method getCustomActionUrls to get html data, calls getMaintenanceUrl to get the actual html tag, 369 * and returns a formatted/concatenated string of action urls. 370 * 371 * @see LookupableHelperService#getActionUrls(org.kuali.rice.krad.bo.BusinessObject) 372 */ 373 final public String getActionUrls(BusinessObject businessObject, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) { 374 StringBuffer actions = new StringBuffer(); 375 List<HtmlData> htmlDataList = getCustomActionUrls(businessObject, pkNames); 376 for (HtmlData htmlData : htmlDataList) { 377 actions.append(getMaintenanceUrl(businessObject, htmlData, pkNames, businessObjectRestrictions)); 378 if (htmlData.getChildUrlDataList() != null) { 379 if (htmlData.getChildUrlDataList().size() > 0) { 380 actions.append(ACTION_URLS_CHILDREN_STARTER); 381 for (HtmlData childURLData : htmlData.getChildUrlDataList()) { 382 actions.append(getMaintenanceUrl(businessObject, childURLData, pkNames, businessObjectRestrictions)); 383 actions.append(ACTION_URLS_CHILDREN_SEPARATOR); 384 } 385 if (actions.toString().endsWith(ACTION_URLS_CHILDREN_SEPARATOR)) 386 actions.delete(actions.length() - ACTION_URLS_CHILDREN_SEPARATOR.length(), actions.length()); 387 actions.append(ACTION_URLS_CHILDREN_END); 388 } 389 } 390 actions.append(ACTION_URLS_SEPARATOR); 391 } 392 if (actions.toString().endsWith(ACTION_URLS_SEPARATOR)) 393 actions.delete(actions.length() - ACTION_URLS_SEPARATOR.length(), actions.length()); 394 return actions.toString(); 395 } 396 397 /** 398 * Child classes should override this method if they want to return some other action urls. 399 * 400 * @returns This default implementation returns links to edit and copy maintenance action for 401 * the current maintenance record if the business object class has an associated maintenance document. 402 * Also checks value of allowsNewOrCopy in maintenance document xml before rendering the copy link. 403 * @see LookupableHelperService#getCustomActionUrls(org.kuali.rice.krad.bo.BusinessObject, java.util.List, java.util.List pkNames) 404 */ 405 public List<HtmlData> getCustomActionUrls(BusinessObject businessObject, List pkNames) { 406 List<HtmlData> htmlDataList = new ArrayList<HtmlData>(); 407 if (allowsMaintenanceEditAction(businessObject)) { 408 htmlDataList.add(getUrlData(businessObject, KRADConstants.MAINTENANCE_EDIT_METHOD_TO_CALL, pkNames)); 409 } 410 if (allowsMaintenanceNewOrCopyAction()) { 411 htmlDataList.add(getUrlData(businessObject, KRADConstants.MAINTENANCE_COPY_METHOD_TO_CALL, pkNames)); 412 } 413 if (allowsMaintenanceDeleteAction(businessObject)) { 414 htmlDataList.add(getUrlData(businessObject, KRADConstants.MAINTENANCE_DELETE_METHOD_TO_CALL, pkNames)); 415 } 416 return htmlDataList; 417 } 418 419 /** 420 * This method ... 421 * for KULRice 3070 422 * 423 * @return 424 */ 425 protected boolean allowsMaintenanceDeleteAction(BusinessObject businessObject) { 426 427 boolean allowsMaintain = false; 428 boolean allowsDelete = false; 429 430 String maintDocTypeName = getMaintenanceDocumentTypeName(); 431 432 if (StringUtils.isNotBlank(maintDocTypeName)) { 433 allowsMaintain = getBusinessObjectAuthorizationService().canMaintain(businessObject, GlobalVariables.getUserSession().getPerson(), maintDocTypeName); 434 } 435 436 allowsDelete = KNSServiceLocator.getMaintenanceDocumentDictionaryService().getAllowsRecordDeletion(businessObjectClass); 437 438 return allowsDelete && allowsMaintain; 439 } 440 441 /** 442 * This method constructs an AnchorHtmlData. 443 * This method can be overriden by child classes if they want to construct the html data in a different way. 444 * Foe example, if they want different type of html tag, like input/image. 445 * 446 * @param businessObject 447 * @param methodToCall 448 * @param displayText 449 * @param pkNames 450 * @return 451 */ 452 protected HtmlData.AnchorHtmlData getUrlData(BusinessObject businessObject, String methodToCall, String displayText, List pkNames) { 453 454 String href = getActionUrlHref(businessObject, methodToCall, pkNames); 455 //String title = StringUtils.isBlank(href)?"":getActionUrlTitleText(businessObject, displayText, pkNames); 456 HtmlData.AnchorHtmlData anchorHtmlData = new HtmlData.AnchorHtmlData(href, methodToCall, displayText); 457 return anchorHtmlData; 458 } 459 460 /** 461 * This method calls its overloaded method with displayText as methodToCall 462 * 463 * @param businessObject 464 * @param methodToCall 465 * @param pkNames 466 * @return 467 */ 468 protected HtmlData.AnchorHtmlData getUrlData(BusinessObject businessObject, String methodToCall, List pkNames) { 469 return getUrlData(businessObject, methodToCall, methodToCall, pkNames); 470 } 471 472 /** 473 * A utility method that returns an empty list of action urls. 474 * 475 * @return 476 */ 477 protected List<HtmlData> getEmptyActionUrls() { 478 return new ArrayList<HtmlData>(); 479 } 480 481 protected HtmlData getEmptyAnchorHtmlData() { 482 return new HtmlData.AnchorHtmlData(); 483 } 484 485 /** 486 * This method generates and returns href for the given parameters. 487 * This method can be overridden by child classes if they have to generate href differently. 488 * For example, refer to IntendedIncumbentLookupableHelperServiceImpl 489 * 490 * @param businessObject 491 * @param methodToCall 492 * @param pkNames 493 * @return 494 */ 495 protected String getActionUrlHref(BusinessObject businessObject, String methodToCall, List pkNames) { 496 Properties parameters = new Properties(); 497 parameters.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, methodToCall); 498 // TODO: why is this not using the businessObject parmeter's class? 499 parameters.put(KRADConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, businessObject.getClass().getName()); 500 parameters.putAll(getParametersFromPrimaryKey(businessObject, pkNames)); 501 if (StringUtils.isNotBlank(getReturnLocation())) { 502 parameters.put(KRADConstants.RETURN_LOCATION_PARAMETER, getReturnLocation()); 503 } 504 return UrlFactory.parameterizeUrl(KRADConstants.MAINTENANCE_ACTION, parameters); 505 } 506 507 protected Properties getParametersFromPrimaryKey(BusinessObject businessObject, List pkNames) { 508 Properties parameters = new Properties(); 509 for (Iterator iter = pkNames.iterator(); iter.hasNext();) { 510 String fieldNm = (String) iter.next(); 511 512 // If we cannot find the attribute in the data dictionary, then we cannot determine whether it should be encrypted 513 if (getDataDictionaryService().getAttributeDefinition(businessObjectClass.getName(), fieldNm) == null) { 514 String errorMessage = "The field " + fieldNm + " could not be found in the data dictionary for class " 515 + businessObjectClass.getName() + ", and thus it could not be determined whether it is a secure field."; 516 517 if (ConfigContext.getCurrentContextConfig().getBooleanProperty(KNSConstants.EXCEPTION_ON_MISSING_FIELD_CONVERSION_ATTRIBUTE, false)) { 518 throw new RuntimeException(errorMessage); 519 } else { 520 LOG.error(errorMessage); 521 continue; 522 } 523 } 524 525 Object fieldVal = ObjectUtils.getPropertyValue(businessObject, fieldNm); 526 if (fieldVal == null) { 527 fieldVal = KRADConstants.EMPTY_STRING; 528 } 529 if (fieldVal instanceof java.sql.Date) { 530 String formattedString = ""; 531 if (Formatter.findFormatter(fieldVal.getClass()) != null) { 532 Formatter formatter = Formatter.getFormatter(fieldVal.getClass()); 533 formattedString = (String) formatter.format(fieldVal); 534 fieldVal = formattedString; 535 } 536 } 537 538 // secure values are not passed in urls 539 if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, fieldNm)) { 540 LOG.warn("field name " + fieldNm + " is a secure value and not included in pk parameter results"); 541 continue; 542 } 543 544 parameters.put(fieldNm, fieldVal.toString()); 545 } 546 return parameters; 547 } 548 549 /** 550 * This method generates and returns title text for action urls. 551 * Child classes can override this if they want to generate the title text differently. 552 * For example, refer to BatchJobStatusLookupableHelperServiceImpl 553 * 554 * @param businessObject 555 * @param displayText 556 * @param pkNames 557 * @return 558 */ 559 protected String getActionUrlTitleText(BusinessObject businessObject, String displayText, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) { 560 String prependTitleText = displayText + " " 561 + getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(getBusinessObjectClass().getName()).getObjectLabel() 562 + " " 563 + this.getKualiConfigurationService().getPropertyValueAsString(TITLE_ACTION_URL_PREPENDTEXT_PROPERTY); 564 return HtmlData.getTitleText(prependTitleText, businessObject, pkNames, businessObjectRestrictions); 565 } 566 567 /** 568 * Returns the maintenance document type associated with the business object class or null if one does not 569 * exist. 570 * 571 * @return String representing the maintenance document type name 572 */ 573 protected String getMaintenanceDocumentTypeName() { 574 MaintenanceDocumentDictionaryService dd = getMaintenanceDocumentDictionaryService(); 575 String maintDocTypeName = dd.getDocumentTypeName(getBusinessObjectClass()); 576 return maintDocTypeName; 577 } 578 579 /** 580 * Gets the readOnlyFieldsList attribute. 581 * 582 * @return Returns the readOnlyFieldsList. 583 */ 584 public List<String> getReadOnlyFieldsList() { 585 return readOnlyFieldsList; 586 } 587 588 589 /** 590 * Sets the readOnlyFieldsList attribute value. 591 * 592 * @param readOnlyFieldsList The readOnlyFieldsList to set. 593 */ 594 public void setReadOnlyFieldsList(List<String> readOnlyFieldsList) { 595 this.readOnlyFieldsList = readOnlyFieldsList; 596 } 597 598 protected HashMap<String, Boolean> noLookupResultFieldInquiryCache = new HashMap<String, Boolean>(); 599 protected HashMap<Class, Class> inquirableClassCache = new HashMap<Class, Class>(); 600 protected HashMap<String, Boolean> forceLookupResultFieldInquiryCache = new HashMap<String, Boolean>(); 601 602 /** 603 * Returns the inquiry url for a field if one exist. 604 * 605 * @param bo the business object instance to build the urls for 606 * @param propertyName the property which links to an inquirable 607 * @return String url to inquiry 608 */ 609 public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) { 610 HtmlData inquiryUrl = new HtmlData.AnchorHtmlData(); 611 612 String cacheKey = bo.getClass().getName() + "." + propertyName; 613 Boolean noLookupResultFieldInquiry = noLookupResultFieldInquiryCache.get(cacheKey); 614 if (noLookupResultFieldInquiry == null) { 615 noLookupResultFieldInquiry = getBusinessObjectDictionaryService().noLookupResultFieldInquiry(bo.getClass(), propertyName); 616 if (noLookupResultFieldInquiry == null) { 617 noLookupResultFieldInquiry = Boolean.TRUE; 618 } 619 noLookupResultFieldInquiryCache.put(cacheKey, noLookupResultFieldInquiry); 620 } 621 if (!noLookupResultFieldInquiry) { 622 623 Class<Inquirable> inquirableClass = inquirableClassCache.get(bo.getClass()); 624 if (!inquirableClassCache.containsKey(bo.getClass())) { 625 inquirableClass = getBusinessObjectDictionaryService().getInquirableClass(bo.getClass()); 626 inquirableClassCache.put(bo.getClass(), inquirableClass); 627 } 628 Inquirable inq = null; 629 try { 630 if (inquirableClass != null) { 631 inq = inquirableClass.newInstance(); 632 } else { 633 inq = getKualiInquirable(); 634 if (LOG.isDebugEnabled()) { 635 LOG.debug("Default Inquirable Class: " + inq.getClass()); 636 } 637 } 638 Boolean forceLookupResultFieldInquiry = forceLookupResultFieldInquiryCache.get(cacheKey); 639 if (forceLookupResultFieldInquiry == null) { 640 forceLookupResultFieldInquiry = getBusinessObjectDictionaryService().forceLookupResultFieldInquiry(bo.getClass(), propertyName); 641 if (forceLookupResultFieldInquiry == null) { 642 forceLookupResultFieldInquiry = Boolean.FALSE; 643 } 644 forceLookupResultFieldInquiryCache.put(cacheKey, forceLookupResultFieldInquiry); 645 } 646 inquiryUrl = inq.getInquiryUrl(bo, propertyName, forceLookupResultFieldInquiry); 647 } catch (Exception ex) { 648 LOG.error("unable to create inquirable to get inquiry URL", ex); 649 } 650 } 651 652 return inquiryUrl; 653 } 654 655 protected CopiedObject<ArrayList<Column>> resultColumns = null; 656 657 /** 658 * Constructs the list of columns for the search results. All properties for the column objects come from the DataDictionary. 659 */ 660 public List<Column> getColumns() { 661 if (resultColumns == null) { 662 ArrayList<Column> columns = new ArrayList<Column>(); 663 for (String attributeName : getBusinessObjectDictionaryService().getLookupResultFieldNames(getBusinessObjectClass())) { 664 Column column = new Column(); 665 column.setPropertyName(attributeName); 666 String columnTitle = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName); 667 Boolean useShortLabel = getBusinessObjectDictionaryService().getLookupResultFieldUseShortLabel(businessObjectClass, attributeName); 668 if (useShortLabel != null && useShortLabel) { 669 columnTitle = getDataDictionaryService().getAttributeShortLabel(getBusinessObjectClass(), attributeName); 670 } 671 if (StringUtils.isBlank(columnTitle)) { 672 columnTitle = getDataDictionaryService().getCollectionLabel(getBusinessObjectClass(), attributeName); 673 } 674 column.setColumnTitle(columnTitle); 675 column.setMaxLength(getColumnMaxLength(attributeName)); 676 677 if (!businessObjectClass.isInterface()) { 678 try { 679 column.setFormatter(ObjectUtils.getFormatterWithDataDictionary(getBusinessObjectClass() 680 .newInstance(), attributeName)); 681 } catch (InstantiationException e) { 682 LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e); 683 // just swallow exception and leave formatter blank 684 } catch (IllegalAccessException e) { 685 LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e); 686 // just swallow exception and leave formatter blank 687 } 688 } 689 690 String alternateDisplayPropertyName = getBusinessObjectDictionaryService() 691 .getLookupFieldAlternateDisplayAttributeName(getBusinessObjectClass(), attributeName); 692 if (StringUtils.isNotBlank(alternateDisplayPropertyName)) { 693 column.setAlternateDisplayPropertyName(alternateDisplayPropertyName); 694 } 695 696 String additionalDisplayPropertyName = getBusinessObjectDictionaryService() 697 .getLookupFieldAdditionalDisplayAttributeName(getBusinessObjectClass(), attributeName); 698 if (StringUtils.isNotBlank(additionalDisplayPropertyName)) { 699 column.setAdditionalDisplayPropertyName(additionalDisplayPropertyName); 700 } else { 701 boolean translateCodes = getBusinessObjectDictionaryService().tranlateCodesInLookup(getBusinessObjectClass()); 702 if (translateCodes) { 703 FieldUtils.setAdditionalDisplayPropertyForCodes(getBusinessObjectClass(), attributeName, column); 704 } 705 } 706 707 column.setTotal(getBusinessObjectDictionaryService().getLookupResultFieldTotal(getBusinessObjectClass(), attributeName)); 708 709 columns.add(column); 710 } 711 resultColumns = ObjectUtils.deepCopyForCaching(columns); 712 return columns; 713 } 714 return resultColumns.getContent(); 715 } 716 717 protected static Integer RESULTS_DEFAULT_MAX_COLUMN_LENGTH = null; 718 719 protected int getColumnMaxLength(String attributeName) { 720 Integer fieldDefinedMaxLength = getBusinessObjectDictionaryService().getLookupResultFieldMaxLength(getBusinessObjectClass(), attributeName); 721 if (fieldDefinedMaxLength == null) { 722 if (RESULTS_DEFAULT_MAX_COLUMN_LENGTH == null) { 723 try { 724 RESULTS_DEFAULT_MAX_COLUMN_LENGTH = Integer.valueOf(getParameterService().getParameterValueAsString( 725 KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE, KRADConstants.RESULTS_DEFAULT_MAX_COLUMN_LENGTH)); 726 } catch (NumberFormatException ex) { 727 LOG.error("Lookup field max length parameter not found and unable to parse default set in system parameters (RESULTS_DEFAULT_MAX_COLUMN_LENGTH)."); 728 } 729 } 730 return RESULTS_DEFAULT_MAX_COLUMN_LENGTH.intValue(); 731 } 732 return fieldDefinedMaxLength.intValue(); 733 } 734 735 /** 736 * @return Returns the backLocation. 737 */ 738 public String getBackLocation() { 739 return WebUtils.sanitizeBackLocation(this.backLocation); 740 } 741 742 /** 743 * @param backLocation The backLocation to set. 744 */ 745 public void setBackLocation(String backLocation) { 746 this.backLocation = backLocation; 747 } 748 749 /** 750 * @see LookupableHelperService#getReturnLocation() 751 */ 752 public String getReturnLocation() { 753 return backLocation; 754 } 755 756 /** 757 * This method is for lookupable implementations 758 * 759 * @see LookupableHelperService#getReturnUrl(org.kuali.rice.krad.bo.BusinessObject, java.util.Map, java.lang.String, java.util.List) 760 */ 761 final public HtmlData getReturnUrl(BusinessObject businessObject, Map fieldConversions, String lookupImpl, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { 762 String href = getReturnHref(businessObject, fieldConversions, lookupImpl, returnKeys); 763 String returnUrlAnchorLabel = 764 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY); 765 HtmlData.AnchorHtmlData anchor = new HtmlData.AnchorHtmlData(href, HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions)); 766 anchor.setDisplayText(returnUrlAnchorLabel); 767 return anchor; 768 } 769 770 /** 771 * This method is for lookupable implementations 772 * 773 * @param businessObject 774 * @param fieldConversions 775 * @param lookupImpl 776 * @param returnKeys 777 * @return 778 */ 779 final protected String getReturnHref(BusinessObject businessObject, Map fieldConversions, String lookupImpl, List returnKeys) { 780 if (StringUtils.isNotBlank(backLocation)) { 781 return UrlFactory.parameterizeUrl(backLocation, getParameters( 782 businessObject, fieldConversions, lookupImpl, returnKeys)); 783 } 784 return ""; 785 } 786 787 /** 788 * @see LookupableHelperService#getReturnUrl(org.kuali.core.bo.BusinessObject, java.util.Map, java.lang.String) 789 */ 790 public HtmlData getReturnUrl(BusinessObject businessObject, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { 791 Properties parameters = getParameters( 792 businessObject, lookupForm.getFieldConversions(), lookupForm.getLookupableImplServiceName(), returnKeys); 793 if (StringUtils.isEmpty(lookupForm.getHtmlDataType()) || HtmlData.ANCHOR_HTML_DATA_TYPE.equals(lookupForm.getHtmlDataType())) 794 return getReturnAnchorHtmlData(businessObject, parameters, lookupForm, returnKeys, businessObjectRestrictions); 795 else 796 return getReturnInputHtmlData(businessObject, parameters, lookupForm, returnKeys, businessObjectRestrictions); 797 } 798 799 protected HtmlData getReturnInputHtmlData(BusinessObject businessObject, Properties parameters, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { 800 String returnUrlAnchorLabel = 801 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY); 802 String name = KRADConstants.MULTIPLE_VALUE_LOOKUP_SELECTED_OBJ_ID_PARAM_PREFIX + lookupForm.getLookupObjectId(); 803 HtmlData.InputHtmlData input = new HtmlData.InputHtmlData(name, HtmlData.InputHtmlData.CHECKBOX_INPUT_TYPE); 804 input.setTitle(HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions)); 805 if (((MultipleValueLookupForm) lookupForm).getCompositeObjectIdMap() == null || 806 ((MultipleValueLookupForm) lookupForm).getCompositeObjectIdMap().get( 807 ((PersistableBusinessObject) businessObject).getObjectId()) == null) { 808 input.setChecked(""); 809 } else { 810 input.setChecked(HtmlData.InputHtmlData.CHECKBOX_CHECKED_VALUE); 811 } 812 input.setValue(HtmlData.InputHtmlData.CHECKBOX_CHECKED_VALUE); 813 return input; 814 } 815 816 protected HtmlData getReturnAnchorHtmlData(BusinessObject businessObject, Properties parameters, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { 817 String returnUrlAnchorLabel = 818 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY); 819 HtmlData.AnchorHtmlData anchor = new HtmlData.AnchorHtmlData( 820 getReturnHref(parameters, lookupForm, returnKeys), 821 HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions)); 822 anchor.setDisplayText(returnUrlAnchorLabel); 823 return anchor; 824 } 825 826 protected String getReturnHref(Properties parameters, LookupForm lookupForm, List returnKeys) { 827 if (StringUtils.isNotBlank(backLocation)) { 828 String href = UrlFactory.parameterizeUrl(backLocation, parameters); 829 return addToReturnHref(href, lookupForm); 830 } 831 return ""; 832 } 833 834 protected String addToReturnHref(String href, LookupForm lookupForm) { 835 String lookupAnchor = ""; 836 if (StringUtils.isNotEmpty(lookupForm.getAnchor())) { 837 lookupAnchor = lookupForm.getAnchor(); 838 } 839 href += "&anchor=" + lookupAnchor + "&docNum=" + (StringUtils.isEmpty(getDocNum()) ? "" : getDocNum()); 840 return href; 841 } 842 843 protected Properties getParameters(BusinessObject bo, Map<String, String> fieldConversions, String lookupImpl, List returnKeys) { 844 Properties parameters = new Properties(); 845 parameters.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL); 846 if (getDocFormKey() != null) { 847 parameters.put(KRADConstants.DOC_FORM_KEY, getDocFormKey()); 848 } 849 if (lookupImpl != null) { 850 parameters.put(KRADConstants.REFRESH_CALLER, lookupImpl); 851 } 852 if (getDocNum() != null) { 853 parameters.put(KRADConstants.DOC_NUM, getDocNum()); 854 } 855 856 if (getReferencesToRefresh() != null) { 857 parameters.put(KRADConstants.REFERENCES_TO_REFRESH, getReferencesToRefresh()); 858 } 859 860 Iterator returnKeysIt = getReturnKeys().iterator(); 861 while (returnKeysIt.hasNext()) { 862 String fieldNm = (String) returnKeysIt.next(); 863 864 // If we cannot find the attribute in the data dictionary, then we cannot determine whether it should be encrypted 865 if (getDataDictionaryService().getAttributeDefinition(businessObjectClass.getName(), fieldNm) == null) { 866 String errorMessage = "The field " + fieldNm + " could not be found in the data dictionary for class " 867 + businessObjectClass.getName() + ", and thus it could not be determined whether it is a secure field."; 868 869 if (ConfigContext.getCurrentContextConfig().getBooleanProperty(KNSConstants.EXCEPTION_ON_MISSING_FIELD_CONVERSION_ATTRIBUTE, false)) { 870 throw new RuntimeException(errorMessage); 871 } else { 872 LOG.error(errorMessage); 873 continue; 874 } 875 } 876 877 Object fieldVal = ObjectUtils.getPropertyValue(bo, fieldNm); 878 if (fieldVal == null) { 879 fieldVal = KRADConstants.EMPTY_STRING; 880 } 881 882 if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, fieldNm)) { 883 LOG.warn("field name " + fieldNm + " is a secure value and not included in parameter results"); 884 continue; 885 } 886 887 //need to format date in url 888 if (fieldVal instanceof Date) { 889 DateFormatter dateFormatter = new DateFormatter(); 890 fieldVal = dateFormatter.format(fieldVal); 891 } 892 893 if (fieldConversions.containsKey(fieldNm)) { 894 fieldNm = (String) fieldConversions.get(fieldNm); 895 } 896 897 parameters.put(fieldNm, fieldVal.toString()); 898 } 899 900 return parameters; 901 } 902 903 /** 904 * @return a List of the names of fields which are marked in data dictionary as return fields. 905 */ 906 public List<String> getReturnKeys() { 907 List<String> returnKeys; 908 if (fieldConversions != null && !fieldConversions.isEmpty()) { 909 returnKeys = new ArrayList<String>(fieldConversions.keySet()); 910 } else { 911 returnKeys = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass()); 912 } 913 914 return returnKeys; 915 } 916 917 /** 918 * Gets the docFormKey attribute. 919 * 920 * @return Returns the docFormKey. 921 */ 922 public String getDocFormKey() { 923 return docFormKey; 924 } 925 926 /** 927 * Sets the docFormKey attribute value. 928 * 929 * @param docFormKey The docFormKey to set. 930 */ 931 public void setDocFormKey(String docFormKey) { 932 this.docFormKey = docFormKey; 933 } 934 935 /** 936 * @see LookupableHelperService#setFieldConversions(java.util.Map) 937 */ 938 public void setFieldConversions(Map fieldConversions) { 939 this.fieldConversions = fieldConversions; 940 } 941 942 /** 943 * Gets the lookupService attribute. 944 * 945 * @return Returns the lookupService. 946 */ 947 protected LookupService getLookupService() { 948 return lookupService != null ? lookupService : KRADServiceLocatorWeb.getLookupService(); 949 } 950 951 /** 952 * Sets the lookupService attribute value. 953 * 954 * @param lookupService The lookupService to set. 955 */ 956 public void setLookupService(LookupService lookupService) { 957 this.lookupService = lookupService; 958 } 959 960 /** 961 * Uses the DD to determine which is the default sort order. 962 * 963 * @return property names that will be used to sort on by default 964 */ 965 public List<String> getDefaultSortColumns() { 966 return getBusinessObjectDictionaryService().getLookupDefaultSortFieldNames(getBusinessObjectClass()); 967 } 968 969 /** 970 * Checks that any required search fields have value. 971 * 972 * @see LookupableHelperService#validateSearchParameters(java.util.Map) 973 */ 974 public void validateSearchParameters(Map<String, String> fieldValues) { 975 List<String> lookupFieldAttributeList = null; 976 if (getBusinessObjectMetaDataService().isLookupable(getBusinessObjectClass())) { 977 lookupFieldAttributeList = getBusinessObjectMetaDataService().getLookupableFieldNames(getBusinessObjectClass()); 978 } 979 if (lookupFieldAttributeList == null) { 980 throw new RuntimeException("Lookup not defined for business object " + getBusinessObjectClass()); 981 } 982 for (Iterator iter = lookupFieldAttributeList.iterator(); iter.hasNext();) { 983 String attributeName = (String) iter.next(); 984 if (fieldValues.containsKey(attributeName)) { 985 // get label of attribute for message 986 String attributeLabel = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName); 987 988 String attributeValue = (String) fieldValues.get(attributeName); 989 990 // check for required if field does not have value 991 if (StringUtils.isBlank(attributeValue)) { 992 if ((getBusinessObjectDictionaryService().getLookupAttributeRequired(getBusinessObjectClass(), attributeName)).booleanValue()) { 993 GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_REQUIRED, attributeLabel); 994 } 995 } 996 validateSearchParameterWildcardAndOperators(attributeName, attributeValue); 997 } 998 } 999 1000 if (GlobalVariables.getMessageMap().hasErrors()) { 1001 throw new ValidationException("errors in search criteria"); 1002 } 1003 } 1004 1005 protected void validateSearchParameterWildcardAndOperators(String attributeName, String attributeValue) { 1006 if (StringUtils.isBlank(attributeValue)) 1007 return; 1008 1009 // make sure a wildcard/operator is in the value 1010 boolean found = false; 1011 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) { 1012 String queryCharacter = op.op(); 1013 1014 if (attributeValue.contains(queryCharacter)) { 1015 found = true; 1016 } 1017 } 1018 if (!found) 1019 return; 1020 1021 String attributeLabel = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName); 1022 if (getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, attributeName)) { 1023 BusinessObject example = null; 1024 try { 1025 example = (BusinessObject) businessObjectClass.newInstance(); 1026 } catch (Exception e) { 1027 LOG.error("Exception caught instantiating " + businessObjectClass.getName(), e); 1028 throw new RuntimeException("Cannot instantiate " + businessObjectClass.getName(), e); 1029 } 1030 1031 Class propertyType = ObjectUtils.getPropertyType(example, attributeName, getPersistenceStructureService()); 1032 if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) || TypeUtils.isTemporalClass(propertyType)) { 1033 GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_WILDCARDS_AND_OPERATORS_NOT_ALLOWED_ON_FIELD, attributeLabel); 1034 } 1035 if (TypeUtils.isStringClass(propertyType)) { 1036 GlobalVariables.getMessageMap().putInfo(attributeName, RiceKeyConstants.INFO_WILDCARDS_AND_OPERATORS_TREATED_LITERALLY, attributeLabel); 1037 } 1038 } else { 1039 if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, attributeName)) { 1040 if (!attributeValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) { 1041 // encrypted values usually come from the DB, so we don't need to filter for wildcards 1042 1043 // wildcards are not allowed on restricted fields, because they are typically encrypted, and wildcard searches cannot be performed without 1044 // decrypting every row, which is currently not supported by KNS 1045 1046 GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_SECURE_FIELD, attributeLabel); 1047 } 1048 } 1049 } 1050 } 1051 1052 /** 1053 * Constructs the list of rows for the search fields. All properties for the field objects come 1054 * from the DataDictionary. To be called by setBusinessObject 1055 */ 1056 protected void setRows() { 1057 List<String> lookupFieldAttributeList = null; 1058 if (getBusinessObjectMetaDataService().isLookupable(getBusinessObjectClass())) { 1059 lookupFieldAttributeList = getBusinessObjectMetaDataService().getLookupableFieldNames( 1060 getBusinessObjectClass()); 1061 } 1062 if (lookupFieldAttributeList == null) { 1063 throw new RuntimeException("Lookup not defined for business object " + getBusinessObjectClass()); 1064 } 1065 1066 // construct field object for each search attribute 1067 List fields = new ArrayList(); 1068 try { 1069 fields = FieldUtils.createAndPopulateFieldsForLookup(lookupFieldAttributeList, getReadOnlyFieldsList(), 1070 getBusinessObjectClass()); 1071 } catch (InstantiationException e) { 1072 throw new RuntimeException("Unable to create instance of business object class" + e.getMessage()); 1073 } catch (IllegalAccessException e) { 1074 throw new RuntimeException("Unable to create instance of business object class" + e.getMessage()); 1075 } 1076 1077 int numCols = getBusinessObjectDictionaryService().getLookupNumberOfColumns(this.getBusinessObjectClass()); 1078 1079 this.rows = FieldUtils.wrapFields(fields, numCols); 1080 } 1081 1082 public List<Row> getRows() { 1083 return rows; 1084 } 1085 1086 public abstract List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues); 1087 1088 /** 1089 * This implementation of this method throws an UnsupportedOperationException, since not every implementation 1090 * may actually want to use this operation. Subclasses desiring other behaviors 1091 * will need to override this. 1092 * 1093 * @see LookupableHelperService#getSearchResultsUnbounded(java.util.Map) 1094 */ 1095 public List<? extends BusinessObject> getSearchResultsUnbounded(Map<String, String> fieldValues) { 1096 throw new UnsupportedOperationException("Lookupable helper services do not always support getSearchResultsUnbounded"); 1097 } 1098 1099 /** 1100 * Performs the lookup and returns a collection of lookup items 1101 * 1102 * @param lookupForm 1103 * @param resultTable 1104 * @param bounded 1105 * @return 1106 */ 1107 public Collection<? extends BusinessObject> performLookup(LookupForm lookupForm, Collection<ResultRow> resultTable, boolean bounded) { 1108 Map lookupFormFields = lookupForm.getFieldsForLookup(); 1109 1110 setBackLocation((String) lookupFormFields.get(KRADConstants.BACK_LOCATION)); 1111 setDocFormKey((String) lookupFormFields.get(KRADConstants.DOC_FORM_KEY)); 1112 Collection<? extends BusinessObject> displayList; 1113 1114 LookupUtils.preProcessRangeFields(lookupFormFields); 1115 1116 // call search method to get results 1117 if (bounded) { 1118 displayList = getSearchResults(lookupFormFields); 1119 } else { 1120 displayList = getSearchResultsUnbounded(lookupFormFields); 1121 } 1122 1123 boolean hasReturnableRow = false; 1124 1125 List<String> returnKeys = getReturnKeys(); 1126 List<String> pkNames = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass()); 1127 Person user = GlobalVariables.getUserSession().getPerson(); 1128 1129 // iterate through result list and wrap rows with return url and action 1130 // urls 1131 for (BusinessObject element : displayList) { 1132 BusinessObject baseElement = element; 1133 //if ebo, then use base BO to get lookupId and find restrictions 1134 //we don't need to do this anymore as the BO is required to implement the EBO interface as of this time 1135 //if this needs reimplemented in the future, one should consider what happens/needs to happen 1136 //with the base BO fields (OBJ ID in particular) as they are all null/empty on new instantiation 1137 //which will fail if we try to depend on any values within it. 1138 //KULRICE-7223 1139// if (ExternalizableBusinessObjectUtils.isExternalizableBusinessObject(element.getClass())) { 1140// try { 1141// baseElement = (BusinessObject)this.getBusinessObjectClass().newInstance(); 1142// } catch (InstantiationException e) { 1143// e.printStackTrace(); 1144// } catch (IllegalAccessException e) { 1145// e.printStackTrace(); 1146// } 1147// } 1148 1149 final String lookupId = KNSServiceLocator.getLookupResultsService().getLookupId(baseElement); 1150 if (lookupId != null) { 1151 lookupForm.setLookupObjectId(lookupId); 1152 } 1153 1154 BusinessObjectRestrictions businessObjectRestrictions = getBusinessObjectAuthorizationService() 1155 .getLookupResultRestrictions(element, user); 1156 1157 HtmlData returnUrl = getReturnUrl(element, lookupForm, returnKeys, businessObjectRestrictions); 1158 String actionUrls = getActionUrls(element, pkNames, businessObjectRestrictions); 1159 // Fix for JIRA - KFSMI-2417 1160 if ("".equals(actionUrls)) { 1161 actionUrls = ACTION_URLS_EMPTY; 1162 } 1163 1164 List<Column> columns = getColumns(); 1165 for (Iterator iterator = columns.iterator(); iterator.hasNext();) { 1166 Column col = (Column) iterator.next(); 1167 1168 String propValue = ObjectUtils.getFormattedPropertyValue(element, col.getPropertyName(), col.getFormatter()); 1169 Class propClass = getPropertyClass(element, col.getPropertyName()); 1170 1171 col.setComparator(CellComparatorHelper.getAppropriateComparatorForPropertyClass(propClass)); 1172 col.setValueComparator(CellComparatorHelper.getAppropriateValueComparatorForPropertyClass(propClass)); 1173 1174 String propValueBeforePotientalMasking = propValue; 1175 propValue = maskValueIfNecessary(element.getClass(), col.getPropertyName(), propValue, 1176 businessObjectRestrictions); 1177 col.setPropertyValue(propValue); 1178 1179 // if property value is masked, don't display additional or alternate properties, or allow totals 1180 if (StringUtils.equals(propValueBeforePotientalMasking, propValue)) { 1181 if (StringUtils.isNotBlank(col.getAlternateDisplayPropertyName())) { 1182 String alternatePropertyValue = ObjectUtils.getFormattedPropertyValue(element, col 1183 .getAlternateDisplayPropertyName(), null); 1184 col.setPropertyValue(alternatePropertyValue); 1185 } 1186 1187 if (StringUtils.isNotBlank(col.getAdditionalDisplayPropertyName())) { 1188 String additionalPropertyValue = ObjectUtils.getFormattedPropertyValue(element, col 1189 .getAdditionalDisplayPropertyName(), null); 1190 col.setPropertyValue(col.getPropertyValue() + " *-* " + additionalPropertyValue); 1191 } 1192 } else { 1193 col.setTotal(false); 1194 } 1195 1196 if (col.isTotal()) { 1197 Object unformattedPropValue = ObjectUtils.getPropertyValue(element, col.getPropertyName()); 1198 col.setUnformattedPropertyValue(unformattedPropValue); 1199 } 1200 1201 if (StringUtils.isNotBlank(propValue)) { 1202 col.setColumnAnchor(getInquiryUrl(element, col.getPropertyName())); 1203 } 1204 } 1205 1206 ResultRow row = new ResultRow(columns, returnUrl.constructCompleteHtmlTag(), actionUrls); 1207 row.setRowId(returnUrl.getName()); 1208 row.setReturnUrlHtmlData(returnUrl); 1209 1210 // because of concerns of the BO being cached in session on the 1211 // ResultRow, 1212 // let's only attach it when needed (currently in the case of 1213 // export) 1214 if (getBusinessObjectDictionaryService().isExportable(getBusinessObjectClass())) { 1215 row.setBusinessObject(element); 1216 } 1217 1218 if (lookupId != null) { 1219 row.setObjectId(lookupId); 1220 } 1221 1222 boolean rowReturnable = isResultReturnable(element); 1223 row.setRowReturnable(rowReturnable); 1224 if (rowReturnable) { 1225 hasReturnableRow = true; 1226 } 1227 resultTable.add(row); 1228 } 1229 1230 lookupForm.setHasReturnableRow(hasReturnableRow); 1231 1232 return displayList; 1233 } 1234 1235 /** 1236 * Gets the Class for the property in the given BusinessObject instance, if 1237 * property is not accessible then runtime exception is thrown 1238 * 1239 * @param element BusinessObject instance that contains property 1240 * @param propertyName Name of property in BusinessObject to get class for 1241 * @return Type for property as Class 1242 */ 1243 protected Class getPropertyClass(BusinessObject element, String propertyName) { 1244 Class propClass = null; 1245 1246 try { 1247 propClass = ObjectUtils.getPropertyType(element, propertyName, getPersistenceStructureService()); 1248 1249 } catch (Exception e) { 1250 throw new RuntimeException("Cannot access PropertyType for property " + "'" + propertyName + "' " 1251 + " on an instance of '" + element.getClass().getName() + "'.", e); 1252 } 1253 1254 return propClass; 1255 } 1256 1257 1258 1259 protected String maskValueIfNecessary(Class businessObjectClass, String propertyName, String propertyValue, BusinessObjectRestrictions businessObjectRestrictions) { 1260 String maskedPropertyValue = propertyValue; 1261 if (businessObjectRestrictions != null) { 1262 FieldRestriction fieldRestriction = businessObjectRestrictions.getFieldRestriction(propertyName); 1263 if (fieldRestriction != null && (fieldRestriction.isMasked() || fieldRestriction.isPartiallyMasked())) { 1264 maskedPropertyValue = fieldRestriction.getMaskFormatter().maskValue(propertyValue); 1265 } 1266 } 1267 return maskedPropertyValue; 1268 } 1269 1270 1271 protected void setReferencesToRefresh(String referencesToRefresh) { 1272 this.referencesToRefresh = referencesToRefresh; 1273 } 1274 1275 public String getReferencesToRefresh() { 1276 return referencesToRefresh; 1277 } 1278 1279 protected SequenceAccessorService getSequenceAccessorService() { 1280 return sequenceAccessorService != null ? sequenceAccessorService : KRADServiceLocator 1281 .getSequenceAccessorService(); 1282 } 1283 1284 public void setSequenceAccessorService(SequenceAccessorService sequenceAccessorService) { 1285 this.sequenceAccessorService = sequenceAccessorService; 1286 } 1287 1288 public BusinessObjectService getBusinessObjectService() { 1289 return businessObjectService != null ? businessObjectService : KRADServiceLocator.getBusinessObjectService(); 1290 } 1291 1292 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 1293 this.businessObjectService = businessObjectService; 1294 } 1295 1296 protected LookupResultsService getLookupResultsService() { 1297 return lookupResultsService != null ? lookupResultsService : KNSServiceLocator.getLookupResultsService(); 1298 } 1299 1300 public void setLookupResultsService(LookupResultsService lookupResultsService) { 1301 this.lookupResultsService = lookupResultsService; 1302 } 1303 1304 /** 1305 * @return false always, subclasses should override to do something smarter 1306 * @see LookupableHelperService#isSearchUsingOnlyPrimaryKeyValues() 1307 */ 1308 public boolean isSearchUsingOnlyPrimaryKeyValues() { 1309 // by default, this implementation returns false, as lookups may not necessarily support this 1310 return false; 1311 } 1312 1313 /** 1314 * Returns "N/A" 1315 * 1316 * @return "N/A" 1317 * @see LookupableHelperService#getPrimaryKeyFieldLabels() 1318 */ 1319 public String getPrimaryKeyFieldLabels() { 1320 return KRADConstants.NOT_AVAILABLE_STRING; 1321 } 1322 1323 /** 1324 * @see LookupableHelperService#isResultReturnable(org.kuali.core.bo.BusinessObject) 1325 */ 1326 public boolean isResultReturnable(BusinessObject object) { 1327 return true; 1328 } 1329 1330 /** 1331 * This method does the logic for the clear action. 1332 * 1333 * @see LookupableHelperService#performClear() 1334 */ 1335 public void performClear(LookupForm lookupForm) { 1336 for (Iterator iter = this.getRows().iterator(); iter.hasNext();) { 1337 Row row = (Row) iter.next(); 1338 for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) { 1339 Field field = (Field) iterator.next(); 1340 if (field.isSecure()) { 1341 field.setSecure(false); 1342 field.setDisplayMaskValue(null); 1343 field.setEncryptedValue(null); 1344 } 1345 1346 if (!field.getFieldType().equals(Field.RADIO)) { 1347 field.setPropertyValue(field.getDefaultValue()); 1348 if (field.getFieldType().equals(Field.MULTISELECT)) { 1349 field.setPropertyValues(null); 1350 } 1351 } 1352 } 1353 } 1354 } 1355 1356 /** 1357 * @see LookupableHelperService#shouldDisplayHeaderNonMaintActions() 1358 */ 1359 public boolean shouldDisplayHeaderNonMaintActions() { 1360 return true; 1361 } 1362 1363 /** 1364 * @see LookupableHelperService#shouldDisplayLookupCriteria() 1365 */ 1366 public boolean shouldDisplayLookupCriteria() { 1367 return true; 1368 } 1369 1370 /** 1371 * @see LookupableHelperService#getSupplementalMenuBar() 1372 */ 1373 public String getSupplementalMenuBar() { 1374 return new String(); 1375 } 1376 1377 /** 1378 * @see LookupableHelperService#getTitle() 1379 */ 1380 public String getTitle() { 1381 return getBusinessObjectDictionaryService().getLookupTitle(getBusinessObjectClass()); 1382 } 1383 1384 /** 1385 * @see LookupableHelperService#performCustomAction(boolean) 1386 */ 1387 public boolean performCustomAction(boolean ignoreErrors) { 1388 return false; 1389 } 1390 1391 /** 1392 * @see Lookupable#getExtraField() 1393 */ 1394 public Field getExtraField() { 1395 return null; 1396 } 1397 1398 public boolean allowsNewOrCopyAction(String documentTypeName) { 1399 throw new UnsupportedOperationException("Function not supported."); 1400 } 1401 1402 /** 1403 * Functional requirements state that users are able to perform searches using criteria values that they are not allowed to view. 1404 * 1405 * @see LookupableHelperService#applyFieldAuthorizationsFromNestedLookups(org.kuali.rice.krad.web.ui.Field) 1406 */ 1407 public void applyFieldAuthorizationsFromNestedLookups(Field field) { 1408 BusinessObjectAuthorizationService boAuthzService = this.getBusinessObjectAuthorizationService(); 1409 if (!Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) { 1410 if (field.getPropertyValue() != null && field.getPropertyValue().endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) { 1411 if (boAuthzService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, field.getPropertyName())) { 1412 AttributeSecurity attributeSecurity = getDataDictionaryService().getAttributeSecurity(businessObjectClass.getName(), field.getPropertyName()); 1413 Person user = GlobalVariables.getUserSession().getPerson(); 1414 String decryptedValue = ""; 1415 try { 1416 String cipherText = StringUtils.removeEnd(field.getPropertyValue(), EncryptionService.ENCRYPTION_POST_PREFIX); 1417 if(CoreApiServiceLocator.getEncryptionService().isEnabled()) { 1418 decryptedValue = getEncryptionService().decrypt(cipherText); 1419 } 1420 } catch (GeneralSecurityException e) { 1421 throw new RuntimeException("Error decrypting value for business object " + businessObjectClass + " attribute " + field.getPropertyName(), e); 1422 } 1423 if (attributeSecurity.isMask() && !boAuthzService.canFullyUnmaskField(user, 1424 businessObjectClass, field.getPropertyName(), null)) { 1425 MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter(); 1426 field.setEncryptedValue(field.getPropertyValue()); 1427 field.setDisplayMaskValue(maskFormatter.maskValue(decryptedValue)); 1428 field.setSecure(true); 1429 } else if (attributeSecurity.isPartialMask() && !boAuthzService.canPartiallyUnmaskField(user, 1430 businessObjectClass, field.getPropertyName(), null)) { 1431 MaskFormatter maskFormatter = attributeSecurity.getPartialMaskFormatter(); 1432 field.setEncryptedValue(field.getPropertyValue()); 1433 field.setDisplayMaskValue(maskFormatter.maskValue(decryptedValue)); 1434 field.setSecure(true); 1435 } else { 1436 field.setPropertyValue(org.kuali.rice.krad.lookup.LookupUtils 1437 .forceUppercase(businessObjectClass, field.getPropertyName(), decryptedValue)); 1438 } 1439 } else { 1440 throw new RuntimeException("Field " + field.getPersonNameAttributeName() + " was encrypted on " + businessObjectClass.getName() + 1441 " lookup was encrypted when it should not have been encrypted according to the data dictionary."); 1442 } 1443 } 1444 } else { 1445 if (boAuthzService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, field.getPropertyName())) { 1446 LOG.error("Cannot handle multiple value field types that have field authorizations, please implement custom lookupable helper service"); 1447 throw new RuntimeException("Cannot handle multiple value field types that have field authorizations."); 1448 } 1449 } 1450 } 1451 1452 /** 1453 * Calls methods that can be overridden by child lookupables to implement conditional logic for setting 1454 * read-only, required, and hidden attributes. Called in the last part of the lookup lifecycle so the 1455 * fields values that will be sent will be correctly reflected in the rows (like after a clear). 1456 * 1457 * @see #getConditionallyReadOnlyPropertyNames() 1458 * @see #getConditionallyRequiredPropertyNames() 1459 * @see #getConditionallyHiddenPropertyNames() 1460 * @see LookupableHelperService#applyConditionalLogicForFieldDisplay() 1461 */ 1462 public void applyConditionalLogicForFieldDisplay() { 1463 Set<String> readOnlyFields = getConditionallyReadOnlyPropertyNames(); 1464 Set<String> requiredFields = getConditionallyRequiredPropertyNames(); 1465 Set<String> hiddenFields = getConditionallyHiddenPropertyNames(); 1466 1467 for (Iterator iter = this.getRows().iterator(); iter.hasNext();) { 1468 Row row = (Row) iter.next(); 1469 for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) { 1470 Field field = (Field) iterator.next(); 1471 1472 if (readOnlyFields != null && readOnlyFields.contains(field.getPropertyName())) { 1473 field.setReadOnly(true); 1474 } 1475 1476 if (requiredFields != null && requiredFields.contains(field.getPropertyName())) { 1477 field.setFieldRequired(true); 1478 } 1479 1480 if (hiddenFields != null && hiddenFields.contains(field.getPropertyName())) { 1481 field.setFieldType(Field.HIDDEN); 1482 } 1483 } 1484 } 1485 } 1486 1487 /** 1488 * @return Set of property names that should be set as read only based on the current search 1489 * contents, note request parms containing search field values can be retrieved with 1490 * {@link #getParameters()} 1491 */ 1492 public Set<String> getConditionallyReadOnlyPropertyNames() { 1493 return new HashSet<String>(); 1494 } 1495 1496 /** 1497 * @return Set of property names that should be set as required based on the current search 1498 * contents, note request parms containing search field values can be retrieved with 1499 * {@link #getParameters()} 1500 */ 1501 public Set<String> getConditionallyRequiredPropertyNames() { 1502 return new HashSet<String>(); 1503 } 1504 1505 /** 1506 * @return Set of property names that should be set as hidden based on the current search 1507 * contents, note request parms containing search field values can be retrieved with 1508 * {@link #getParameters()} 1509 */ 1510 public Set<String> getConditionallyHiddenPropertyNames() { 1511 return new HashSet<String>(); 1512 } 1513 1514 /** 1515 * Helper method to get the value for a property out of the row-field graph. If property is 1516 * multi-value then the values will be joined by a semi-colon. 1517 * 1518 * @param propertyName - name of property to retrieve value for 1519 * @return current property value as a String 1520 */ 1521 protected String getCurrentSearchFieldValue(String propertyName) { 1522 String currentValue = null; 1523 1524 boolean fieldFound = false; 1525 for (Iterator iter = this.getRows().iterator(); iter.hasNext();) { 1526 Row row = (Row) iter.next(); 1527 for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) { 1528 Field field = (Field) iterator.next(); 1529 1530 if (StringUtils.equalsIgnoreCase(propertyName, field.getPropertyName())) { 1531 if (Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) { 1532 currentValue = StringUtils.join(field.getPropertyValues(), ";"); 1533 } else { 1534 currentValue = field.getPropertyValue(); 1535 } 1536 fieldFound = true; 1537 } 1538 1539 if (fieldFound) { 1540 break; 1541 } 1542 } 1543 1544 if (fieldFound) { 1545 break; 1546 } 1547 } 1548 1549 return currentValue; 1550 } 1551}