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