001 /** 002 * Copyright 2005-2012 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 fieldVal = getEncryptionService().encrypt(fieldVal) + EncryptionService.ENCRYPTION_POST_PREFIX; 527 } catch (GeneralSecurityException e) { 528 LOG.error("Exception while trying to encrypted value for inquiry framework.", e); 529 throw new RuntimeException(e); 530 } 531 532 } 533 534 parameters.put(fieldNm, fieldVal.toString()); 535 } 536 return parameters; 537 } 538 539 /** 540 * This method generates and returns title text for action urls. 541 * Child classes can override this if they want to generate the title text differently. 542 * For example, refer to BatchJobStatusLookupableHelperServiceImpl 543 * 544 * @param businessObject 545 * @param displayText 546 * @param pkNames 547 * @return 548 */ 549 protected String getActionUrlTitleText(BusinessObject businessObject, String displayText, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) { 550 String prependTitleText = displayText + " " 551 + getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(getBusinessObjectClass().getName()).getObjectLabel() 552 + " " 553 + this.getKualiConfigurationService().getPropertyValueAsString(TITLE_ACTION_URL_PREPENDTEXT_PROPERTY); 554 return HtmlData.getTitleText(prependTitleText, businessObject, pkNames, businessObjectRestrictions); 555 } 556 557 /** 558 * Returns the maintenance document type associated with the business object class or null if one does not 559 * exist. 560 * 561 * @return String representing the maintenance document type name 562 */ 563 protected String getMaintenanceDocumentTypeName() { 564 MaintenanceDocumentDictionaryService dd = getMaintenanceDocumentDictionaryService(); 565 String maintDocTypeName = dd.getDocumentTypeName(getBusinessObjectClass()); 566 return maintDocTypeName; 567 } 568 569 /** 570 * Gets the readOnlyFieldsList attribute. 571 * 572 * @return Returns the readOnlyFieldsList. 573 */ 574 public List<String> getReadOnlyFieldsList() { 575 return readOnlyFieldsList; 576 } 577 578 579 /** 580 * Sets the readOnlyFieldsList attribute value. 581 * 582 * @param readOnlyFieldsList The readOnlyFieldsList to set. 583 */ 584 public void setReadOnlyFieldsList(List<String> readOnlyFieldsList) { 585 this.readOnlyFieldsList = readOnlyFieldsList; 586 } 587 588 protected HashMap<String, Boolean> noLookupResultFieldInquiryCache = new HashMap<String, Boolean>(); 589 protected HashMap<Class, Class> inquirableClassCache = new HashMap<Class, Class>(); 590 protected HashMap<String, Boolean> forceLookupResultFieldInquiryCache = new HashMap<String, Boolean>(); 591 592 /** 593 * Returns the inquiry url for a field if one exist. 594 * 595 * @param bo the business object instance to build the urls for 596 * @param propertyName the property which links to an inquirable 597 * @return String url to inquiry 598 */ 599 public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) { 600 HtmlData inquiryUrl = new HtmlData.AnchorHtmlData(); 601 602 String cacheKey = bo.getClass().getName() + "." + propertyName; 603 Boolean noLookupResultFieldInquiry = noLookupResultFieldInquiryCache.get(cacheKey); 604 if (noLookupResultFieldInquiry == null) { 605 noLookupResultFieldInquiry = getBusinessObjectDictionaryService().noLookupResultFieldInquiry(bo.getClass(), propertyName); 606 if (noLookupResultFieldInquiry == null) { 607 noLookupResultFieldInquiry = Boolean.TRUE; 608 } 609 noLookupResultFieldInquiryCache.put(cacheKey, noLookupResultFieldInquiry); 610 } 611 if (!noLookupResultFieldInquiry) { 612 613 Class<Inquirable> inquirableClass = inquirableClassCache.get(bo.getClass()); 614 if (!inquirableClassCache.containsKey(bo.getClass())) { 615 inquirableClass = getBusinessObjectDictionaryService().getInquirableClass(bo.getClass()); 616 inquirableClassCache.put(bo.getClass(), inquirableClass); 617 } 618 Inquirable inq = null; 619 try { 620 if (inquirableClass != null) { 621 inq = inquirableClass.newInstance(); 622 } else { 623 inq = getKualiInquirable(); 624 if (LOG.isDebugEnabled()) { 625 LOG.debug("Default Inquirable Class: " + inq.getClass()); 626 } 627 } 628 Boolean forceLookupResultFieldInquiry = forceLookupResultFieldInquiryCache.get(cacheKey); 629 if (forceLookupResultFieldInquiry == null) { 630 forceLookupResultFieldInquiry = getBusinessObjectDictionaryService().forceLookupResultFieldInquiry(bo.getClass(), propertyName); 631 if (forceLookupResultFieldInquiry == null) { 632 forceLookupResultFieldInquiry = Boolean.FALSE; 633 } 634 forceLookupResultFieldInquiryCache.put(cacheKey, forceLookupResultFieldInquiry); 635 } 636 inquiryUrl = inq.getInquiryUrl(bo, propertyName, forceLookupResultFieldInquiry); 637 } catch (Exception ex) { 638 LOG.error("unable to create inquirable to get inquiry URL", ex); 639 } 640 } 641 642 return inquiryUrl; 643 } 644 645 protected CopiedObject<ArrayList<Column>> resultColumns = null; 646 647 /** 648 * Constructs the list of columns for the search results. All properties for the column objects come from the DataDictionary. 649 */ 650 public List<Column> getColumns() { 651 if (resultColumns == null) { 652 ArrayList<Column> columns = new ArrayList<Column>(); 653 for (String attributeName : getBusinessObjectDictionaryService().getLookupResultFieldNames(getBusinessObjectClass())) { 654 Column column = new Column(); 655 column.setPropertyName(attributeName); 656 String columnTitle = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName); 657 Boolean useShortLabel = getBusinessObjectDictionaryService().getLookupResultFieldUseShortLabel(businessObjectClass, attributeName); 658 if (useShortLabel != null && useShortLabel) { 659 columnTitle = getDataDictionaryService().getAttributeShortLabel(getBusinessObjectClass(), attributeName); 660 } 661 if (StringUtils.isBlank(columnTitle)) { 662 columnTitle = getDataDictionaryService().getCollectionLabel(getBusinessObjectClass(), attributeName); 663 } 664 column.setColumnTitle(columnTitle); 665 column.setMaxLength(getColumnMaxLength(attributeName)); 666 667 if (!businessObjectClass.isInterface()) { 668 try { 669 column.setFormatter(ObjectUtils.getFormatterWithDataDictionary(getBusinessObjectClass() 670 .newInstance(), attributeName)); 671 } catch (InstantiationException e) { 672 LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e); 673 // just swallow exception and leave formatter blank 674 } catch (IllegalAccessException e) { 675 LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e); 676 // just swallow exception and leave formatter blank 677 } 678 } 679 680 String alternateDisplayPropertyName = getBusinessObjectDictionaryService() 681 .getLookupFieldAlternateDisplayAttributeName(getBusinessObjectClass(), attributeName); 682 if (StringUtils.isNotBlank(alternateDisplayPropertyName)) { 683 column.setAlternateDisplayPropertyName(alternateDisplayPropertyName); 684 } 685 686 String additionalDisplayPropertyName = getBusinessObjectDictionaryService() 687 .getLookupFieldAdditionalDisplayAttributeName(getBusinessObjectClass(), attributeName); 688 if (StringUtils.isNotBlank(additionalDisplayPropertyName)) { 689 column.setAdditionalDisplayPropertyName(additionalDisplayPropertyName); 690 } else { 691 boolean translateCodes = getBusinessObjectDictionaryService().tranlateCodesInLookup(getBusinessObjectClass()); 692 if (translateCodes) { 693 FieldUtils.setAdditionalDisplayPropertyForCodes(getBusinessObjectClass(), attributeName, column); 694 } 695 } 696 697 column.setTotal(getBusinessObjectDictionaryService().getLookupResultFieldTotal(getBusinessObjectClass(), attributeName)); 698 699 columns.add(column); 700 } 701 resultColumns = ObjectUtils.deepCopyForCaching(columns); 702 return columns; 703 } 704 return resultColumns.getContent(); 705 } 706 707 protected static Integer RESULTS_DEFAULT_MAX_COLUMN_LENGTH = null; 708 709 protected int getColumnMaxLength(String attributeName) { 710 Integer fieldDefinedMaxLength = getBusinessObjectDictionaryService().getLookupResultFieldMaxLength(getBusinessObjectClass(), attributeName); 711 if (fieldDefinedMaxLength == null) { 712 if (RESULTS_DEFAULT_MAX_COLUMN_LENGTH == null) { 713 try { 714 RESULTS_DEFAULT_MAX_COLUMN_LENGTH = Integer.valueOf(getParameterService().getParameterValueAsString( 715 KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE, KRADConstants.RESULTS_DEFAULT_MAX_COLUMN_LENGTH)); 716 } catch (NumberFormatException ex) { 717 LOG.error("Lookup field max length parameter not found and unable to parse default set in system parameters (RESULTS_DEFAULT_MAX_COLUMN_LENGTH)."); 718 } 719 } 720 return RESULTS_DEFAULT_MAX_COLUMN_LENGTH.intValue(); 721 } 722 return fieldDefinedMaxLength.intValue(); 723 } 724 725 /** 726 * @return Returns the backLocation. 727 */ 728 public String getBackLocation() { 729 return backLocation; 730 } 731 732 /** 733 * @param backLocation The backLocation to set. 734 */ 735 public void setBackLocation(String backLocation) { 736 this.backLocation = backLocation; 737 } 738 739 /** 740 * @see LookupableHelperService#getReturnLocation() 741 */ 742 public String getReturnLocation() { 743 return backLocation; 744 } 745 746 /** 747 * This method is for lookupable implementations 748 * 749 * @see LookupableHelperService#getReturnUrl(org.kuali.rice.krad.bo.BusinessObject, java.util.Map, java.lang.String, java.util.List) 750 */ 751 final public HtmlData getReturnUrl(BusinessObject businessObject, Map fieldConversions, String lookupImpl, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { 752 String href = getReturnHref(businessObject, fieldConversions, lookupImpl, returnKeys); 753 String returnUrlAnchorLabel = 754 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY); 755 HtmlData.AnchorHtmlData anchor = new HtmlData.AnchorHtmlData(href, HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions)); 756 anchor.setDisplayText(returnUrlAnchorLabel); 757 return anchor; 758 } 759 760 /** 761 * This method is for lookupable implementations 762 * 763 * @param businessObject 764 * @param fieldConversions 765 * @param lookupImpl 766 * @param returnKeys 767 * @return 768 */ 769 final protected String getReturnHref(BusinessObject businessObject, Map fieldConversions, String lookupImpl, List returnKeys) { 770 if (StringUtils.isNotBlank(backLocation)) { 771 return UrlFactory.parameterizeUrl(backLocation, getParameters( 772 businessObject, fieldConversions, lookupImpl, returnKeys)); 773 } 774 return ""; 775 } 776 777 /** 778 * @see LookupableHelperService#getReturnUrl(org.kuali.core.bo.BusinessObject, java.util.Map, java.lang.String) 779 */ 780 public HtmlData getReturnUrl(BusinessObject businessObject, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { 781 Properties parameters = getParameters( 782 businessObject, lookupForm.getFieldConversions(), lookupForm.getLookupableImplServiceName(), returnKeys); 783 if (StringUtils.isEmpty(lookupForm.getHtmlDataType()) || HtmlData.ANCHOR_HTML_DATA_TYPE.equals(lookupForm.getHtmlDataType())) 784 return getReturnAnchorHtmlData(businessObject, parameters, lookupForm, returnKeys, businessObjectRestrictions); 785 else 786 return getReturnInputHtmlData(businessObject, parameters, lookupForm, returnKeys, businessObjectRestrictions); 787 } 788 789 protected HtmlData getReturnInputHtmlData(BusinessObject businessObject, Properties parameters, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { 790 String returnUrlAnchorLabel = 791 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY); 792 String name = KRADConstants.MULTIPLE_VALUE_LOOKUP_SELECTED_OBJ_ID_PARAM_PREFIX + lookupForm.getLookupObjectId(); 793 HtmlData.InputHtmlData input = new HtmlData.InputHtmlData(name, HtmlData.InputHtmlData.CHECKBOX_INPUT_TYPE); 794 input.setTitle(HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions)); 795 if (((MultipleValueLookupForm) lookupForm).getCompositeObjectIdMap() == null || 796 ((MultipleValueLookupForm) lookupForm).getCompositeObjectIdMap().get( 797 ((PersistableBusinessObject) businessObject).getObjectId()) == null) { 798 input.setChecked(""); 799 } else { 800 input.setChecked(HtmlData.InputHtmlData.CHECKBOX_CHECKED_VALUE); 801 } 802 input.setValue(HtmlData.InputHtmlData.CHECKBOX_CHECKED_VALUE); 803 return input; 804 } 805 806 protected HtmlData getReturnAnchorHtmlData(BusinessObject businessObject, Properties parameters, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { 807 String returnUrlAnchorLabel = 808 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY); 809 HtmlData.AnchorHtmlData anchor = new HtmlData.AnchorHtmlData( 810 getReturnHref(parameters, lookupForm, returnKeys), 811 HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions)); 812 anchor.setDisplayText(returnUrlAnchorLabel); 813 return anchor; 814 } 815 816 protected String getReturnHref(Properties parameters, LookupForm lookupForm, List returnKeys) { 817 if (StringUtils.isNotBlank(backLocation)) { 818 String href = UrlFactory.parameterizeUrl(backLocation, parameters); 819 return addToReturnHref(href, lookupForm); 820 } 821 return ""; 822 } 823 824 protected String addToReturnHref(String href, LookupForm lookupForm) { 825 String lookupAnchor = ""; 826 if (StringUtils.isNotEmpty(lookupForm.getAnchor())) { 827 lookupAnchor = lookupForm.getAnchor(); 828 } 829 href += "&anchor=" + lookupAnchor + "&docNum=" + (StringUtils.isEmpty(getDocNum()) ? "" : getDocNum()); 830 return href; 831 } 832 833 protected Properties getParameters(BusinessObject bo, Map<String, String> fieldConversions, String lookupImpl, List returnKeys) { 834 Properties parameters = new Properties(); 835 parameters.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL); 836 if (getDocFormKey() != null) { 837 parameters.put(KRADConstants.DOC_FORM_KEY, getDocFormKey()); 838 } 839 if (lookupImpl != null) { 840 parameters.put(KRADConstants.REFRESH_CALLER, lookupImpl); 841 } 842 if (getDocNum() != null) { 843 parameters.put(KRADConstants.DOC_NUM, getDocNum()); 844 } 845 846 if (getReferencesToRefresh() != null) { 847 parameters.put(KRADConstants.REFERENCES_TO_REFRESH, getReferencesToRefresh()); 848 } 849 850 Iterator returnKeysIt = getReturnKeys().iterator(); 851 while (returnKeysIt.hasNext()) { 852 String fieldNm = (String) returnKeysIt.next(); 853 854 Object fieldVal = ObjectUtils.getPropertyValue(bo, fieldNm); 855 if (fieldVal == null) { 856 fieldVal = KRADConstants.EMPTY_STRING; 857 } 858 859 if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, fieldNm)) { 860 try { 861 fieldVal = getEncryptionService().encrypt(fieldVal) + EncryptionService.ENCRYPTION_POST_PREFIX; 862 } catch (GeneralSecurityException e) { 863 LOG.error("Exception while trying to encrypted value for inquiry framework.", e); 864 throw new RuntimeException(e); 865 } 866 867 } 868 869 //need to format date in url 870 if (fieldVal instanceof Date) { 871 DateFormatter dateFormatter = new DateFormatter(); 872 fieldVal = dateFormatter.format(fieldVal); 873 } 874 875 if (fieldConversions.containsKey(fieldNm)) { 876 fieldNm = (String) fieldConversions.get(fieldNm); 877 } 878 879 parameters.put(fieldNm, fieldVal.toString()); 880 } 881 882 return parameters; 883 } 884 885 /** 886 * @return a List of the names of fields which are marked in data dictionary as return fields. 887 */ 888 public List<String> getReturnKeys() { 889 List<String> returnKeys; 890 if (fieldConversions != null && !fieldConversions.isEmpty()) { 891 returnKeys = new ArrayList<String>(fieldConversions.keySet()); 892 } else { 893 returnKeys = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass()); 894 } 895 896 return returnKeys; 897 } 898 899 /** 900 * Gets the docFormKey attribute. 901 * 902 * @return Returns the docFormKey. 903 */ 904 public String getDocFormKey() { 905 return docFormKey; 906 } 907 908 /** 909 * Sets the docFormKey attribute value. 910 * 911 * @param docFormKey The docFormKey to set. 912 */ 913 public void setDocFormKey(String docFormKey) { 914 this.docFormKey = docFormKey; 915 } 916 917 /** 918 * @see LookupableHelperService#setFieldConversions(java.util.Map) 919 */ 920 public void setFieldConversions(Map fieldConversions) { 921 this.fieldConversions = fieldConversions; 922 } 923 924 /** 925 * Gets the lookupService attribute. 926 * 927 * @return Returns the lookupService. 928 */ 929 protected LookupService getLookupService() { 930 return lookupService != null ? lookupService : KRADServiceLocatorWeb.getLookupService(); 931 } 932 933 /** 934 * Sets the lookupService attribute value. 935 * 936 * @param lookupService The lookupService to set. 937 */ 938 public void setLookupService(LookupService lookupService) { 939 this.lookupService = lookupService; 940 } 941 942 /** 943 * Uses the DD to determine which is the default sort order. 944 * 945 * @return property names that will be used to sort on by default 946 */ 947 public List<String> getDefaultSortColumns() { 948 return getBusinessObjectDictionaryService().getLookupDefaultSortFieldNames(getBusinessObjectClass()); 949 } 950 951 /** 952 * Checks that any required search fields have value. 953 * 954 * @see LookupableHelperService#validateSearchParameters(java.util.Map) 955 */ 956 public void validateSearchParameters(Map<String, String> fieldValues) { 957 List<String> lookupFieldAttributeList = null; 958 if (getBusinessObjectMetaDataService().isLookupable(getBusinessObjectClass())) { 959 lookupFieldAttributeList = getBusinessObjectMetaDataService().getLookupableFieldNames(getBusinessObjectClass()); 960 } 961 if (lookupFieldAttributeList == null) { 962 throw new RuntimeException("Lookup not defined for business object " + getBusinessObjectClass()); 963 } 964 for (Iterator iter = lookupFieldAttributeList.iterator(); iter.hasNext();) { 965 String attributeName = (String) iter.next(); 966 if (fieldValues.containsKey(attributeName)) { 967 // get label of attribute for message 968 String attributeLabel = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName); 969 970 String attributeValue = (String) fieldValues.get(attributeName); 971 972 // check for required if field does not have value 973 if (StringUtils.isBlank(attributeValue)) { 974 if ((getBusinessObjectDictionaryService().getLookupAttributeRequired(getBusinessObjectClass(), attributeName)).booleanValue()) { 975 GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_REQUIRED, attributeLabel); 976 } 977 } 978 validateSearchParameterWildcardAndOperators(attributeName, attributeValue); 979 } 980 } 981 982 if (GlobalVariables.getMessageMap().hasErrors()) { 983 throw new ValidationException("errors in search criteria"); 984 } 985 } 986 987 protected void validateSearchParameterWildcardAndOperators(String attributeName, String attributeValue) { 988 if (StringUtils.isBlank(attributeValue)) 989 return; 990 991 // make sure a wildcard/operator is in the value 992 boolean found = false; 993 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) { 994 String queryCharacter = op.op(); 995 996 if (attributeValue.contains(queryCharacter)) { 997 found = true; 998 } 999 } 1000 if (!found) 1001 return; 1002 1003 String attributeLabel = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName); 1004 if (getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, attributeName)) { 1005 BusinessObject example = null; 1006 try { 1007 example = (BusinessObject) businessObjectClass.newInstance(); 1008 } catch (Exception e) { 1009 LOG.error("Exception caught instantiating " + businessObjectClass.getName(), e); 1010 throw new RuntimeException("Cannot instantiate " + businessObjectClass.getName(), e); 1011 } 1012 1013 Class propertyType = ObjectUtils.getPropertyType(example, attributeName, getPersistenceStructureService()); 1014 if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) || TypeUtils.isTemporalClass(propertyType)) { 1015 GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_WILDCARDS_AND_OPERATORS_NOT_ALLOWED_ON_FIELD, attributeLabel); 1016 } 1017 if (TypeUtils.isStringClass(propertyType)) { 1018 GlobalVariables.getMessageMap().putInfo(attributeName, RiceKeyConstants.INFO_WILDCARDS_AND_OPERATORS_TREATED_LITERALLY, attributeLabel); 1019 } 1020 } else { 1021 if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, attributeName)) { 1022 if (!attributeValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) { 1023 // encrypted values usually come from the DB, so we don't need to filter for wildcards 1024 1025 // wildcards are not allowed on restricted fields, because they are typically encrypted, and wildcard searches cannot be performed without 1026 // decrypting every row, which is currently not supported by KNS 1027 1028 GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_SECURE_FIELD, attributeLabel); 1029 } 1030 } 1031 } 1032 } 1033 1034 /** 1035 * Constructs the list of rows for the search fields. All properties for the field objects come 1036 * from the DataDictionary. To be called by setBusinessObject 1037 */ 1038 protected void setRows() { 1039 List<String> lookupFieldAttributeList = null; 1040 if (getBusinessObjectMetaDataService().isLookupable(getBusinessObjectClass())) { 1041 lookupFieldAttributeList = getBusinessObjectMetaDataService().getLookupableFieldNames( 1042 getBusinessObjectClass()); 1043 } 1044 if (lookupFieldAttributeList == null) { 1045 throw new RuntimeException("Lookup not defined for business object " + getBusinessObjectClass()); 1046 } 1047 1048 // construct field object for each search attribute 1049 List fields = new ArrayList(); 1050 try { 1051 fields = FieldUtils.createAndPopulateFieldsForLookup(lookupFieldAttributeList, getReadOnlyFieldsList(), 1052 getBusinessObjectClass()); 1053 } catch (InstantiationException e) { 1054 throw new RuntimeException("Unable to create instance of business object class" + e.getMessage()); 1055 } catch (IllegalAccessException e) { 1056 throw new RuntimeException("Unable to create instance of business object class" + e.getMessage()); 1057 } 1058 1059 int numCols = getBusinessObjectDictionaryService().getLookupNumberOfColumns(this.getBusinessObjectClass()); 1060 1061 this.rows = FieldUtils.wrapFields(fields, numCols); 1062 } 1063 1064 public List<Row> getRows() { 1065 return rows; 1066 } 1067 1068 public abstract List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues); 1069 1070 /** 1071 * This implementation of this method throws an UnsupportedOperationException, since not every implementation 1072 * may actually want to use this operation. Subclasses desiring other behaviors 1073 * will need to override this. 1074 * 1075 * @see LookupableHelperService#getSearchResultsUnbounded(java.util.Map) 1076 */ 1077 public List<? extends BusinessObject> getSearchResultsUnbounded(Map<String, String> fieldValues) { 1078 throw new UnsupportedOperationException("Lookupable helper services do not always support getSearchResultsUnbounded"); 1079 } 1080 1081 /** 1082 * Performs the lookup and returns a collection of lookup items 1083 * 1084 * @param lookupForm 1085 * @param resultTable 1086 * @param bounded 1087 * @return 1088 */ 1089 public Collection<? extends BusinessObject> performLookup(LookupForm lookupForm, Collection<ResultRow> resultTable, boolean bounded) { 1090 Map lookupFormFields = lookupForm.getFieldsForLookup(); 1091 1092 setBackLocation((String) lookupFormFields.get(KRADConstants.BACK_LOCATION)); 1093 setDocFormKey((String) lookupFormFields.get(KRADConstants.DOC_FORM_KEY)); 1094 Collection<? extends BusinessObject> displayList; 1095 1096 LookupUtils.preProcessRangeFields(lookupFormFields); 1097 1098 // call search method to get results 1099 if (bounded) { 1100 displayList = getSearchResults(lookupFormFields); 1101 } else { 1102 displayList = getSearchResultsUnbounded(lookupFormFields); 1103 } 1104 1105 boolean hasReturnableRow = false; 1106 1107 List<String> returnKeys = getReturnKeys(); 1108 List<String> pkNames = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass()); 1109 Person user = GlobalVariables.getUserSession().getPerson(); 1110 1111 // iterate through result list and wrap rows with return url and action 1112 // urls 1113 for (BusinessObject element : displayList) { 1114 BusinessObject baseElement = element; 1115 //if ebo, then use base BO to get lookupId and find restrictions 1116 //we don't need to do this anymore as the BO is required to implement the EBO interface as of this time 1117 //if this needs reimplemented in the future, one should consider what happens/needs to happen 1118 //with the base BO fields (OBJ ID in particular) as they are all null/empty on new instantiation 1119 //which will fail if we try to depend on any values within it. 1120 //KULRICE-7223 1121 // if (ExternalizableBusinessObjectUtils.isExternalizableBusinessObject(element.getClass())) { 1122 // try { 1123 // baseElement = (BusinessObject)this.getBusinessObjectClass().newInstance(); 1124 // } catch (InstantiationException e) { 1125 // e.printStackTrace(); 1126 // } catch (IllegalAccessException e) { 1127 // e.printStackTrace(); 1128 // } 1129 // } 1130 1131 final String lookupId = KNSServiceLocator.getLookupResultsService().getLookupId(baseElement); 1132 if (lookupId != null) { 1133 lookupForm.setLookupObjectId(lookupId); 1134 } 1135 1136 BusinessObjectRestrictions businessObjectRestrictions = getBusinessObjectAuthorizationService() 1137 .getLookupResultRestrictions(element, user); 1138 1139 HtmlData returnUrl = getReturnUrl(element, lookupForm, returnKeys, businessObjectRestrictions); 1140 String actionUrls = getActionUrls(element, pkNames, businessObjectRestrictions); 1141 // Fix for JIRA - KFSMI-2417 1142 if ("".equals(actionUrls)) { 1143 actionUrls = ACTION_URLS_EMPTY; 1144 } 1145 1146 List<Column> columns = getColumns(); 1147 for (Iterator iterator = columns.iterator(); iterator.hasNext();) { 1148 Column col = (Column) iterator.next(); 1149 1150 String propValue = ObjectUtils.getFormattedPropertyValue(element, col.getPropertyName(), col.getFormatter()); 1151 Class propClass = getPropertyClass(element, col.getPropertyName()); 1152 1153 col.setComparator(CellComparatorHelper.getAppropriateComparatorForPropertyClass(propClass)); 1154 col.setValueComparator(CellComparatorHelper.getAppropriateValueComparatorForPropertyClass(propClass)); 1155 1156 String propValueBeforePotientalMasking = propValue; 1157 propValue = maskValueIfNecessary(element.getClass(), col.getPropertyName(), propValue, 1158 businessObjectRestrictions); 1159 col.setPropertyValue(propValue); 1160 1161 // if property value is masked, don't display additional or alternate properties, or allow totals 1162 if (StringUtils.equals(propValueBeforePotientalMasking, propValue)) { 1163 if (StringUtils.isNotBlank(col.getAlternateDisplayPropertyName())) { 1164 String alternatePropertyValue = ObjectUtils.getFormattedPropertyValue(element, col 1165 .getAlternateDisplayPropertyName(), null); 1166 col.setPropertyValue(alternatePropertyValue); 1167 } 1168 1169 if (StringUtils.isNotBlank(col.getAdditionalDisplayPropertyName())) { 1170 String additionalPropertyValue = ObjectUtils.getFormattedPropertyValue(element, col 1171 .getAdditionalDisplayPropertyName(), null); 1172 col.setPropertyValue(col.getPropertyValue() + " *-* " + additionalPropertyValue); 1173 } 1174 } else { 1175 col.setTotal(false); 1176 } 1177 1178 if (col.isTotal()) { 1179 Object unformattedPropValue = ObjectUtils.getPropertyValue(element, col.getPropertyName()); 1180 col.setUnformattedPropertyValue(unformattedPropValue); 1181 } 1182 1183 if (StringUtils.isNotBlank(propValue)) { 1184 col.setColumnAnchor(getInquiryUrl(element, col.getPropertyName())); 1185 } 1186 } 1187 1188 ResultRow row = new ResultRow(columns, returnUrl.constructCompleteHtmlTag(), actionUrls); 1189 row.setRowId(returnUrl.getName()); 1190 row.setReturnUrlHtmlData(returnUrl); 1191 1192 // because of concerns of the BO being cached in session on the 1193 // ResultRow, 1194 // let's only attach it when needed (currently in the case of 1195 // export) 1196 if (getBusinessObjectDictionaryService().isExportable(getBusinessObjectClass())) { 1197 row.setBusinessObject(element); 1198 } 1199 1200 if (lookupId != null) { 1201 row.setObjectId(lookupId); 1202 } 1203 1204 boolean rowReturnable = isResultReturnable(element); 1205 row.setRowReturnable(rowReturnable); 1206 if (rowReturnable) { 1207 hasReturnableRow = true; 1208 } 1209 resultTable.add(row); 1210 } 1211 1212 lookupForm.setHasReturnableRow(hasReturnableRow); 1213 1214 return displayList; 1215 } 1216 1217 /** 1218 * Gets the Class for the property in the given BusinessObject instance, if 1219 * property is not accessible then runtime exception is thrown 1220 * 1221 * @param element BusinessObject instance that contains property 1222 * @param propertyName Name of property in BusinessObject to get class for 1223 * @return Type for property as Class 1224 */ 1225 protected Class getPropertyClass(BusinessObject element, String propertyName) { 1226 Class propClass = null; 1227 1228 try { 1229 propClass = ObjectUtils.getPropertyType(element, propertyName, getPersistenceStructureService()); 1230 1231 } catch (Exception e) { 1232 throw new RuntimeException("Cannot access PropertyType for property " + "'" + propertyName + "' " 1233 + " on an instance of '" + element.getClass().getName() + "'.", e); 1234 } 1235 1236 return propClass; 1237 } 1238 1239 1240 1241 protected String maskValueIfNecessary(Class businessObjectClass, String propertyName, String propertyValue, BusinessObjectRestrictions businessObjectRestrictions) { 1242 String maskedPropertyValue = propertyValue; 1243 if (businessObjectRestrictions != null) { 1244 FieldRestriction fieldRestriction = businessObjectRestrictions.getFieldRestriction(propertyName); 1245 if (fieldRestriction != null && (fieldRestriction.isMasked() || fieldRestriction.isPartiallyMasked())) { 1246 maskedPropertyValue = fieldRestriction.getMaskFormatter().maskValue(propertyValue); 1247 } 1248 } 1249 return maskedPropertyValue; 1250 } 1251 1252 1253 protected void setReferencesToRefresh(String referencesToRefresh) { 1254 this.referencesToRefresh = referencesToRefresh; 1255 } 1256 1257 public String getReferencesToRefresh() { 1258 return referencesToRefresh; 1259 } 1260 1261 protected SequenceAccessorService getSequenceAccessorService() { 1262 return sequenceAccessorService != null ? sequenceAccessorService : KRADServiceLocator 1263 .getSequenceAccessorService(); 1264 } 1265 1266 public void setSequenceAccessorService(SequenceAccessorService sequenceAccessorService) { 1267 this.sequenceAccessorService = sequenceAccessorService; 1268 } 1269 1270 public BusinessObjectService getBusinessObjectService() { 1271 return businessObjectService != null ? businessObjectService : KRADServiceLocator.getBusinessObjectService(); 1272 } 1273 1274 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 1275 this.businessObjectService = businessObjectService; 1276 } 1277 1278 protected LookupResultsService getLookupResultsService() { 1279 return lookupResultsService != null ? lookupResultsService : KNSServiceLocator.getLookupResultsService(); 1280 } 1281 1282 public void setLookupResultsService(LookupResultsService lookupResultsService) { 1283 this.lookupResultsService = lookupResultsService; 1284 } 1285 1286 /** 1287 * @return false always, subclasses should override to do something smarter 1288 * @see LookupableHelperService#isSearchUsingOnlyPrimaryKeyValues() 1289 */ 1290 public boolean isSearchUsingOnlyPrimaryKeyValues() { 1291 // by default, this implementation returns false, as lookups may not necessarily support this 1292 return false; 1293 } 1294 1295 /** 1296 * Returns "N/A" 1297 * 1298 * @return "N/A" 1299 * @see LookupableHelperService#getPrimaryKeyFieldLabels() 1300 */ 1301 public String getPrimaryKeyFieldLabels() { 1302 return KRADConstants.NOT_AVAILABLE_STRING; 1303 } 1304 1305 /** 1306 * @see LookupableHelperService#isResultReturnable(org.kuali.core.bo.BusinessObject) 1307 */ 1308 public boolean isResultReturnable(BusinessObject object) { 1309 return true; 1310 } 1311 1312 /** 1313 * This method does the logic for the clear action. 1314 * 1315 * @see LookupableHelperService#performClear() 1316 */ 1317 public void performClear(LookupForm lookupForm) { 1318 for (Iterator iter = this.getRows().iterator(); iter.hasNext();) { 1319 Row row = (Row) iter.next(); 1320 for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) { 1321 Field field = (Field) iterator.next(); 1322 if (field.isSecure()) { 1323 field.setSecure(false); 1324 field.setDisplayMaskValue(null); 1325 field.setEncryptedValue(null); 1326 } 1327 1328 if (!field.getFieldType().equals(Field.RADIO)) { 1329 field.setPropertyValue(field.getDefaultValue()); 1330 } 1331 } 1332 } 1333 } 1334 1335 /** 1336 * @see LookupableHelperService#shouldDisplayHeaderNonMaintActions() 1337 */ 1338 public boolean shouldDisplayHeaderNonMaintActions() { 1339 return true; 1340 } 1341 1342 /** 1343 * @see LookupableHelperService#shouldDisplayLookupCriteria() 1344 */ 1345 public boolean shouldDisplayLookupCriteria() { 1346 return true; 1347 } 1348 1349 /** 1350 * @see LookupableHelperService#getSupplementalMenuBar() 1351 */ 1352 public String getSupplementalMenuBar() { 1353 return new String(); 1354 } 1355 1356 /** 1357 * @see LookupableHelperService#getTitle() 1358 */ 1359 public String getTitle() { 1360 return getBusinessObjectDictionaryService().getLookupTitle(getBusinessObjectClass()); 1361 } 1362 1363 /** 1364 * @see LookupableHelperService#performCustomAction(boolean) 1365 */ 1366 public boolean performCustomAction(boolean ignoreErrors) { 1367 return false; 1368 } 1369 1370 /** 1371 * @see Lookupable#getExtraField() 1372 */ 1373 public Field getExtraField() { 1374 return null; 1375 } 1376 1377 public boolean allowsNewOrCopyAction(String documentTypeName) { 1378 throw new UnsupportedOperationException("Function not supported."); 1379 } 1380 1381 /** 1382 * Functional requirements state that users are able to perform searches using criteria values that they are not allowed to view. 1383 * 1384 * @see LookupableHelperService#applyFieldAuthorizationsFromNestedLookups(org.kuali.rice.krad.web.ui.Field) 1385 */ 1386 public void applyFieldAuthorizationsFromNestedLookups(Field field) { 1387 BusinessObjectAuthorizationService boAuthzService = this.getBusinessObjectAuthorizationService(); 1388 if (!Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) { 1389 if (field.getPropertyValue() != null && field.getPropertyValue().endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) { 1390 if (boAuthzService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, field.getPropertyName())) { 1391 AttributeSecurity attributeSecurity = getDataDictionaryService().getAttributeSecurity(businessObjectClass.getName(), field.getPropertyName()); 1392 Person user = GlobalVariables.getUserSession().getPerson(); 1393 String decryptedValue; 1394 try { 1395 String cipherText = StringUtils.removeEnd(field.getPropertyValue(), EncryptionService.ENCRYPTION_POST_PREFIX); 1396 decryptedValue = getEncryptionService().decrypt(cipherText); 1397 } catch (GeneralSecurityException e) { 1398 throw new RuntimeException("Error decrypting value for business object " + businessObjectClass + " attribute " + field.getPropertyName(), e); 1399 } 1400 if (attributeSecurity.isMask() && !boAuthzService.canFullyUnmaskField(user, 1401 businessObjectClass, field.getPropertyName(), null)) { 1402 MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter(); 1403 field.setEncryptedValue(field.getPropertyValue()); 1404 field.setDisplayMaskValue(maskFormatter.maskValue(decryptedValue)); 1405 field.setSecure(true); 1406 } else if (attributeSecurity.isPartialMask() && !boAuthzService.canPartiallyUnmaskField(user, 1407 businessObjectClass, field.getPropertyName(), null)) { 1408 MaskFormatter maskFormatter = attributeSecurity.getPartialMaskFormatter(); 1409 field.setEncryptedValue(field.getPropertyValue()); 1410 field.setDisplayMaskValue(maskFormatter.maskValue(decryptedValue)); 1411 field.setSecure(true); 1412 } else { 1413 field.setPropertyValue(org.kuali.rice.krad.lookup.LookupUtils 1414 .forceUppercase(businessObjectClass, field.getPropertyName(), decryptedValue)); 1415 } 1416 } else { 1417 throw new RuntimeException("Field " + field.getPersonNameAttributeName() + " was encrypted on " + businessObjectClass.getName() + 1418 " lookup was encrypted when it should not have been encrypted according to the data dictionary."); 1419 } 1420 } 1421 } else { 1422 if (boAuthzService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, field.getPropertyName())) { 1423 LOG.error("Cannot handle multiple value field types that have field authorizations, please implement custom lookupable helper service"); 1424 throw new RuntimeException("Cannot handle multiple value field types that have field authorizations."); 1425 } 1426 } 1427 } 1428 1429 /** 1430 * Calls methods that can be overridden by child lookupables to implement conditional logic for setting 1431 * read-only, required, and hidden attributes. Called in the last part of the lookup lifecycle so the 1432 * fields values that will be sent will be correctly reflected in the rows (like after a clear). 1433 * 1434 * @see #getConditionallyReadOnlyPropertyNames() 1435 * @see #getConditionallyRequiredPropertyNames() 1436 * @see #getConditionallyHiddenPropertyNames() 1437 * @see LookupableHelperService#applyConditionalLogicForFieldDisplay() 1438 */ 1439 public void applyConditionalLogicForFieldDisplay() { 1440 Set<String> readOnlyFields = getConditionallyReadOnlyPropertyNames(); 1441 Set<String> requiredFields = getConditionallyRequiredPropertyNames(); 1442 Set<String> hiddenFields = getConditionallyHiddenPropertyNames(); 1443 1444 for (Iterator iter = this.getRows().iterator(); iter.hasNext();) { 1445 Row row = (Row) iter.next(); 1446 for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) { 1447 Field field = (Field) iterator.next(); 1448 1449 if (readOnlyFields != null && readOnlyFields.contains(field.getPropertyName())) { 1450 field.setReadOnly(true); 1451 } 1452 1453 if (requiredFields != null && requiredFields.contains(field.getPropertyName())) { 1454 field.setFieldRequired(true); 1455 } 1456 1457 if (hiddenFields != null && hiddenFields.contains(field.getPropertyName())) { 1458 field.setFieldType(Field.HIDDEN); 1459 } 1460 } 1461 } 1462 } 1463 1464 /** 1465 * @return Set of property names that should be set as read only based on the current search 1466 * contents, note request parms containing search field values can be retrieved with 1467 * {@link #getParameters()} 1468 */ 1469 public Set<String> getConditionallyReadOnlyPropertyNames() { 1470 return new HashSet<String>(); 1471 } 1472 1473 /** 1474 * @return Set of property names that should be set as required 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> getConditionallyRequiredPropertyNames() { 1479 return new HashSet<String>(); 1480 } 1481 1482 /** 1483 * @return Set of property names that should be set as hidden 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> getConditionallyHiddenPropertyNames() { 1488 return new HashSet<String>(); 1489 } 1490 1491 /** 1492 * Helper method to get the value for a property out of the row-field graph. If property is 1493 * multi-value then the values will be joined by a semi-colon. 1494 * 1495 * @param propertyName - name of property to retrieve value for 1496 * @return current property value as a String 1497 */ 1498 protected String getCurrentSearchFieldValue(String propertyName) { 1499 String currentValue = null; 1500 1501 boolean fieldFound = false; 1502 for (Iterator iter = this.getRows().iterator(); iter.hasNext();) { 1503 Row row = (Row) iter.next(); 1504 for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) { 1505 Field field = (Field) iterator.next(); 1506 1507 if (StringUtils.equalsIgnoreCase(propertyName, field.getPropertyName())) { 1508 if (Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) { 1509 currentValue = StringUtils.join(field.getPropertyValues(), ";"); 1510 } else { 1511 currentValue = field.getPropertyValue(); 1512 } 1513 fieldFound = true; 1514 } 1515 1516 if (fieldFound) { 1517 break; 1518 } 1519 } 1520 1521 if (fieldFound) { 1522 break; 1523 } 1524 } 1525 1526 return currentValue; 1527 } 1528 }