001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.lookup; 017 018import org.apache.commons.beanutils.PropertyUtils; 019import org.apache.commons.lang.BooleanUtils; 020import org.apache.commons.lang.StringUtils; 021import org.kuali.rice.core.api.CoreApiServiceLocator; 022import org.kuali.rice.core.api.config.property.ConfigurationService; 023import org.kuali.rice.core.api.encryption.EncryptionService; 024import org.kuali.rice.core.api.search.SearchOperator; 025import org.kuali.rice.core.api.util.RiceKeyConstants; 026import org.kuali.rice.core.api.util.type.TypeUtils; 027import org.kuali.rice.kim.api.identity.Person; 028import org.kuali.rice.krad.bo.ExternalizableBusinessObject; 029import org.kuali.rice.krad.datadictionary.BusinessObjectEntry; 030import org.kuali.rice.krad.datadictionary.RelationshipDefinition; 031import org.kuali.rice.krad.service.DataObjectAuthorizationService; 032import org.kuali.rice.krad.service.DataObjectMetaDataService; 033import org.kuali.rice.krad.service.DocumentDictionaryService; 034import org.kuali.rice.krad.service.KRADServiceLocator; 035import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 036import org.kuali.rice.krad.service.LookupService; 037import org.kuali.rice.krad.service.ModuleService; 038import org.kuali.rice.krad.uif.UifConstants; 039import org.kuali.rice.krad.uif.UifParameters; 040import org.kuali.rice.krad.uif.control.Control; 041import org.kuali.rice.krad.uif.control.HiddenControl; 042import org.kuali.rice.krad.uif.control.ValueConfiguredControl; 043import org.kuali.rice.krad.uif.field.InputField; 044import org.kuali.rice.krad.uif.field.LinkField; 045import org.kuali.rice.krad.uif.field.LookupInputField; 046import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl; 047import org.kuali.rice.krad.uif.util.ComponentUtils; 048import org.kuali.rice.krad.uif.util.LookupInquiryUtils; 049import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 050import org.kuali.rice.krad.uif.view.LookupView; 051import org.kuali.rice.krad.uif.view.View; 052import org.kuali.rice.krad.util.BeanPropertyComparator; 053import org.kuali.rice.krad.util.GlobalVariables; 054import org.kuali.rice.krad.util.KRADConstants; 055import org.kuali.rice.krad.util.KRADUtils; 056import org.kuali.rice.krad.util.ObjectUtils; 057import org.kuali.rice.krad.util.UrlFactory; 058import org.kuali.rice.krad.web.form.LookupForm; 059 060import java.security.GeneralSecurityException; 061import java.util.ArrayList; 062import java.util.Collection; 063import java.util.Collections; 064import java.util.HashMap; 065import java.util.List; 066import java.util.Map; 067import java.util.Properties; 068 069/** 070 * Default implementation of <code>Lookupable</code> 071 * 072 * @author Kuali Rice Team (rice.collab@kuali.org) 073 */ 074public class LookupableImpl extends ViewHelperServiceImpl implements Lookupable { 075 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupableImpl.class); 076 077 private Class<?> dataObjectClass; 078 079 private Map<String, String> parameters; 080 private List<String> defaultSortAttributeNames; 081 082 // TODO delyea: where to take into account the sort ascending value (old KNS appeared to ignore?) 083 private boolean sortAscending; 084 085 private Map<String, String> fieldConversions; 086 private List<String> readOnlyFieldsList; 087 088 private transient ConfigurationService configurationService; 089 private transient DataObjectAuthorizationService dataObjectAuthorizationService; 090 private transient DataObjectMetaDataService dataObjectMetaDataService; 091 private transient DocumentDictionaryService documentDictionaryService; 092 private transient LookupService lookupService; 093 private transient EncryptionService encryptionService; 094 095 /** 096 * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#populateViewFromRequestParameters(org.kuali.rice.krad.uif.view.View, 097 * java.util.Map) 098 */ 099 @Override 100 public void populateViewFromRequestParameters(View view, Map<String, String> parameters) { 101 super.populateViewFromRequestParameters(view, parameters); 102 /* On the old Lookupable and LookupableHelperService in KNS the parameters list used to have multipart form 103 * data in it where it may not in the new KRAD. See PojoFormBase.populate() method for more information 104 */ 105 setParameters(parameters); 106 } 107 108 /** 109 * Initialization of Lookupable requires that the business object class be set for the {@link 110 * #initializeAttributeFieldFromDataDictionary(View, org.kuali.rice.krad.uif.field.InputField)} method 111 * 112 * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#performInitialization(org.kuali.rice.krad.uif.view.View, java.lang.Object) 113 */ 114 @Override 115 public void performInitialization(View view, Object model) { 116 if (!LookupView.class.isAssignableFrom(view.getClass())) { 117 throw new IllegalArgumentException( 118 "View class '" + view.getClass() + " is not assignable from the '" + LookupView.class + "'"); 119 } 120 121 LookupView lookupView = (LookupView) view; 122 initializeLookupViewHelperService(lookupView); 123 124 super.performInitialization(view, model); 125 } 126 127 /** 128 * Initializes properties on this lookupable from the <code>LookupView</code> 129 * 130 * @param lookupView - lookup view instance 131 */ 132 protected void initializeLookupViewHelperService(LookupView lookupView) { 133 setDefaultSortAttributeNames(lookupView.getDefaultSortAttributeNames()); 134 setSortAscending(lookupView.isDefaultSortAscending()); 135 setDataObjectClass(lookupView.getDataObjectClassName()); 136 } 137 138 /** 139 * @see org.kuali.rice.krad.lookup.Lookupable#initSuppressAction(org.kuali.rice.krad.web.form.LookupForm) 140 */ 141 @Override 142 public void initSuppressAction(LookupForm lookupForm) { 143 LookupViewAuthorizerBase lookupAuthorizer = (LookupViewAuthorizerBase) lookupForm.getView().getAuthorizer(); 144 Person user = GlobalVariables.getUserSession().getPerson(); 145 ((LookupView) lookupForm.getView()).setSuppressActions(!lookupAuthorizer.canInitiateDocument(lookupForm, user)); 146 } 147 148 /** 149 * @see org.kuali.rice.krad.lookup.Lookupable#performSearch 150 */ 151 @Override 152 public Collection<?> performSearch(LookupForm form, Map<String, String> searchCriteria, boolean bounded) { 153 Collection<?> displayList; 154 155 LookupUtils.preprocessDateFields(searchCriteria); 156 157 // TODO: force uppercase will be done in binding at some point 158 displayList = getSearchResults(form, LookupUtils.forceUppercase(getDataObjectClass(), searchCriteria), 159 !bounded); 160 161 // TODO delyea - is this the best way to set that the entire set has a returnable row? 162 List<String> pkNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(getDataObjectClass()); 163 Person user = GlobalVariables.getUserSession().getPerson(); 164 165 for (Object object : displayList) { 166 if (isResultReturnable(object)) { 167 form.setAtLeastOneRowReturnable(true); 168 } 169 } 170 171 return displayList; 172 } 173 174 protected List<?> getSearchResults(LookupForm form, Map<String, String> searchCriteria, boolean unbounded) { 175 List<?> searchResults; 176 177 // removed blank search values and decrypt any encrypted search values 178 Map<String, String> nonBlankSearchCriteria = processSearchCriteria(form, searchCriteria); 179 180 boolean searchUsingOnlyPrimaryKeyValues = 181 getLookupService().allPrimaryKeyValuesPresentAndNotWildcard(getDataObjectClass(), searchCriteria); 182 183 // if this class is an EBO, just call the module service to get the results 184 if (ExternalizableBusinessObject.class.isAssignableFrom(getDataObjectClass())) { 185 return getSearchResultsForEBO(nonBlankSearchCriteria, unbounded); 186 } 187 188 // if any of the properties refer to an embedded EBO, call the EBO 189 // lookups first and apply to the local lookup 190 try { 191 if (LookupUtils.hasExternalBusinessObjectProperty(getDataObjectClass(), nonBlankSearchCriteria)) { 192 Map<String, String> eboSearchCriteria = adjustCriteriaForNestedEBOs(nonBlankSearchCriteria, unbounded); 193 194 if (LOG.isDebugEnabled()) { 195 LOG.debug("Passing these results into the lookup service: " + eboSearchCriteria); 196 } 197 198 // add those results as criteria run the normal search (but with the EBO criteria added) 199 searchResults = (List<?>) getLookupService() 200 .findCollectionBySearchHelper(getDataObjectClass(), eboSearchCriteria, unbounded); 201 } else { 202 searchResults = (List<?>) getLookupService() 203 .findCollectionBySearchHelper(getDataObjectClass(), nonBlankSearchCriteria, unbounded); 204 } 205 } catch (IllegalAccessException e) { 206 LOG.error("Error trying to perform search", e); 207 throw new RuntimeException("Error trying to perform search", e); 208 } catch (InstantiationException e1) { 209 LOG.error("Error trying to perform search", e1); 210 throw new RuntimeException("Error trying to perform search", e1); 211 } 212 213 if (searchResults == null) { 214 searchResults = new ArrayList<Object>(); 215 } 216 217 // sort list if default sort column given 218 List<String> defaultSortColumns = getDefaultSortAttributeNames(); 219 if ((defaultSortColumns != null) && (defaultSortColumns.size() > 0)) { 220 Collections.sort(searchResults, new BeanPropertyComparator(defaultSortColumns, true)); 221 } 222 223 return searchResults; 224 } 225 226 protected Map<String, String> processSearchCriteria(LookupForm lookupForm, Map<String, String> searchCriteria) { 227 Map<String, InputField> criteriaFields = getCriteriaFieldsForValidation((LookupView) lookupForm.getView(), 228 lookupForm); 229 230 Map<String, String> nonBlankSearchCriteria = new HashMap<String, String>(); 231 for (String fieldName : searchCriteria.keySet()) { 232 String fieldValue = searchCriteria.get(fieldName); 233 234 // don't add hidden criteria 235 LookupView lookupView = (LookupView) lookupForm.getView(); 236 InputField inputField = criteriaFields.get(fieldName); 237 if (inputField.getControl() instanceof HiddenControl) { 238 continue; 239 } 240 241 // only add criteria if non blank 242 if (StringUtils.isNotBlank(fieldValue)) { 243 if (fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) { 244 String encryptedValue = StringUtils.removeEnd(fieldValue, EncryptionService.ENCRYPTION_POST_PREFIX); 245 try { 246 if(CoreApiServiceLocator.getEncryptionService().isEnabled()) { 247 fieldValue = getEncryptionService().decrypt(encryptedValue); 248 } 249 } catch (GeneralSecurityException e) { 250 LOG.error("Error decrypting value for business object class " + getDataObjectClass() + 251 " attribute " + fieldName, e); 252 throw new RuntimeException( 253 "Error decrypting value for business object class " + getDataObjectClass() + 254 " attribute " + fieldName, e); 255 } 256 } 257 258 nonBlankSearchCriteria.put(fieldName, fieldValue); 259 } 260 } 261 262 return nonBlankSearchCriteria; 263 } 264 265 protected List<?> getSearchResultsForEBO(Map<String, String> searchCriteria, boolean unbounded) { 266 ModuleService eboModuleService = 267 KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(getDataObjectClass()); 268 BusinessObjectEntry ddEntry = 269 eboModuleService.getExternalizableBusinessObjectDictionaryEntry(getDataObjectClass()); 270 271 Map<String, String> filteredFieldValues = new HashMap<String, String>(); 272 for (String fieldName : searchCriteria.keySet()) { 273 if (ddEntry.getAttributeNames().contains(fieldName)) { 274 filteredFieldValues.put(fieldName, searchCriteria.get(fieldName)); 275 } 276 } 277 278 List<?> searchResults = eboModuleService.getExternalizableBusinessObjectsListForLookup( 279 (Class<? extends ExternalizableBusinessObject>) getDataObjectClass(), (Map) filteredFieldValues, 280 unbounded); 281 282 return searchResults; 283 } 284 285 protected Map<String, String> adjustCriteriaForNestedEBOs(Map<String, String> searchCriteria, 286 boolean unbounded) throws InstantiationException, IllegalAccessException { 287 if (LOG.isDebugEnabled()) { 288 LOG.debug("has EBO reference: " + getDataObjectClass()); 289 LOG.debug("properties: " + searchCriteria); 290 } 291 292 // remove the EBO criteria 293 Map<String, String> nonEboFieldValues = 294 LookupUtils.removeExternalizableBusinessObjectFieldValues(getDataObjectClass(), searchCriteria); 295 if (LOG.isDebugEnabled()) { 296 LOG.debug("Non EBO properties removed: " + nonEboFieldValues); 297 } 298 299 // get the list of EBO properties attached to this object 300 List<String> eboPropertyNames = 301 LookupUtils.getExternalizableBusinessObjectProperties(getDataObjectClass(), searchCriteria); 302 if (LOG.isDebugEnabled()) { 303 LOG.debug("EBO properties: " + eboPropertyNames); 304 } 305 306 // loop over those properties 307 for (String eboPropertyName : eboPropertyNames) { 308 // extract the properties as known to the EBO 309 Map<String, String> eboFieldValues = 310 LookupUtils.getExternalizableBusinessObjectFieldValues(eboPropertyName, searchCriteria); 311 if (LOG.isDebugEnabled()) { 312 LOG.debug("EBO properties for master EBO property: " + eboPropertyName); 313 LOG.debug("properties: " + eboFieldValues); 314 } 315 316 // run search against attached EBO's module service 317 ModuleService eboModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService( 318 LookupUtils.getExternalizableBusinessObjectClass(getDataObjectClass(), eboPropertyName)); 319 320 // KULRICE-4401 made eboResults an empty list and only filled if 321 // service is found. 322 List<?> eboResults = Collections.emptyList(); 323 if (eboModuleService != null) { 324 eboResults = eboModuleService.getExternalizableBusinessObjectsListForLookup( 325 LookupUtils.getExternalizableBusinessObjectClass(getDataObjectClass(), eboPropertyName), 326 (Map) eboFieldValues, unbounded); 327 } else { 328 LOG.debug("EBO ModuleService is null: " + eboPropertyName); 329 } 330 // get the mapping/relationship between the EBO object and it's 331 // parent object 332 // use that to adjust the searchCriteria 333 334 // get the parent property type 335 Class<?> eboParentClass; 336 String eboParentPropertyName; 337 if (ObjectUtils.isNestedAttribute(eboPropertyName)) { 338 eboParentPropertyName = StringUtils.substringBeforeLast(eboPropertyName, "."); 339 try { 340 eboParentClass = 341 PropertyUtils.getPropertyType(getDataObjectClass().newInstance(), eboParentPropertyName); 342 } catch (Exception ex) { 343 throw new RuntimeException("Unable to create an instance of the business object class: " + 344 getDataObjectClass().getName(), ex); 345 } 346 } else { 347 eboParentClass = getDataObjectClass(); 348 eboParentPropertyName = null; 349 } 350 351 if (LOG.isDebugEnabled()) { 352 LOG.debug("determined EBO parent class/property name: " + eboParentClass + "/" + eboParentPropertyName); 353 } 354 355 // look that up in the DD (BOMDS) 356 // find the appropriate relationship 357 // CHECK THIS: what if eboPropertyName is a nested attribute - 358 // need to strip off the eboParentPropertyName if not null 359 RelationshipDefinition rd = 360 getDataObjectMetaDataService().getDictionaryRelationship(eboParentClass, eboPropertyName); 361 if (LOG.isDebugEnabled()) { 362 LOG.debug("Obtained RelationshipDefinition for " + eboPropertyName); 363 LOG.debug(rd); 364 } 365 366 // copy the needed properties (primary only) to the field values KULRICE-4446 do 367 // so only if the relationship definition exists 368 // NOTE: this will work only for single-field PK unless the ORM 369 // layer is directly involved 370 // (can't make (field1,field2) in ( (v1,v2),(v3,v4) ) style 371 // queries in the lookup framework 372 if (ObjectUtils.isNotNull(rd)) { 373 if (rd.getPrimitiveAttributes().size() > 1) { 374 throw new RuntimeException( 375 "EBO Links don't work for relationships with multiple-field primary keys."); 376 } 377 String boProperty = rd.getPrimitiveAttributes().get(0).getSourceName(); 378 String eboProperty = rd.getPrimitiveAttributes().get(0).getTargetName(); 379 StringBuffer boPropertyValue = new StringBuffer(); 380 381 // loop over the results, making a string that the lookup 382 // DAO will convert into an 383 // SQL "IN" clause 384 for (Object ebo : eboResults) { 385 if (boPropertyValue.length() != 0) { 386 boPropertyValue.append(SearchOperator.OR.op()); 387 } 388 try { 389 boPropertyValue.append(PropertyUtils.getProperty(ebo, eboProperty).toString()); 390 } catch (Exception ex) { 391 LOG.warn("Unable to get value for " + eboProperty + " on " + ebo); 392 } 393 } 394 395 if (eboParentPropertyName == null) { 396 // non-nested property containing the EBO 397 nonEboFieldValues.put(boProperty, boPropertyValue.toString()); 398 } else { 399 // property nested within the main searched-for BO that 400 // contains the EBO 401 nonEboFieldValues.put(eboParentPropertyName + "." + boProperty, boPropertyValue.toString()); 402 } 403 } 404 } 405 406 return nonEboFieldValues; 407 } 408 409 /** 410 * @see org.kuali.rice.krad.lookup.Lookupable#performClear 411 */ 412 @Override 413 public Map<String, String> performClear(LookupForm form, Map<String, String> searchCriteria) { 414 Map<String, InputField> criteriaFieldMap = getCriteriaFieldsForValidation((LookupView) form.getView(), form); 415 Map<String, String> clearedSearchCriteria = new HashMap<String, String>(); 416 for (Map.Entry<String, String> searchKeyValue : searchCriteria.entrySet()) { 417 String searchPropertyName = searchKeyValue.getKey(); 418 419 InputField inputField = criteriaFieldMap.get(searchPropertyName); 420 if (inputField != null) { 421 // TODO: check secure fields 422// if (field.isSecure()) { 423// field.setSecure(false); 424// field.setDisplayMaskValue(null); 425// field.setEncryptedValue(null); 426// } 427 428 // TODO: need formatting on default value and make sure it works when control converts 429 // from checkbox to radio 430 clearedSearchCriteria.put(searchPropertyName, inputField.getDefaultValue()); 431 } else { 432 throw new RuntimeException("Invalid search field sent for property name: " + searchPropertyName); 433 } 434 } 435 436 return clearedSearchCriteria; 437 } 438 439 /** 440 * @see org.kuali.rice.krad.lookup.Lookupable#validateSearchParameters 441 */ 442 @Override 443 public boolean validateSearchParameters(LookupForm form, Map<String, String> searchCriteria) { 444 boolean valid = true; 445 446 if (!getViewDictionaryService().isLookupable(getDataObjectClass())) { 447 throw new RuntimeException("Lookup not defined for data object " + getDataObjectClass()); 448 } 449 450 Map<String, InputField> criteriaFields = getCriteriaFieldsForValidation((LookupView) form.getView(), form); 451 452 // validate required 453 // TODO: this will be done by the uif validation service at some point 454 for (Map.Entry<String, String> searchKeyValue : searchCriteria.entrySet()) { 455 String searchPropertyName = searchKeyValue.getKey(); 456 String searchPropertyValue = searchKeyValue.getValue(); 457 458 LookupView lookupView = (LookupView) form.getView(); 459 InputField inputField = criteriaFields.get(searchPropertyName); 460 if (inputField != null) { 461 if (StringUtils.isBlank(searchPropertyValue) && BooleanUtils.isTrue(inputField.getRequired())) { 462 GlobalVariables.getMessageMap() 463 .putError(inputField.getPropertyName(), RiceKeyConstants.ERROR_REQUIRED, 464 inputField.getLabel()); 465 } 466 467 validateSearchParameterWildcardAndOperators(inputField, searchPropertyValue); 468 } else { 469 throw new RuntimeException("Invalid search field sent for property name: " + searchPropertyName); 470 } 471 } 472 473 if (GlobalVariables.getMessageMap().hasErrors()) { 474 valid = false; 475 } 476 477 return valid; 478 } 479 480 protected Map<String, InputField> getCriteriaFieldsForValidation(LookupView lookupView, LookupForm form) { 481 Map<String, InputField> criteriaFieldMap = new HashMap<String, InputField>(); 482 483 // TODO; need hooks for code generated components and also this doesn't have lifecycle which 484 // could change fields 485 List<InputField> fields = ComponentUtils.getComponentsOfTypeDeep(lookupView.getCriteriaFields(), 486 InputField.class); 487 for (InputField field : fields) { 488 criteriaFieldMap.put(field.getPropertyName(), field); 489 } 490 491 return criteriaFieldMap; 492 } 493 494 /** 495 * Validates that any wildcards contained within the search value are valid wilcards and allowed for the 496 * property type for which the field is searching 497 * 498 * @param inputField - attribute field instance for the field that is being searched 499 * @param searchPropertyValue - value given for field to search for 500 */ 501 protected void validateSearchParameterWildcardAndOperators(InputField inputField, 502 String searchPropertyValue) { 503 if (StringUtils.isBlank(searchPropertyValue)) 504 return; 505 506 // make sure a wildcard/operator is in the value 507 boolean found = false; 508 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) { 509 String queryCharacter = op.op(); 510 511 if (searchPropertyValue.contains(queryCharacter)) { 512 found = true; 513 } 514 } 515 516 if (!found) { 517 return; 518 } 519 520 String attributeLabel = inputField.getLabel(); 521 if ((LookupInputField.class.isAssignableFrom(inputField.getClass())) && 522 (((LookupInputField) inputField).isTreatWildcardsAndOperatorsAsLiteral())) { 523 Object dataObjectExample = null; 524 try { 525 dataObjectExample = getDataObjectClass().newInstance(); 526 } catch (Exception e) { 527 LOG.error("Exception caught instantiating " + getDataObjectClass().getName(), e); 528 throw new RuntimeException("Cannot instantiate " + getDataObjectClass().getName(), e); 529 } 530 531 Class<?> propertyType = 532 ObjectPropertyUtils.getPropertyType(getDataObjectClass(), inputField.getPropertyName()); 533 if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) || 534 TypeUtils.isTemporalClass(propertyType)) { 535 GlobalVariables.getMessageMap().putError(inputField.getPropertyName(), 536 RiceKeyConstants.ERROR_WILDCARDS_AND_OPERATORS_NOT_ALLOWED_ON_FIELD, attributeLabel); 537 } 538 539 if (TypeUtils.isStringClass(propertyType)) { 540 GlobalVariables.getMessageMap().putInfo(inputField.getPropertyName(), 541 RiceKeyConstants.INFO_WILDCARDS_AND_OPERATORS_TREATED_LITERALLY, attributeLabel); 542 } 543 } else { 544 if (getDataObjectAuthorizationService() 545 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(getDataObjectClass(), 546 inputField.getPropertyName())) { 547 if (!searchPropertyValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) { 548 // encrypted values usually come from the DB, so we don't 549 // need to filter for wildcards 550 // wildcards are not allowed on restricted fields, because 551 // they are typically encrypted, and wildcard searches cannot be performed without 552 // decrypting every row, which is currently not supported by KRAD 553 554 GlobalVariables.getMessageMap() 555 .putError(inputField.getPropertyName(), RiceKeyConstants.ERROR_SECURE_FIELD, 556 attributeLabel); 557 } 558 } 559 } 560 } 561 562 /** 563 * @see org.kuali.rice.krad.lookup.Lookupable#getReturnUrlForResults 564 */ 565 public void getReturnUrlForResults(LinkField returnLinkField, Object model) { 566 LookupForm lookupForm = (LookupForm) model; 567 LookupView lookupView = (LookupView) returnLinkField.getContext().get(UifConstants.ContextVariableNames.VIEW); 568 569 Object dataObject = returnLinkField.getContext().get(UifConstants.ContextVariableNames.LINE); 570 571 // don't render return link if the object is null or if the row is not returnable 572 if ((dataObject == null) || (!isResultReturnable(dataObject))) { 573 returnLinkField.setRender(false); 574 return; 575 } 576 577 // build return link href 578 String href = getReturnUrl(lookupView, lookupForm, dataObject); 579 if (StringUtils.isBlank(href)) { 580 returnLinkField.setRender(false); 581 return; 582 } 583 // TODO: need to handle returning anchor 584 returnLinkField.setHrefText(href); 585 586 // build return link label and title 587 String linkLabel = getConfigurationService().getPropertyValueAsString( 588 KRADConstants.Lookup.TITLE_RETURN_URL_PREPENDTEXT_PROPERTY); 589 returnLinkField.setLinkLabel(linkLabel); 590 591 List<String> returnKeys = getReturnKeys(lookupView, lookupForm, dataObject); 592 Map<String, String> returnKeyValues = KRADUtils.getPropertyKeyValuesFromDataObject(returnKeys, dataObject); 593 594 String title = LookupInquiryUtils.getLinkTitleText(linkLabel, getDataObjectClass(), returnKeyValues); 595 returnLinkField.setTitle(title); 596 597 // Add the return target if it is set 598 String returnTarget = lookupView.getReturnTarget(); 599 if (returnTarget != null) { 600 returnLinkField.setTarget(returnTarget); 601 602 // Add the close script if lookup is in a light box 603 if (!returnTarget.equals("_self")) { 604 605 // Add the return script if the returnByScript flag is set 606 if (lookupView.isReturnByScript()) { 607 Properties props = getReturnUrlParameters(lookupView, lookupForm, dataObject); 608 609 StringBuilder script = new StringBuilder("e.preventDefault();"); 610 for (String returnField : lookupForm.getFieldConversions().values()) { 611 if (props.containsKey(returnField)) { 612 Object fieldName = returnField.replace("'", "\\'"); 613 Object value = props.get(returnField); 614 script = script.append("returnLookupResultByScript(\"" + returnField + "\", '" + value + "');"); 615 } 616 } 617 returnLinkField.setOnClickScript(script.append("closeLightbox();").toString()); 618 } else{ 619 // Close the light box if return target is not _self or _parent 620 returnLinkField.setOnClickScript("e.preventDefault();closeLightbox();createLoading(true);returnLookupResultReload(jQuery(this));"); 621 } 622 } 623 } else { 624 // If no return target is set return in same frame 625 // This is to insure that non light box lookups return correctly 626 returnLinkField.setTarget("_self"); 627 } 628 } 629 630 /** 631 * Builds the URL for returning the given data object result row 632 * 633 * <p> 634 * Note return URL will only be built if a return location is specified on the <code>LookupForm</code> 635 * </p> 636 * 637 * @param lookupView - lookup view instance containing lookup configuration 638 * @param lookupForm - lookup form instance containing the data 639 * @param dataObject - data object instance for the current line and for which the return URL is being built 640 * @return String return URL or blank if URL cannot be built 641 */ 642 protected String getReturnUrl(LookupView lookupView, LookupForm lookupForm, Object dataObject) { 643 Properties props = getReturnUrlParameters(lookupView, lookupForm, dataObject); 644 645 String href = ""; 646 if (StringUtils.isNotBlank(lookupForm.getReturnLocation())) { 647 href = UrlFactory.parameterizeUrl(lookupForm.getReturnLocation(), props); 648 } 649 650 return href; 651 } 652 653 /** 654 * Builds up a <code>Properties</code> object that will be used to provide the request parameters for the 655 * return URL link 656 * 657 * @param lookupView - lookup view instance containing lookup configuration 658 * @param lookupForm - lookup form instance containing the data 659 * @param dataObject - data object instance for the current line and for which the return URL is being built 660 * @return Properties instance containing request parameters for return URL 661 */ 662 protected Properties getReturnUrlParameters(LookupView lookupView, LookupForm lookupForm, Object dataObject) { 663 Properties props = new Properties(); 664 props.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL); 665 666 if (StringUtils.isNotBlank(lookupForm.getReturnFormKey())) { 667 props.put(UifParameters.FORM_KEY, lookupForm.getReturnFormKey()); 668 } 669 670 props.put(KRADConstants.REFRESH_CALLER, lookupView.getId()); 671 props.put(KRADConstants.REFRESH_DATA_OBJECT_CLASS, getDataObjectClass().getName()); 672 673 if (StringUtils.isNotBlank(lookupForm.getDocNum())) { 674 props.put(UifParameters.DOC_NUM, lookupForm.getDocNum()); 675 } 676 677 if (StringUtils.isNotBlank(lookupForm.getReferencesToRefresh())) { 678 props.put(KRADConstants.REFERENCES_TO_REFRESH, lookupForm.getReferencesToRefresh()); 679 } 680 681 List<String> returnKeys = getReturnKeys(lookupView, lookupForm, dataObject); 682 Map<String, String> returnKeyValues = KRADUtils.getPropertyKeyValuesFromDataObject(returnKeys, dataObject); 683 684 for (String returnKey : returnKeyValues.keySet()) { 685 String returnValue = returnKeyValues.get(returnKey); 686 if (lookupForm.getFieldConversions().containsKey(returnKey)) { 687 returnKey = lookupForm.getFieldConversions().get(returnKey); 688 } 689 690 props.put(returnKey, returnValue); 691 } 692 693 return props; 694 } 695 696 /** 697 * Returns the configured return key property names or if not configured defaults to the primary keys 698 * for the data object class 699 * 700 * @return List<String> property names which should be passed back on the return URL 701 */ 702 protected List<String> getReturnKeys(LookupView lookupView, LookupForm lookupForm, Object dataObject) { 703 List<String> returnKeys; 704 if (lookupForm.getFieldConversions() != null && !lookupForm.getFieldConversions().isEmpty()) { 705 returnKeys = new ArrayList<String>(lookupForm.getFieldConversions().keySet()); 706 } else { 707 returnKeys = getDataObjectMetaDataService().listPrimaryKeyFieldNames(getDataObjectClass()); 708 } 709 710 return returnKeys; 711 } 712 713 /** 714 * @see org.kuali.rice.krad.lookup.Lookupable#getMaintenanceActionLink 715 */ 716 public void getMaintenanceActionLink(LinkField actionLinkField, Object model, String maintenanceMethodToCall) { 717 LookupForm lookupForm = (LookupForm) model; 718 LookupView lookupView = (LookupView) actionLinkField.getContext().get(UifConstants.ContextVariableNames.VIEW); 719 Object dataObject = actionLinkField.getContext().get(UifConstants.ContextVariableNames.LINE); 720 721 List<String> pkNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(getDataObjectClass()); 722 723 // build maintenance link href 724 String href = getActionUrlHref(lookupForm, dataObject, maintenanceMethodToCall, pkNames); 725 if (StringUtils.isBlank(href)) { 726 actionLinkField.setRender(false); 727 return; 728 } 729 // TODO: need to handle returning anchor 730 actionLinkField.setHrefText(href); 731 732 // build action title 733 String prependTitleText = actionLinkField.getLinkLabel() + " " + 734 getDataDictionaryService().getDataDictionary().getDataObjectEntry(getDataObjectClass().getName()) 735 .getObjectLabel() + " " + 736 getConfigurationService().getPropertyValueAsString( 737 KRADConstants.Lookup.TITLE_ACTION_URL_PREPENDTEXT_PROPERTY); 738 739 Map<String, String> primaryKeyValues = KRADUtils.getPropertyKeyValuesFromDataObject(pkNames, dataObject); 740 String title = LookupInquiryUtils.getLinkTitleText(prependTitleText, getDataObjectClass(), primaryKeyValues); 741 actionLinkField.setTitle(title); 742 // TODO : do not hardcode the _self string 743 actionLinkField.setTarget("_self"); 744 lookupForm.setAtLeastOneRowHasActions(true); 745 } 746 747 /** 748 * Generates a URL to perform a maintenance action on the given result data object 749 * 750 * <p> 751 * Will build a URL containing keys of the data object to invoke the given maintenance action method 752 * within the maintenance controller 753 * </p> 754 * 755 * @param dataObject - data object instance for the line to build the maintenance action link for 756 * @param methodToCall - method name on the maintenance controller that should be invoked 757 * @param pkNames - list of primary key field names for the data object whose key/value pairs will be added to 758 * the maintenance link 759 * @return String URL link for the maintenance action 760 */ 761 protected String getActionUrlHref(LookupForm lookupForm, Object dataObject, String methodToCall, 762 List<String> pkNames) { 763 LookupView lookupView = (LookupView) lookupForm.getView(); 764 765 Properties props = new Properties(); 766 props.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, methodToCall); 767 768 Map<String, String> primaryKeyValues = KRADUtils.getPropertyKeyValuesFromDataObject(pkNames, dataObject); 769 for (String primaryKey : primaryKeyValues.keySet()) { 770 String primaryKeyValue = primaryKeyValues.get(primaryKey); 771 772 props.put(primaryKey, primaryKeyValue); 773 } 774 775 if (StringUtils.isNotBlank(lookupForm.getReturnLocation())) { 776 props.put(KRADConstants.RETURN_LOCATION_PARAMETER, lookupForm.getReturnLocation()); 777 } 778 779 props.put(UifParameters.DATA_OBJECT_CLASS_NAME, lookupForm.getDataObjectClassName()); 780 props.put(UifParameters.VIEW_TYPE_NAME, UifConstants.ViewType.MAINTENANCE.name()); 781 782 String maintenanceMapping = KRADConstants.Maintenance.REQUEST_MAPPING_MAINTENANCE; 783 if (StringUtils.isNotBlank(lookupView.getMaintenanceUrlMapping())) { 784 maintenanceMapping = lookupView.getMaintenanceUrlMapping(); 785 } 786 787 return UrlFactory.parameterizeUrl(maintenanceMapping, props); 788 } 789 790 /** 791 * Sets the value for the attribute field control to contain the field conversion values for the line 792 * 793 * @see org.kuali.rice.krad.lookup.LookupableImpl#setMultiValueLookupSelect 794 */ 795 @Override 796 public void setMultiValueLookupSelect(InputField selectField, Object model) { 797 LookupForm lookupForm = (LookupForm) model; 798 Object lineDataObject = selectField.getContext().get(UifConstants.ContextVariableNames.LINE); 799 if (lineDataObject == null) { 800 throw new RuntimeException("Unable to get data object for line from component: " + selectField.getId()); 801 } 802 803 Control selectControl = ((InputField) selectField).getControl(); 804 if ((selectControl != null) && (selectControl instanceof ValueConfiguredControl)) { 805 String lineIdentifier = ""; 806 807 // get value for each field conversion from line and add to lineIdentifier 808 Map<String, String> fieldConversions = lookupForm.getFieldConversions(); 809 List<String> fromFieldNames = new ArrayList<String>(fieldConversions.keySet()); 810 Collections.sort(fromFieldNames); 811 for (String fromFieldName : fromFieldNames) { 812 Object fromFieldValue = ObjectPropertyUtils.getPropertyValue(lineDataObject, fromFieldName); 813 if (fromFieldValue != null) { 814 lineIdentifier += fromFieldValue; 815 } 816 lineIdentifier += ":"; 817 } 818 lineIdentifier = StringUtils.removeEnd(lineIdentifier, ":"); 819 820 ((ValueConfiguredControl) selectControl).setValue(lineIdentifier); 821 } 822 } 823 824 /** 825 * Determines if given data object has associated maintenance document that allows new or copy 826 * maintenance 827 * actions 828 * 829 * @return boolean true if the maintenance new or copy action is allowed for the data object instance, false 830 * otherwise 831 */ 832 public boolean allowsMaintenanceNewOrCopyAction() { 833 boolean allowsNewOrCopy = false; 834 835 String maintDocTypeName = getMaintenanceDocumentTypeName(); 836 if (StringUtils.isNotBlank(maintDocTypeName)) { 837 allowsNewOrCopy = getDataObjectAuthorizationService() 838 .canCreate(getDataObjectClass(), GlobalVariables.getUserSession().getPerson(), maintDocTypeName); 839 } 840 841 return allowsNewOrCopy; 842 } 843 844 /** 845 * Determines if given data object has associated maintenance document that allows edit maintenance 846 * actions 847 * 848 * @return boolean true if the maintenance edit action is allowed for the data object instance, false otherwise 849 */ 850 public boolean allowsMaintenanceEditAction(Object dataObject) { 851 boolean allowsEdit = false; 852 853 String maintDocTypeName = getMaintenanceDocumentTypeName(); 854 if (StringUtils.isNotBlank(maintDocTypeName)) { 855 allowsEdit = getDataObjectAuthorizationService() 856 .canMaintain(dataObject, GlobalVariables.getUserSession().getPerson(), maintDocTypeName); 857 } 858 859 return allowsEdit; 860 } 861 862 /** 863 * Determines if given data object has associated maintenance document that allows delete maintenance 864 * actions. 865 * 866 * @return boolean true if the maintenance delete action is allowed for the data object instance, false otherwise 867 */ 868 public boolean allowsMaintenanceDeleteAction(Object dataObject) { 869 boolean allowsMaintain = false; 870 boolean allowsDelete = false; 871 872 String maintDocTypeName = getMaintenanceDocumentTypeName(); 873 if (StringUtils.isNotBlank(maintDocTypeName)) { 874 allowsMaintain = getDataObjectAuthorizationService() 875 .canMaintain(dataObject, GlobalVariables.getUserSession().getPerson(), maintDocTypeName); 876 } 877 878 allowsDelete = getDocumentDictionaryService().getAllowsRecordDeletion(getDataObjectClass()); 879 880 return allowsDelete && allowsMaintain; 881 } 882 883 /** 884 * Returns the maintenance document type associated with the business object class or null if one does not exist. 885 * 886 * @return String representing the maintenance document type name 887 */ 888 protected String getMaintenanceDocumentTypeName() { 889 DocumentDictionaryService dd = getDocumentDictionaryService(); 890 String maintDocTypeName = dd.getMaintenanceDocumentTypeName(getDataObjectClass()); 891 892 return maintDocTypeName; 893 } 894 895 /** 896 * Determines whether a given data object that's returned as one of the lookup's results is considered returnable, 897 * which means that for single-value lookups, a "return value" link may be rendered, and for multiple 898 * value lookups, a checkbox is rendered. 899 * 900 * Note that this can be part of an authorization mechanism, but not the complete authorization mechanism. The 901 * component that invoked the lookup/ lookup caller (e.g. document, nesting lookup, etc.) needs to check 902 * that the object that was passed to it was returnable as well because there are ways around this method 903 * (e.g. crafting a custom return URL). 904 * 905 * @param dataObject - an object from the search result set 906 * @return true if the row is returnable and false if it is not 907 */ 908 protected boolean isResultReturnable(Object dataObject) { 909 return true; 910 } 911 912 /** 913 * @see org.kuali.rice.krad.lookup.Lookupable#setDataObjectClass 914 */ 915 @Override 916 public void setDataObjectClass(Class<?> dataObjectClass) { 917 this.dataObjectClass = dataObjectClass; 918 } 919 920 /** 921 * @see org.kuali.rice.krad.lookup.Lookupable#getDataObjectClass 922 */ 923 @Override 924 public Class<?> getDataObjectClass() { 925 return this.dataObjectClass; 926 } 927 928 /** 929 * @see org.kuali.rice.krad.lookup.Lookupable#setFieldConversions 930 */ 931 @Override 932 public void setFieldConversions(Map<String, String> fieldConversions) { 933 this.fieldConversions = fieldConversions; 934 } 935 936 /** 937 * @see org.kuali.rice.krad.lookup.Lookupable#setReadOnlyFieldsList 938 */ 939 @Override 940 public void setReadOnlyFieldsList(List<String> readOnlyFieldsList) { 941 this.readOnlyFieldsList = readOnlyFieldsList; 942 } 943 944 public Map<String, String> getParameters() { 945 return parameters; 946 } 947 948 public void setParameters(Map<String, String> parameters) { 949 this.parameters = parameters; 950 } 951 952 public List<String> getDefaultSortAttributeNames() { 953 return defaultSortAttributeNames; 954 } 955 956 public void setDefaultSortAttributeNames(List<String> defaultSortAttributeNames) { 957 this.defaultSortAttributeNames = defaultSortAttributeNames; 958 } 959 960 public boolean isSortAscending() { 961 return sortAscending; 962 } 963 964 public void setSortAscending(boolean sortAscending) { 965 this.sortAscending = sortAscending; 966 } 967 968 public List<String> getReadOnlyFieldsList() { 969 return readOnlyFieldsList; 970 } 971 972 public Map<String, String> getFieldConversions() { 973 return fieldConversions; 974 } 975 976 protected ConfigurationService getConfigurationService() { 977 if (configurationService == null) { 978 this.configurationService = KRADServiceLocator.getKualiConfigurationService(); 979 } 980 return configurationService; 981 } 982 983 public void setConfigurationService(ConfigurationService configurationService) { 984 this.configurationService = configurationService; 985 } 986 987 protected DataObjectAuthorizationService getDataObjectAuthorizationService() { 988 if (dataObjectAuthorizationService == null) { 989 this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService(); 990 } 991 return dataObjectAuthorizationService; 992 } 993 994 public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) { 995 this.dataObjectAuthorizationService = dataObjectAuthorizationService; 996 } 997 998 protected DataObjectMetaDataService getDataObjectMetaDataService() { 999 if (dataObjectMetaDataService == null) { 1000 this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService(); 1001 } 1002 return dataObjectMetaDataService; 1003 } 1004 1005 public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) { 1006 this.dataObjectMetaDataService = dataObjectMetaDataService; 1007 } 1008 1009 public DocumentDictionaryService getDocumentDictionaryService() { 1010 if (documentDictionaryService == null) { 1011 documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService(); 1012 } 1013 return documentDictionaryService; 1014 } 1015 1016 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) { 1017 this.documentDictionaryService = documentDictionaryService; 1018 } 1019 1020 protected LookupService getLookupService() { 1021 if (lookupService == null) { 1022 this.lookupService = KRADServiceLocatorWeb.getLookupService(); 1023 } 1024 return lookupService; 1025 } 1026 1027 public void setLookupService(LookupService lookupService) { 1028 this.lookupService = lookupService; 1029 } 1030 1031 protected EncryptionService getEncryptionService() { 1032 if (encryptionService == null) { 1033 this.encryptionService = CoreApiServiceLocator.getEncryptionService(); 1034 } 1035 return encryptionService; 1036 } 1037 1038 public void setEncryptionService(EncryptionService encryptionService) { 1039 this.encryptionService = encryptionService; 1040 } 1041}