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