001/** 002 * Copyright 2005-2018 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.rules; 017 018import java.security.GeneralSecurityException; 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024import java.util.Properties; 025import java.util.Set; 026 027import org.apache.commons.lang.StringUtils; 028import org.kuali.rice.core.api.CoreApiServiceLocator; 029import org.kuali.rice.core.api.config.property.ConfigurationService; 030import org.kuali.rice.core.api.datetime.DateTimeService; 031import org.kuali.rice.core.api.exception.RiceIllegalArgumentException; 032import org.kuali.rice.core.api.mo.common.active.MutableInactivatable; 033import org.kuali.rice.core.api.util.RiceKeyConstants; 034import org.kuali.rice.core.web.format.Formatter; 035import org.kuali.rice.kew.api.WorkflowDocument; 036import org.kuali.rice.kim.api.identity.PersonService; 037import org.kuali.rice.kim.api.role.RoleService; 038import org.kuali.rice.kim.api.services.KimApiServiceLocator; 039import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter; 040import org.kuali.rice.krad.data.DataObjectService; 041import org.kuali.rice.krad.data.KradDataServiceLocator; 042import org.kuali.rice.krad.datadictionary.InactivationBlockingMetadata; 043import org.kuali.rice.krad.datadictionary.validation.ErrorLevel; 044import org.kuali.rice.krad.datadictionary.validation.result.ConstraintValidationResult; 045import org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult; 046import org.kuali.rice.krad.document.Document; 047import org.kuali.rice.krad.exception.ValidationException; 048import org.kuali.rice.krad.maintenance.BulkUpdateMaintainable; 049import org.kuali.rice.krad.maintenance.BulkUpdateMaintenanceDataObject; 050import org.kuali.rice.krad.maintenance.Maintainable; 051import org.kuali.rice.krad.maintenance.MaintenanceDocument; 052import org.kuali.rice.krad.maintenance.MaintenanceDocumentAuthorizer; 053import org.kuali.rice.krad.rules.rule.event.AddCollectionLineEvent; 054import org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent; 055import org.kuali.rice.krad.service.DataDictionaryService; 056import org.kuali.rice.krad.service.DataObjectAuthorizationService; 057import org.kuali.rice.krad.service.DictionaryValidationService; 058import org.kuali.rice.krad.service.InactivationBlockingDetectionService; 059import org.kuali.rice.krad.service.InactivationBlockingDisplayService; 060import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 061import org.kuali.rice.krad.service.LegacyDataAdapter; 062import org.kuali.rice.krad.util.ErrorMessage; 063import org.kuali.rice.krad.util.ForeignKeyFieldsPopulationState; 064import org.kuali.rice.krad.util.GlobalVariables; 065import org.kuali.rice.krad.util.KRADConstants; 066import org.kuali.rice.krad.util.KRADPropertyConstants; 067import org.kuali.rice.krad.util.RouteToCompletionUtil; 068import org.kuali.rice.krad.util.UrlFactory; 069import org.kuali.rice.krad.workflow.service.WorkflowDocumentService; 070import org.springframework.util.AutoPopulatingList; 071 072/** 073 * Contains all of the business rules that are common to all maintenance documents. 074 * 075 * @author Kuali Rice Team (rice.collab@kuali.org) 076 */ 077public class MaintenanceDocumentRuleBase extends DocumentRuleBase implements MaintenanceDocumentRule { 078 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceDocumentRuleBase.class); 079 080 // these two constants are used to correctly prefix errors added to 081 // the global errors 082 public static final String MAINTAINABLE_ERROR_PREFIX = KRADConstants.MAINTENANCE_NEW_MAINTAINABLE; 083 public static final String DOCUMENT_ERROR_PREFIX = "document."; 084 public static final String MAINTAINABLE_ERROR_PATH = DOCUMENT_ERROR_PREFIX + "newMaintainableObject"; 085 086 private DataDictionaryService ddService; 087 private DataObjectService dataObjectService; 088 private DictionaryValidationService dictionaryValidationService; 089 private ConfigurationService configService; 090 private WorkflowDocumentService workflowDocumentService; 091 private PersonService personService; 092 private RoleService roleService; 093 private DataObjectAuthorizationService dataObjectAuthorizationService; 094 095 private Object oldDataObject; 096 private Object newDataObject; 097 private Class<?> dataObjectClass; 098 099 protected List<String> priorErrorPath; 100 101 /** 102 * Default constructor a MaintenanceDocumentRuleBase.java. 103 */ 104 public MaintenanceDocumentRuleBase() { 105 priorErrorPath = new ArrayList<String>(); 106 } 107 108 /** 109 * @see MaintenanceDocumentRule#processSaveDocument(org.kuali.rice.krad.document.Document) 110 */ 111 @Override 112 public boolean processSaveDocument(Document document) { 113 MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document; 114 115 // remove all items from the errorPath temporarily (because it may not 116 // be what we expect, or what we need) 117 clearErrorPath(); 118 119 // setup convenience pointers to the old & new bo 120 setupBaseConvenienceObjects(maintenanceDocument); 121 122 // the document must be in a valid state for saving. this does not include business 123 // rules, but just enough testing that the document is populated and in a valid state 124 // to not cause exceptions when saved. if this passes, then the save will always occur, 125 // regardless of business rules. 126 if (!isDocumentValidForSave(maintenanceDocument)) { 127 resumeErrorPath(); 128 return false; 129 } 130 131 // apply rules that are specific to the class of the maintenance document 132 // (if implemented). this will always succeed if not overloaded by the 133 // subclass 134 if (!processCustomSaveDocumentBusinessRules(maintenanceDocument)) { 135 resumeErrorPath(); 136 return false; 137 } 138 139 // return the original set of items to the errorPath 140 resumeErrorPath(); 141 142 // return the original set of items to the errorPath, to ensure no impact 143 // on other upstream or downstream items that rely on the errorPath 144 return true; 145 } 146 147 /** 148 * @see MaintenanceDocumentRule#processRouteDocument(org.kuali.rice.krad.document.Document) 149 */ 150 @Override 151 public boolean processRouteDocument(Document document) { 152 LOG.info("processRouteDocument called"); 153 154 MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document; 155 156 boolean completeRequestPending = RouteToCompletionUtil.checkIfAtleastOneAdHocCompleteRequestExist( 157 maintenanceDocument); 158 159 // Validate the document if the header is valid and no pending completion requests 160 if (completeRequestPending) { 161 return true; 162 } 163 164 // get the documentAuthorizer for this document 165 MaintenanceDocumentAuthorizer documentAuthorizer = 166 (MaintenanceDocumentAuthorizer) getDocumentDictionaryService().getDocumentAuthorizer(document); 167 168 // remove all items from the errorPath temporarily (because it may not 169 // be what we expect, or what we need) 170 clearErrorPath(); 171 172 // setup convenience pointers to the old & new bo 173 setupBaseConvenienceObjects(maintenanceDocument); 174 175 // apply rules that are common across all maintenance documents, regardless of class 176 processGlobalSaveDocumentBusinessRules(maintenanceDocument); 177 178 // from here on, it is in a default-success mode, and will route unless one of the 179 // business rules stop it. 180 boolean success = true; 181 182 WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument(); 183 if (workflowDocument.isInitiated() || workflowDocument.isSaved()) { 184 try { 185 success &= documentAuthorizer.canCreateOrMaintain((MaintenanceDocument) document, 186 GlobalVariables.getUserSession().getPerson()); 187 if (success == false) { 188 GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, 189 RiceKeyConstants.AUTHORIZATION_ERROR_DOCUMENT, 190 new String[]{GlobalVariables.getUserSession().getPerson().getPrincipalName(), 191 "Create/Maintain", getDocumentDictionaryService().getMaintenanceDocumentTypeName( 192 newDataObject.getClass())}); 193 } 194 } catch (RiceIllegalArgumentException e) { 195 // TODO error message the right way 196 GlobalVariables.getMessageMap().putError("Unable to determine authorization due to previous errors", 197 "Unable to determine authorization due to previous errors"); 198 } 199 } 200 // apply rules that are common across all maintenance documents, regardless of class 201 success &= processGlobalRouteDocumentBusinessRules(maintenanceDocument); 202 203 // apply rules that are specific to the class of the maintenance document 204 // (if implemented). this will always succeed if not overloaded by the 205 // subclass 206 success &= processCustomRouteDocumentBusinessRules(maintenanceDocument); 207 208 success &= processInactivationBlockChecking(maintenanceDocument); 209 210 // return the original set of items to the errorPath, to ensure no impact 211 // on other upstream or downstream items that rely on the errorPath 212 resumeErrorPath(); 213 214 return success; 215 } 216 217 /** 218 * Determines whether a document is inactivating the record being maintained 219 * 220 * @param maintenanceDocument 221 * @return true iff the document is inactivating the business object; false otherwise 222 */ 223 protected boolean isDocumentInactivatingBusinessObject(MaintenanceDocument maintenanceDocument) { 224 if (maintenanceDocument.isEdit()) { 225 Class<?> dataObjectClass = maintenanceDocument.getNewMaintainableObject().getDataObjectClass(); 226 // we can only be inactivating a business object if we're editing it 227 if (dataObjectClass != null && MutableInactivatable.class.isAssignableFrom(dataObjectClass)) { 228 MutableInactivatable oldInactivateableBO = (MutableInactivatable) oldDataObject; 229 MutableInactivatable newInactivateableBO = (MutableInactivatable) newDataObject; 230 231 return oldInactivateableBO.isActive() && !newInactivateableBO.isActive(); 232 } 233 } 234 return false; 235 } 236 237 /** 238 * Determines whether this document has been inactivation blocked 239 * 240 * @param maintenanceDocument 241 * @return true iff there is NOTHING that blocks this record 242 */ 243 protected boolean processInactivationBlockChecking(MaintenanceDocument maintenanceDocument) { 244 if (isDocumentInactivatingBusinessObject(maintenanceDocument)) { 245 Class<?> dataObjectClass = maintenanceDocument.getNewMaintainableObject().getDataObjectClass(); 246 Set<InactivationBlockingMetadata> inactivationBlockingMetadatas = 247 getDataDictionaryService().getAllInactivationBlockingDefinitions(dataObjectClass); 248 249 if (inactivationBlockingMetadatas != null) { 250 for (InactivationBlockingMetadata inactivationBlockingMetadata : inactivationBlockingMetadatas) { 251 // for the purposes of maint doc validation, we only need to look for the first blocking record 252 253 // we found a blocking record, so we return false 254 if (!processInactivationBlockChecking(maintenanceDocument, inactivationBlockingMetadata)) { 255 return false; 256 } 257 } 258 } 259 } 260 return true; 261 } 262 263 /** 264 * Given a InactivationBlockingMetadata, which represents a relationship that may block inactivation of a BO, it 265 * determines whether there 266 * is a record that violates the blocking definition 267 * 268 * @param maintenanceDocument 269 * @param inactivationBlockingMetadata 270 * @return true iff, based on the InactivationBlockingMetadata, the maintenance document should be allowed to route 271 */ 272 protected boolean processInactivationBlockChecking(MaintenanceDocument maintenanceDocument, 273 InactivationBlockingMetadata inactivationBlockingMetadata) { 274 String inactivationBlockingDetectionServiceBeanName = 275 inactivationBlockingMetadata.getInactivationBlockingDetectionServiceBeanName(); 276 if (StringUtils.isBlank(inactivationBlockingDetectionServiceBeanName)) { 277 inactivationBlockingDetectionServiceBeanName = 278 KRADServiceLocatorWeb.DEFAULT_INACTIVATION_BLOCKING_DETECTION_SERVICE; 279 } 280 InactivationBlockingDetectionService inactivationBlockingDetectionService = 281 KRADServiceLocatorWeb.getInactivationBlockingDetectionService( 282 inactivationBlockingDetectionServiceBeanName); 283 284 boolean foundBlockingRecord = inactivationBlockingDetectionService.detectBlockingRecord( 285 newDataObject, inactivationBlockingMetadata); 286 287 if (foundBlockingRecord) { 288 putInactivationBlockingErrorOnPage(maintenanceDocument, inactivationBlockingMetadata); 289 } 290 291 return !foundBlockingRecord; 292 } 293 294 /** 295 * If there is a violation of an InactivationBlockingMetadata, it prints out an appropriate error into the error 296 * map 297 * 298 * @param document 299 * @param inactivationBlockingMetadata 300 */ 301 protected void putInactivationBlockingErrorOnPage(MaintenanceDocument document, 302 InactivationBlockingMetadata inactivationBlockingMetadata) { 303 if (!getLegacyDataAdapter().hasPrimaryKeyFieldValues(newDataObject)) { 304 throw new RuntimeException("Maintenance document did not have all primary key values filled in."); 305 } 306 307 // Even though we found a blocking record in the passed in InactivationBlockingMetada, 308 // we need to look at all inactivationBlockingMetadata associated dataObjectClass for error display 309 Class boClass = document.getNewMaintainableObject().getDataObjectClass(); 310 Set<InactivationBlockingMetadata> inactivationBlockingMetadatas = 311 getDataDictionaryService().getAllInactivationBlockingDefinitions(boClass); 312 313 StringBuffer errorMessage = new StringBuffer(); 314 315 if (inactivationBlockingMetadatas != null ) { 316 317 InactivationBlockingDisplayService inactivationBlockingDisplayService = KRADServiceLocatorWeb 318 .getInactivationBlockingDisplayService(); 319 320 for (InactivationBlockingMetadata blockingMetadata : inactivationBlockingMetadatas) { 321 322 String blockingLabel = getDataDictionaryService().getDataDictionary().getDataObjectEntry(inactivationBlockingMetadata.getBlockingDataObjectClass().getName()).getObjectLabel(); 323 324 String relationshipLabel = inactivationBlockingMetadata.getRelationshipLabel(); 325 String displayLabel; 326 327 if (StringUtils.isEmpty(relationshipLabel)) { 328 329 displayLabel = blockingLabel; 330 } else { 331 displayLabel = blockingLabel + " (" + relationshipLabel + ")"; 332 } 333 List<String> blockerObjectList = inactivationBlockingDisplayService.displayAllBlockingRecords(newDataObject, 334 inactivationBlockingMetadata); 335 336 if (!blockerObjectList.isEmpty()) { 337 errorMessage.append("<h4>"+blockingLabel+"</h4>"); 338 for(String blockerKey : blockerObjectList) { 339 errorMessage.append("<li>"); 340 errorMessage.append(blockerKey); 341 errorMessage.append("</li>"); 342 } 343 } 344 345 errorMessage.append("<br>"); 346 } 347 } 348 349 // post an error about the locked document 350 GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, 351 RiceKeyConstants.ERROR_INACTIVATION_BLOCKED, false, errorMessage.toString()); 352 } 353 354 /** 355 * @see MaintenanceDocumentRule#processApproveDocument(org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent) 356 */ 357 @Override 358 public boolean processApproveDocument(ApproveDocumentEvent approveEvent) { 359 MaintenanceDocument maintenanceDocument = (MaintenanceDocument) approveEvent.getDocument(); 360 361 // remove all items from the errorPath temporarily (because it may not 362 // be what we expect, or what we need) 363 clearErrorPath(); 364 365 // setup convenience pointers to the old & new bo 366 setupBaseConvenienceObjects(maintenanceDocument); 367 368 // apply rules that are common across all maintenance documents, regardless of class 369 processGlobalSaveDocumentBusinessRules(maintenanceDocument); 370 371 // from here on, it is in a default-success mode, and will approve unless one of the 372 // business rules stop it. 373 boolean success = true; 374 375 // apply rules that are common across all maintenance documents, regardless of class 376 success &= processGlobalApproveDocumentBusinessRules(maintenanceDocument); 377 378 // apply rules that are specific to the class of the maintenance document 379 // (if implemented). this will always succeed if not overloaded by the 380 // subclass 381 success &= processCustomApproveDocumentBusinessRules(maintenanceDocument); 382 383 // return the original set of items to the errorPath, to ensure no impact 384 // on other upstream or downstream items that rely on the errorPath 385 resumeErrorPath(); 386 387 return success; 388 } 389 390 /** 391 * {@inheritDoc} 392 * 393 * This implementation additionally performs existence and duplicate checks. 394 */ 395 @Override 396 public boolean processAddCollectionLine(AddCollectionLineEvent addEvent) { 397 MaintenanceDocument maintenanceDocument = (MaintenanceDocument) addEvent.getDocument(); 398 String collectionName = addEvent.getCollectionName(); 399 Object addLine = addEvent.getAddLine(); 400 401 // setup convenience pointers to the old & new bo 402 setupBaseConvenienceObjects(maintenanceDocument); 403 404 // from here on, it is in a default-success mode, and will add the line unless one of the 405 // business rules stop it. 406 boolean success = true; 407 408 // TODO: Will be covered by KULRICE-7666 409 /* 410 // apply rules that check whether child objects that cannot be hooked up by normal means exist and are active */ 411 412 success &= getDictionaryValidationService().validateDefaultExistenceChecksForNewCollectionItem( 413 maintenanceDocument.getNewMaintainableObject().getDataObject(), addLine, collectionName); 414 415 // apply rules that are specific to the class of the maintenance document (if implemented) 416 success &= processCustomAddCollectionLineBusinessRules(maintenanceDocument, collectionName, addLine); 417 418 return success; 419 } 420 421 /** 422 * This method is a convenience method to easily add a Document level error (ie, one not tied to a specific field, 423 * but 424 * applicable to the whole document). 425 * 426 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message. 427 */ 428 protected void putGlobalError(String errorConstant) { 429 if (!errorAlreadyExists(KRADConstants.DOCUMENT_ERRORS, errorConstant)) { 430 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRADConstants.DOCUMENT_ERRORS, errorConstant); 431 } 432 } 433 434 /** 435 * This method is a convenience method to easily add a Document level error (ie, one not tied to a specific field, 436 * but 437 * applicable to the whole document). 438 * 439 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message. 440 * @param parameter - Replacement value for part of the error message. 441 */ 442 protected void putGlobalError(String errorConstant, String parameter) { 443 if (!errorAlreadyExists(KRADConstants.DOCUMENT_ERRORS, errorConstant)) { 444 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRADConstants.DOCUMENT_ERRORS, errorConstant, 445 parameter); 446 } 447 } 448 449 /** 450 * This method is a convenience method to easily add a Document level error (ie, one not tied to a specific field, 451 * but 452 * applicable to the whole document). 453 * 454 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message. 455 * @param parameters - Array of replacement values for part of the error message. 456 */ 457 protected void putGlobalError(String errorConstant, String[] parameters) { 458 if (!errorAlreadyExists(KRADConstants.DOCUMENT_ERRORS, errorConstant)) { 459 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRADConstants.DOCUMENT_ERRORS, errorConstant, 460 parameters); 461 } 462 } 463 464 /** 465 * This method is a convenience method to add a property-specific error to the global errors list. This method 466 * makes 467 * sure that 468 * the correct prefix is added to the property name so that it will display correctly on maintenance documents. 469 * 470 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as 471 * errored in 472 * the UI. 473 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message. 474 */ 475 protected void putFieldError(String propertyName, String errorConstant) { 476 if (!errorAlreadyExists(MAINTAINABLE_ERROR_PREFIX + propertyName, errorConstant)) { 477 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(MAINTAINABLE_ERROR_PREFIX + propertyName, 478 errorConstant); 479 } 480 } 481 482 /** 483 * This method is a convenience method to add a property-specific error to the global errors list. This method 484 * makes 485 * sure that 486 * the correct prefix is added to the property name so that it will display correctly on maintenance documents. 487 * 488 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as 489 * errored in 490 * the UI. 491 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message. 492 * @param parameter - Single parameter value that can be used in the message so that you can display specific 493 * values 494 * to the 495 * user. 496 */ 497 protected void putFieldError(String propertyName, String errorConstant, String parameter) { 498 if (!errorAlreadyExists(MAINTAINABLE_ERROR_PREFIX + propertyName, errorConstant)) { 499 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(MAINTAINABLE_ERROR_PREFIX + propertyName, 500 errorConstant, parameter); 501 } 502 } 503 504 /** 505 * This method is a convenience method to add a property-specific error to the global errors list. This method 506 * makes 507 * sure that 508 * the correct prefix is added to the property name so that it will display correctly on maintenance documents. 509 * 510 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as 511 * errored in 512 * the UI. 513 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message. 514 * @param parameters - Array of strings holding values that can be used in the message so that you can display 515 * specific values 516 * to the user. 517 */ 518 protected void putFieldError(String propertyName, String errorConstant, String[] parameters) { 519 if (!errorAlreadyExists(MAINTAINABLE_ERROR_PREFIX + propertyName, errorConstant)) { 520 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(MAINTAINABLE_ERROR_PREFIX + propertyName, 521 errorConstant, parameters); 522 } 523 } 524 525 /** 526 * Adds a property-specific error to the global errors list, with the DD short label as the single argument. 527 * 528 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as 529 * errored in 530 * the UI. 531 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message. 532 */ 533 protected void putFieldErrorWithShortLabel(String propertyName, String errorConstant) { 534 String shortLabel = getDataDictionaryService().getAttributeShortLabel(dataObjectClass, propertyName); 535 putFieldError(propertyName, errorConstant, shortLabel); 536 } 537 538 /** 539 * This method is a convenience method to add a property-specific document error to the global errors list. This 540 * method makes 541 * sure that the correct prefix is added to the property name so that it will display correctly on maintenance 542 * documents. 543 * 544 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as 545 * errored in 546 * the UI. 547 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message. 548 * @param parameter - Single parameter value that can be used in the message so that you can display specific 549 * values 550 * to the 551 * user. 552 */ 553 protected void putDocumentError(String propertyName, String errorConstant, String parameter) { 554 if (!errorAlreadyExists(DOCUMENT_ERROR_PREFIX + propertyName, errorConstant)) { 555 GlobalVariables.getMessageMap().putError(DOCUMENT_ERROR_PREFIX + propertyName, errorConstant, parameter); 556 } 557 } 558 559 /** 560 * This method is a convenience method to add a property-specific document error to the global errors list. This 561 * method makes 562 * sure that the correct prefix is added to the property name so that it will display correctly on maintenance 563 * documents. 564 * 565 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as 566 * errored in 567 * the UI. 568 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message. 569 * @param parameters - Array of String parameters that can be used in the message so that you can display specific 570 * values to the 571 * user. 572 */ 573 protected void putDocumentError(String propertyName, String errorConstant, String[] parameters) { 574 GlobalVariables.getMessageMap().putError(DOCUMENT_ERROR_PREFIX + propertyName, errorConstant, parameters); 575 } 576 577 /** 578 * Convenience method to determine whether the field already has the message indicated. 579 * 580 * This is useful if you want to suppress duplicate error messages on the same field. 581 * 582 * @param propertyName - propertyName you want to test on 583 * @param errorConstant - errorConstant you want to test 584 * @return returns True if the propertyName indicated already has the errorConstant indicated, false otherwise 585 */ 586 protected boolean errorAlreadyExists(String propertyName, String errorConstant) { 587 if (GlobalVariables.getMessageMap().fieldHasMessage(propertyName, errorConstant)) { 588 return true; 589 } else { 590 return false; 591 } 592 } 593 594 /** 595 * This method specifically doesn't put any prefixes before the error so that the developer can do things specific 596 * to the 597 * globals errors (like newDelegateChangeDocument errors) 598 * 599 * @param propertyName 600 * @param errorConstant 601 */ 602 protected void putGlobalsError(String propertyName, String errorConstant) { 603 if (!errorAlreadyExists(propertyName, errorConstant)) { 604 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(propertyName, errorConstant); 605 } 606 } 607 608 /** 609 * This method specifically doesn't put any prefixes before the error so that the developer can do things specific 610 * to the 611 * globals errors (like newDelegateChangeDocument errors) 612 * 613 * @param propertyName 614 * @param errorConstant 615 * @param parameter 616 */ 617 protected void putGlobalsError(String propertyName, String errorConstant, String parameter) { 618 if (!errorAlreadyExists(propertyName, errorConstant)) { 619 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(propertyName, errorConstant, parameter); 620 } 621 } 622 623 /** 624 * This method is used to deal with error paths that are not what we expect them to be. This method, along with 625 * resumeErrorPath() are used to temporarily clear the errorPath, and then return it to the original state after 626 * the 627 * rule is 628 * executed. 629 * 630 * This method is called at the very beginning of rule enforcement and pulls a copy of the contents of the 631 * errorPath 632 * ArrayList 633 * to a local arrayList for temporary storage. 634 */ 635 protected void clearErrorPath() { 636 // add all the items from the global list to the local list 637 priorErrorPath.addAll(GlobalVariables.getMessageMap().getErrorPath()); 638 639 // clear the global list 640 GlobalVariables.getMessageMap().getErrorPath().clear(); 641 } 642 643 /** 644 * This method is used to deal with error paths that are not what we expect them to be. This method, along with 645 * clearErrorPath() 646 * are used to temporarily clear the errorPath, and then return it to the original state after the rule is 647 * executed. 648 * 649 * This method is called at the very end of the rule enforcement, and returns the temporarily stored copy of the 650 * errorPath to 651 * the global errorPath, so that no other classes are interrupted. 652 */ 653 protected void resumeErrorPath() { 654 // revert the global errorPath back to what it was when we entered this 655 // class 656 GlobalVariables.getMessageMap().getErrorPath().addAll(priorErrorPath); 657 } 658 659 /** 660 * Executes the DataDictionary Validation against the document. 661 * 662 * @param document 663 * @return true if it passes DD validation, false otherwise 664 */ 665 protected boolean dataDictionaryValidate(MaintenanceDocument document) { 666 LOG.debug("MaintenanceDocument validation beginning"); 667 boolean success = true; 668 // explicitly put the errorPath that the dictionaryValidationService 669 // requires 670 GlobalVariables.getMessageMap().addToErrorPath("document.newMaintainableObject"); 671 672 // document must have a newMaintainable object 673 Maintainable newMaintainable = document.getNewMaintainableObject(); 674 if (newMaintainable == null) { 675 GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject"); 676 throw new ValidationException( 677 "Maintainable object from Maintenance Document '" + document.getDocumentTitle() + 678 "' is null, unable to proceed."); 679 } 680 681 // document's newMaintainable must contain an object (ie, not null) 682 Object dataObject = newMaintainable.getDataObject(); 683 if (dataObject == null) { 684 GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject."); 685 throw new ValidationException("Maintainable's component business object is null."); 686 } 687 688 // check if there are errors in validating the business object 689 GlobalVariables.getMessageMap().addToErrorPath("dataObject"); 690 DictionaryValidationResult dictionaryValidationResult = getDictionaryValidationService().validate( 691 newDataObject); 692 if (dictionaryValidationResult.getNumberOfErrors() > 0) { 693 for (ConstraintValidationResult cvr : dictionaryValidationResult) { 694 if (cvr.getStatus() == ErrorLevel.ERROR) { 695 GlobalVariables.getMessageMap().putError(cvr.getAttributePath(), cvr.getErrorKey()); 696 } 697 } 698 } 699 700 // validate default existence checks 701 // TODO: Default existence checks need support for general data objects, see KULRICE-7666 702 success &= getDictionaryValidationService().validateDefaultExistenceChecks(dataObject); 703 GlobalVariables.getMessageMap().removeFromErrorPath("dataObject"); 704 705 // explicitly remove the errorPath we've added 706 GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject"); 707 708 LOG.debug("MaintenanceDocument validation ending"); 709 return success; 710 } 711 712 /** 713 * This method checks the two major cases that may violate primary key integrity. 714 * 715 * 1. Disallow changing of the primary keys on an EDIT maintenance document. Other fields can be changed, but once 716 * the primary 717 * keys have been set, they are permanent. 718 * 719 * 2. Disallow creating a new object whose primary key values are already present in the system on a CREATE NEW 720 * maintenance 721 * document. 722 * 723 * This method also will add new Errors to the Global Error Map. 724 * 725 * @param document - The Maintenance Document being tested. 726 * @return Returns false if either test failed, otherwise returns true. 727 */ 728 protected boolean primaryKeyCheck(MaintenanceDocument document) { 729 // default to success if no failures 730 boolean success = true; 731 Class<?> dataObjectClass = document.getNewMaintainableObject().getDataObjectClass(); 732 733 Object oldBo = document.getOldMaintainableObject().getDataObject(); 734 Object newDataObject = document.getNewMaintainableObject().getDataObject(); 735 736 // We don't do primaryKeyChecks on Bulk Update Data Object maintenance documents. This is 737 // because it doesn't really make any sense to do so, given the behavior of Bulk Update. When a 738 // Bulk Update Document completes, it will update or create a new record for each BO in the list. 739 // As a result, there's no problem with having existing BO records in the system, they will 740 // simply get updated. 741 if (newDataObject instanceof BulkUpdateMaintenanceDataObject) { 742 return success; 743 } 744 745 // fail and complain if the person has changed the primary keys on 746 // an EDIT maintenance document. 747 if (document.isEdit()) { 748 if (!getLegacyDataAdapter().equalsByPrimaryKeys(oldBo, newDataObject)) { 749 // add a complaint to the errors 750 putDocumentError(KRADConstants.DOCUMENT_ERRORS, 751 RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_PRIMARY_KEYS_CHANGED_ON_EDIT, 752 getHumanReadablePrimaryKeyFieldNames(dataObjectClass)); 753 success &= false; 754 } 755 } 756 757 // fail and complain if the person has selected a new object with keys that already exist 758 // in the DB. 759 else if (document.isNew()) { 760 761 // TODO: when/if we have standard support for DO retrieval, do this check for DO's 762 if (newDataObject instanceof PersistableBusinessObjectBaseAdapter) { 763 764 // get a map of the pk field names and values 765 Map<String, ?> newPkFields = getLegacyDataAdapter().getPrimaryKeyFieldValuesDOMDS(newDataObject); 766 767 //Remove any parts of the pk that has a null value as JPA will throw an error 768 Map<String, Object> filteredPkFields = new HashMap<String, Object>(); 769 filteredPkFields.putAll(newPkFields); 770 771 Iterator it = newPkFields.entrySet().iterator(); 772 while (it.hasNext()) { 773 Map.Entry pairs = (Map.Entry)it.next(); 774 if(pairs.getValue() == null){ 775 filteredPkFields.remove(pairs.getKey()); 776 } 777 } 778 779 if(!filteredPkFields.isEmpty()){ 780 // attempt to do a lookup, see if this object already exists by these Primary Keys 781 Object testBo = getLegacyDataAdapter().findByPrimaryKey(dataObjectClass, filteredPkFields); 782 783 // if the retrieve was successful, then this object already exists, and we need 784 // to complain 785 if (testBo != null) { 786 putDocumentError(KRADConstants.DOCUMENT_ERRORS, 787 RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_KEYS_ALREADY_EXIST_ON_CREATE_NEW, 788 getHumanReadablePrimaryKeyFieldNames(dataObjectClass)); 789 success &= false; 790 } 791 } 792 793 } 794 } 795 796 return success; 797 } 798 799 /** 800 * This method creates a human-readable string of the class' primary key field names, as designated by the 801 * DataDictionary. 802 * 803 * @param dataObjectClass 804 * @return human-readable string representation of the primary key field names 805 */ 806 protected String getHumanReadablePrimaryKeyFieldNames(Class<?> dataObjectClass) { 807 String delim = ""; 808 StringBuilder pkFieldNames = new StringBuilder(); 809 810 // get a list of all the primary key field names, walk through them 811 List<String> pkFields = getLegacyDataAdapter().listPrimaryKeyFieldNames(dataObjectClass); 812 for (Iterator<String> iter = pkFields.iterator(); iter.hasNext(); ) { 813 String pkFieldName = (String) iter.next(); 814 815 // TODO should this be getting labels from the view dictionary 816 // use the DataDictionary service to translate field name into human-readable label 817 String humanReadableFieldName = getDataDictionaryService().getAttributeLabel(dataObjectClass, pkFieldName); 818 819 // append the next field 820 pkFieldNames.append(delim + humanReadableFieldName); 821 822 // separate names with commas after the first one 823 if (delim.equalsIgnoreCase("")) { 824 delim = ", "; 825 } 826 } 827 828 return pkFieldNames.toString(); 829 } 830 831 /** 832 * This method enforces all business rules that are common to all maintenance documents which must be tested before 833 * doing an 834 * approval. 835 * 836 * It can be overloaded in special cases where a MaintenanceDocument has very special needs that would be contrary 837 * to what is 838 * enforced here. 839 * 840 * @param document - a populated MaintenanceDocument instance 841 * @return true if the document can be approved, false if not 842 */ 843 protected boolean processGlobalApproveDocumentBusinessRules(MaintenanceDocument document) { 844 return true; 845 } 846 847 /** 848 * This method enforces all business rules that are common to all maintenance documents which must be tested before 849 * doing a 850 * route. 851 * 852 * It can be overloaded in special cases where a MaintenanceDocument has very special needs that would be contrary 853 * to what is 854 * enforced here. 855 * 856 * @param document - a populated MaintenanceDocument instance 857 * @return true if the document can be routed, false if not 858 */ 859 protected boolean processGlobalRouteDocumentBusinessRules(MaintenanceDocument document) { 860 boolean success = true; 861 862 // require a document description field 863 success &= checkEmptyDocumentField( 864 KRADPropertyConstants.DOCUMENT_HEADER + "." + KRADPropertyConstants.DOCUMENT_DESCRIPTION, 865 document.getDocumentHeader().getDocumentDescription(), "Description"); 866 867 return success; 868 } 869 870 /** 871 * This method enforces all business rules that are common to all maintenance documents which must be tested before 872 * doing a 873 * save. 874 * 875 * It can be overloaded in special cases where a MaintenanceDocument has very special needs that would be contrary 876 * to what is 877 * enforced here. 878 * 879 * Note that although this method returns a true or false to indicate whether the save should happen or not, this 880 * result may not 881 * be followed by the calling method. In other words, the boolean result will likely be ignored, and the document 882 * saved, 883 * regardless. 884 * 885 * @param document - a populated MaintenanceDocument instance 886 * @return true if all business rules succeed, false if not 887 */ 888 protected boolean processGlobalSaveDocumentBusinessRules(MaintenanceDocument document) { 889 // default to success 890 boolean success = true; 891 892 // do generic checks that impact primary key violations 893 success &= primaryKeyCheck(document); 894 895 // this is happening only on the processSave, since a Save happens in both the 896 // Route and Save events. 897 success &= this.dataDictionaryValidate(document); 898 899 return success; 900 } 901 902 /** 903 * This method should be overridden to provide custom rules for processing document saving 904 * 905 * @param document 906 * @return boolean 907 */ 908 protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { 909 return true; 910 } 911 912 /** 913 * This method should be overridden to provide custom rules for processing document routing 914 * 915 * @param document 916 * @return boolean 917 */ 918 protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { 919 return true; 920 } 921 922 /** 923 * This method should be overridden to provide custom rules for processing document approval. 924 * 925 * @param document 926 * @return boolean 927 */ 928 protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) { 929 return true; 930 } 931 932 /** 933 * This method should be overridden to provide custom rules for processing adding collection lines. 934 * 935 * @param document 936 * @return boolean 937 */ 938 protected boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, Object line) { 939 return true; 940 } 941 942 // Document Validation Helper Methods 943 944 /** 945 * This method checks to see if the document is in a state that it can be saved without causing exceptions. 946 * 947 * Note that Business Rules are NOT enforced here, only validity checks. 948 * 949 * This method will only return false if the document is in such a state that routing it will cause 950 * RunTimeExceptions. 951 * 952 * @param maintenanceDocument - a populated MaintenaceDocument instance. 953 * @return boolean - returns true unless the object is in an invalid state. 954 */ 955 protected boolean isDocumentValidForSave(MaintenanceDocument maintenanceDocument) { 956 957 boolean success = true; 958 959 success &= super.isDocumentOverviewValid(maintenanceDocument); 960 success &= validateDocumentStructure((Document) maintenanceDocument); 961 success &= validateMaintenanceDocument(maintenanceDocument); 962 success &= validateBulkUpdateMaintenanceDocument(maintenanceDocument); 963 return success; 964 } 965 966 /** 967 * This method makes sure the document itself is valid, and has the necessary fields populated to be routable. 968 * 969 * This is not a business rules test, rather its a structure test to make sure that the document will not cause 970 * exceptions 971 * before routing. 972 * 973 * @param document - document to be tested 974 * @return false if the document is missing key values, true otherwise 975 */ 976 protected boolean validateDocumentStructure(Document document) { 977 boolean success = true; 978 979 // document must have a populated documentNumber 980 String documentHeaderId = document.getDocumentNumber(); 981 if (documentHeaderId == null || StringUtils.isEmpty(documentHeaderId)) { 982 throw new ValidationException("Document has no document number, unable to proceed."); 983 } 984 985 return success; 986 } 987 988 /** 989 * This method checks to make sure the document is a valid maintenanceDocument, and has the necessary values 990 * populated such that it will not cause exceptions in later routing or business rules testing. 991 * 992 * This is not a business rules test. 993 * 994 * @param maintenanceDocument - document to be tested 995 * @return whether maintenance doc passes 996 * @throws ValidationException 997 */ 998 protected boolean validateMaintenanceDocument(MaintenanceDocument maintenanceDocument) { 999 boolean success = true; 1000 Maintainable newMaintainable = maintenanceDocument.getNewMaintainableObject(); 1001 1002 // document must have a newMaintainable object 1003 if (newMaintainable == null) { 1004 throw new ValidationException( 1005 "Maintainable object from Maintenance Document '" + maintenanceDocument.getDocumentTitle() + 1006 "' is null, unable to proceed."); 1007 } 1008 1009 // document's newMaintainable must contain an object (ie, not null) 1010 if (newMaintainable.getDataObject() == null) { 1011 throw new ValidationException("Maintainable's component data object is null."); 1012 } 1013 1014 return success; 1015 } 1016 1017 /** 1018 * This method checks whether this maintenance document represents a bulk update maintenance document, and if so, 1019 * whether the data object is in a persistable state. 1020 * 1021 * @param maintenanceDocument- document to be tested 1022 * @return false for bulk update maintenance doc that are not persistable, true otherwise (incl. non-bulkupdate 1023 * maintenance documents) 1024 */ 1025 protected boolean validateBulkUpdateMaintenanceDocument(MaintenanceDocument maintenanceDocument) { 1026 boolean success = true; 1027 Maintainable newMaintainable = maintenanceDocument.getNewMaintainableObject(); 1028 1029 if (newMaintainable instanceof BulkUpdateMaintainable) { 1030 success = ((BulkUpdateMaintainable) newMaintainable).isPersistable(); 1031 } 1032 1033 return success; 1034 } 1035 1036 /** 1037 * This method tests to make sure the MaintenanceDocument passed in is based on the class you are expecting. 1038 * 1039 * It does this based on the NewMaintainableObject of the MaintenanceDocument. 1040 * 1041 * @param document - MaintenanceDocument instance you want to test 1042 * @param clazz - class you are expecting the MaintenanceDocument to be based on 1043 * @return true if they match, false if not 1044 */ 1045 protected boolean isCorrectMaintenanceClass(MaintenanceDocument document, Class clazz) { 1046 // disallow null arguments 1047 if (document == null || clazz == null) { 1048 throw new IllegalArgumentException("Null arguments were passed in."); 1049 } 1050 1051 // compare the class names 1052 if (clazz.toString().equals(document.getNewMaintainableObject().getDataObjectClass().toString())) { 1053 return true; 1054 } else { 1055 return false; 1056 } 1057 } 1058 1059 /** 1060 * This method accepts an object, and attempts to determine whether it is empty by this method's definition. 1061 * 1062 * OBJECT RESULT null false empty-string false whitespace false otherwise true 1063 * 1064 * If the result is false, it will add an object field error to the Global Errors. 1065 * 1066 * @param valueToTest - any object to test, usually a String 1067 * @param propertyName - the name of the property being tested 1068 * @return true or false, by the description above 1069 */ 1070 protected boolean checkEmptyBOField(String propertyName, Object valueToTest, String parameter) { 1071 boolean success = true; 1072 1073 success = checkEmptyValue(valueToTest); 1074 1075 // if failed, then add a field error 1076 if (!success) { 1077 putFieldError(propertyName, RiceKeyConstants.ERROR_REQUIRED, parameter); 1078 } 1079 1080 return success; 1081 } 1082 1083 /** 1084 * This method accepts document field (such as , and attempts to determine whether it is empty by this method's 1085 * definition. 1086 * 1087 * OBJECT RESULT null false empty-string false whitespace false otherwise true 1088 * 1089 * If the result is false, it will add document field error to the Global Errors. 1090 * 1091 * @param valueToTest - any object to test, usually a String 1092 * @param propertyName - the name of the property being tested 1093 * @return true or false, by the description above 1094 */ 1095 protected boolean checkEmptyDocumentField(String propertyName, Object valueToTest, String parameter) { 1096 boolean success = true; 1097 success = checkEmptyValue(valueToTest); 1098 if (!success) { 1099 putDocumentError(propertyName, RiceKeyConstants.ERROR_REQUIRED, parameter); 1100 } 1101 return success; 1102 } 1103 1104 /** 1105 * This method accepts document field (such as , and attempts to determine whether it is empty by this method's 1106 * definition. 1107 * 1108 * OBJECT RESULT null false empty-string false whitespace false otherwise true 1109 * 1110 * It will the result as a boolean 1111 * 1112 * @param valueToTest - any object to test, usually a String 1113 */ 1114 protected boolean checkEmptyValue(Object valueToTest) { 1115 boolean success = true; 1116 1117 // if its not a string, only fail if its a null object 1118 if (valueToTest == null) { 1119 success = false; 1120 } else { 1121 // test for null, empty-string, or whitespace if its a string 1122 if (valueToTest instanceof String) { 1123 if (StringUtils.isBlank((String) valueToTest)) { 1124 success = false; 1125 } 1126 } 1127 } 1128 1129 return success; 1130 } 1131 1132 /** 1133 * This method is used during debugging to dump the contents of the error map, including the key names. It is not 1134 * used by the 1135 * application in normal circumstances at all. 1136 */ 1137 protected void showErrorMap() { 1138 if (GlobalVariables.getMessageMap().hasNoErrors()) { 1139 return; 1140 } 1141 1142 for (Iterator i = GlobalVariables.getMessageMap().getAllPropertiesAndErrors().iterator(); i.hasNext(); ) { 1143 Map.Entry e = (Map.Entry) i.next(); 1144 1145 AutoPopulatingList errorList = (AutoPopulatingList) e.getValue(); 1146 for (Iterator j = errorList.iterator(); j.hasNext(); ) { 1147 ErrorMessage em = (ErrorMessage) j.next(); 1148 1149 if (em.getMessageParameters() == null) { 1150 LOG.error(e.getKey().toString() + " = " + em.getErrorKey()); 1151 } else { 1152 LOG.error(e.getKey().toString() + " = " + em.getErrorKey() + " : " + 1153 em.getMessageParameters().toString()); 1154 } 1155 } 1156 } 1157 } 1158 1159 /** 1160 * @see MaintenanceDocumentRule#setupBaseConvenienceObjects(org.kuali.rice.krad.maintenance.MaintenanceDocument) 1161 */ 1162 @Override 1163 public void setupBaseConvenienceObjects(MaintenanceDocument document) { 1164 // setup oldAccount convenience objects, make sure all possible sub-objects are populated 1165 oldDataObject = document.getOldMaintainableObject().getDataObject(); 1166 if (oldDataObject != null && oldDataObject instanceof PersistableBusinessObjectBaseAdapter) { 1167 ((PersistableBusinessObjectBaseAdapter) oldDataObject).refreshNonUpdateableReferences(); 1168 } 1169 1170 // setup newAccount convenience objects, make sure all possible sub-objects are populated 1171 newDataObject = document.getNewMaintainableObject().getDataObject(); 1172 if (newDataObject instanceof PersistableBusinessObjectBaseAdapter) { 1173 ((PersistableBusinessObjectBaseAdapter) newDataObject).refreshNonUpdateableReferences(); 1174 } 1175 1176 dataObjectClass = document.getNewMaintainableObject().getDataObjectClass(); 1177 1178 // call the setupConvenienceObjects in the subclass, if a subclass exists 1179 setupConvenienceObjects(); 1180 } 1181 1182 @Override 1183 public void setupConvenienceObjects() { 1184 // should always be overriden by subclass 1185 } 1186 1187 /** 1188 * This method checks to make sure that if the foreign-key fields for the given reference attributes have any 1189 * fields filled out,that all fields are filled out. 1190 * 1191 * If any are filled out, but all are not, it will return false and add a global error message about the problem. 1192 * 1193 * @param referenceName - The name of the reference object, whose foreign-key fields must be all-or-none filled 1194 * out. 1195 * @return true if this is the case, false if not 1196 */ 1197 protected boolean checkForPartiallyFilledOutReferenceForeignKeys(String referenceName) { 1198 boolean success = true; 1199 1200 ForeignKeyFieldsPopulationState fkFieldsState = getLegacyDataAdapter().getForeignKeyFieldsPopulationState( newDataObject, referenceName); 1201 1202 // determine result 1203 if (fkFieldsState.isAnyFieldsPopulated() && !fkFieldsState.isAllFieldsPopulated()) { 1204 success = false; 1205 1206 // add errors if appropriate 1207 1208 // get the full set of foreign-keys 1209 List fKeys = new ArrayList(getLegacyDataAdapter().getForeignKeysForReference( 1210 newDataObject.getClass(), referenceName).keySet()); 1211 String fKeysReadable = consolidateFieldNames(fKeys, ", ").toString(); 1212 1213 // walk through the missing fields 1214 for (Iterator iter = fkFieldsState.getUnpopulatedFieldNames().iterator(); iter.hasNext(); ) { 1215 String fieldName = (String) iter.next(); 1216 1217 // get the human-readable name 1218 String fieldNameReadable = getDataDictionaryService().getAttributeLabel(newDataObject.getClass(), 1219 fieldName); 1220 1221 // add a field error 1222 putFieldError(fieldName, RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_PARTIALLY_FILLED_OUT_REF_FKEYS, 1223 new String[]{fieldNameReadable, fKeysReadable}); 1224 } 1225 } 1226 1227 return success; 1228 } 1229 1230 /** 1231 * This method turns a list of field property names, into a delimited string of the human-readable names. 1232 * 1233 * @param fieldNames - List of fieldNames 1234 * @return A filled StringBuffer ready to go in an error message 1235 */ 1236 protected StringBuilder consolidateFieldNames(List<String> fieldNames, String delimiter) { 1237 StringBuilder sb = new StringBuilder(); 1238 1239 // setup some vars 1240 boolean firstPass = true; 1241 String delim = ""; 1242 1243 // walk through the list 1244 for (Iterator<String> iter = fieldNames.iterator(); iter.hasNext(); ) { 1245 String fieldName = (String) iter.next(); 1246 1247 // get the human-readable name 1248 // add the new one, with the appropriate delimiter 1249 sb.append(delim + getDataDictionaryService().getAttributeLabel(newDataObject.getClass(), fieldName)); 1250 1251 // after the first item, start using a delimiter 1252 if (firstPass) { 1253 delim = delimiter; 1254 firstPass = false; 1255 } 1256 } 1257 1258 return sb; 1259 } 1260 1261 /** 1262 * This method translates the passed in field name into a human-readable attribute label. 1263 * 1264 * It assumes the existing newDataObject's class as the class to examine the fieldName for. 1265 * 1266 * @param fieldName The fieldName you want a human-readable label for. 1267 * @return A human-readable label, pulled from the DataDictionary. 1268 */ 1269 protected String getFieldLabel(String fieldName) { 1270 return getDataDictionaryService().getAttributeLabel(newDataObject.getClass(), fieldName) + "(" + 1271 getDataDictionaryService().getAttributeShortLabel(newDataObject.getClass(), fieldName) + ")"; 1272 } 1273 1274 /** 1275 * This method translates the passed in field name into a human-readable attribute label. 1276 * 1277 * It assumes the existing newDataObject's class as the class to examine the fieldName for. 1278 * 1279 * @param dataObjectClass The class to use in combination with the fieldName. 1280 * @param fieldName The fieldName you want a human-readable label for. 1281 * @return A human-readable label, pulled from the DataDictionary. 1282 */ 1283 protected String getFieldLabel(Class<?> dataObjectClass, String fieldName) { 1284 return getDataDictionaryService().getAttributeLabel(dataObjectClass, fieldName) + "(" + 1285 getDataDictionaryService().getAttributeShortLabel(dataObjectClass, fieldName) + ")"; 1286 } 1287 1288 /** 1289 * Gets the newDataObject attribute. 1290 * 1291 * @return Returns the newDataObject. 1292 */ 1293 protected final Object getNewDataObject() { 1294 return newDataObject; 1295 } 1296 1297 protected void setNewDataObject(Object newDataObject) { 1298 this.newDataObject = newDataObject; 1299 } 1300 1301 /** 1302 * Gets the oldDataObject attribute. 1303 * 1304 * @return Returns the oldDataObject. 1305 */ 1306 protected final Object getOldDataObject() { 1307 return oldDataObject; 1308 } 1309 1310 protected final ConfigurationService getConfigService() { 1311 if (configService == null) { 1312 this.configService = CoreApiServiceLocator.getKualiConfigurationService(); 1313 } 1314 return configService; 1315 } 1316 1317 public final void setConfigService(ConfigurationService configService) { 1318 this.configService = configService; 1319 } 1320 1321 protected final DataDictionaryService getDdService() { 1322 if (ddService == null) { 1323 this.ddService = KRADServiceLocatorWeb.getDataDictionaryService(); 1324 } 1325 return ddService; 1326 } 1327 1328 public final void setDdService(DataDictionaryService ddService) { 1329 this.ddService = ddService; 1330 } 1331 1332 @Override 1333 protected final DictionaryValidationService getDictionaryValidationService() { 1334 if (dictionaryValidationService == null) { 1335 this.dictionaryValidationService = KRADServiceLocatorWeb.getDictionaryValidationService(); 1336 } 1337 return dictionaryValidationService; 1338 } 1339 1340 public final void setDictionaryValidationService(DictionaryValidationService dictionaryValidationService) { 1341 this.dictionaryValidationService = dictionaryValidationService; 1342 } 1343 1344 @Override 1345 public PersonService getPersonService() { 1346 if (personService == null) { 1347 this.personService = KimApiServiceLocator.getPersonService(); 1348 } 1349 return personService; 1350 } 1351 1352 public void setPersonService(PersonService personService) { 1353 this.personService = personService; 1354 } 1355 1356 public DateTimeService getDateTimeService() { 1357 return CoreApiServiceLocator.getDateTimeService(); 1358 } 1359 1360 protected RoleService getRoleService() { 1361 if (this.roleService == null) { 1362 this.roleService = KimApiServiceLocator.getRoleService(); 1363 } 1364 return this.roleService; 1365 } 1366 1367 public WorkflowDocumentService getWorkflowDocumentService() { 1368 if (workflowDocumentService == null) { 1369 this.workflowDocumentService = KRADServiceLocatorWeb.getWorkflowDocumentService(); 1370 } 1371 return workflowDocumentService; 1372 } 1373 1374 public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) { 1375 this.workflowDocumentService = workflowDocumentService; 1376 } 1377 1378 public DataObjectAuthorizationService getDataObjectAuthorizationService() { 1379 if (dataObjectAuthorizationService == null) { 1380 this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService(); 1381 } 1382 return dataObjectAuthorizationService; 1383 } 1384 1385 public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) { 1386 this.dataObjectAuthorizationService = dataObjectAuthorizationService; 1387 } 1388 1389 private LegacyDataAdapter getLegacyDataAdapter() { 1390 return KRADServiceLocatorWeb.getLegacyDataAdapter(); 1391 } 1392 1393 public DataObjectService getDataObjectService() { 1394 if ( dataObjectService == null ) { 1395 dataObjectService = KradDataServiceLocator.getDataObjectService(); 1396 } 1397 return dataObjectService; 1398 } 1399 1400} 1401