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