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