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