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