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