0001: /*
0002: * Copyright 2005-2007 The Kuali Foundation.
0003: *
0004: * Licensed under the Educational Community License, Version 1.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.opensource.org/licenses/ecl1.php
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.kuali.core.maintenance.rules;
0017:
0018: import java.util.ArrayList;
0019: import java.util.Collection;
0020: import java.util.Iterator;
0021: import java.util.List;
0022: import java.util.Map;
0023:
0024: import org.apache.commons.lang.StringUtils;
0025: import org.kuali.RiceConstants;
0026: import org.kuali.RiceKeyConstants;
0027: import org.kuali.core.authorization.FieldAuthorization;
0028: import org.kuali.core.bo.GlobalBusinessObject;
0029: import org.kuali.core.bo.PersistableBusinessObject;
0030: import org.kuali.core.bo.user.UniversalUser;
0031: import org.kuali.core.document.Document;
0032: import org.kuali.core.document.MaintenanceDocument;
0033: import org.kuali.core.document.authorization.MaintenanceDocumentAuthorizations;
0034: import org.kuali.core.document.authorization.MaintenanceDocumentAuthorizer;
0035: import org.kuali.core.exceptions.UnknownDocumentIdException;
0036: import org.kuali.core.exceptions.ValidationException;
0037: import org.kuali.core.maintenance.Maintainable;
0038: import org.kuali.core.rule.AddCollectionLineRule;
0039: import org.kuali.core.rule.event.ApproveDocumentEvent;
0040: import org.kuali.core.rules.DocumentRuleBase;
0041: import org.kuali.core.service.BusinessObjectDictionaryService;
0042: import org.kuali.core.service.BusinessObjectService;
0043: import org.kuali.core.service.DataDictionaryService;
0044: import org.kuali.core.service.DateTimeService;
0045: import org.kuali.core.service.DictionaryValidationService;
0046: import org.kuali.core.service.DocumentAuthorizationService;
0047: import org.kuali.core.service.DocumentService;
0048: import org.kuali.core.service.KualiConfigurationService;
0049: import org.kuali.core.service.MaintenanceDocumentDictionaryService;
0050: import org.kuali.core.service.PersistenceService;
0051: import org.kuali.core.service.PersistenceStructureService;
0052: import org.kuali.core.service.UniversalUserService;
0053: import org.kuali.core.util.ErrorMap;
0054: import org.kuali.core.util.ErrorMessage;
0055: import org.kuali.core.util.ForeignKeyFieldsPopulationState;
0056: import org.kuali.core.util.GlobalVariables;
0057: import org.kuali.core.util.ObjectUtils;
0058: import org.kuali.core.util.TypedArrayList;
0059: import org.kuali.core.workflow.service.KualiWorkflowDocument;
0060: import org.kuali.core.workflow.service.WorkflowDocumentService;
0061: import org.kuali.rice.KNSServiceLocator;
0062:
0063: import edu.iu.uis.eden.exception.WorkflowException;
0064:
0065: /**
0066: * This class contains all of the business rules that are common to all maintenance documents.
0067: *
0068: *
0069: */
0070: public class MaintenanceDocumentRuleBase extends DocumentRuleBase
0071: implements MaintenanceDocumentRule, AddCollectionLineRule {
0072: protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
0073: .getLogger(MaintenanceDocumentRuleBase.class);
0074:
0075: // public static final String CHART_MAINTENANCE_EDOC = "ChartMaintenanceEDoc";
0076:
0077: // these two constants are used to correctly prefix errors added to
0078: // the global errors
0079: public static final String MAINTAINABLE_ERROR_PREFIX = RiceConstants.MAINTENANCE_NEW_MAINTAINABLE;
0080: public static final String DOCUMENT_ERROR_PREFIX = "document.";
0081: public static final String MAINTAINABLE_ERROR_PATH = DOCUMENT_ERROR_PREFIX
0082: + "newMaintainableObject";
0083:
0084: protected PersistenceStructureService persistenceStructureService;
0085: protected PersistenceService persistenceService;
0086: protected DataDictionaryService ddService;
0087: protected BusinessObjectService boService;
0088: protected BusinessObjectDictionaryService boDictionaryService;
0089: protected DictionaryValidationService dictionaryValidationService;
0090: protected KualiConfigurationService configService;
0091: protected DocumentAuthorizationService documentAuthorizationService;
0092: protected MaintenanceDocumentDictionaryService maintDocDictionaryService;
0093: protected WorkflowDocumentService workflowDocumentService;
0094: protected UniversalUserService universalUserService;
0095:
0096: private PersistableBusinessObject oldBo;
0097: private PersistableBusinessObject newBo;
0098: private Class boClass;
0099:
0100: private List priorErrorPath;
0101:
0102: /**
0103: *
0104: * Default constructor a MaintenanceDocumentRuleBase.java.
0105: *
0106: */
0107: public MaintenanceDocumentRuleBase() {
0108:
0109: priorErrorPath = new ArrayList();
0110:
0111: // Pseudo-inject some services.
0112: //
0113: // This approach is being used to make it simpler to convert the Rule classes
0114: // to spring-managed with these services injected by Spring at some later date.
0115: // When this happens, just remove these calls to the setters with
0116: // SpringServiceLocator, and configure the bean defs for spring.
0117: try {
0118: this .setPersistenceStructureService(KNSServiceLocator
0119: .getPersistenceStructureService());
0120: this .setDdService(KNSServiceLocator
0121: .getDataDictionaryService());
0122: this .setPersistenceService(KNSServiceLocator
0123: .getPersistenceService());
0124: this .setBoService(KNSServiceLocator
0125: .getBusinessObjectService());
0126: this .setBoDictionaryService(KNSServiceLocator
0127: .getBusinessObjectDictionaryService());
0128: this .setDictionaryValidationService(KNSServiceLocator
0129: .getDictionaryValidationService());
0130: this .setConfigService(KNSServiceLocator
0131: .getKualiConfigurationService());
0132: this .setDocumentAuthorizationService(KNSServiceLocator
0133: .getDocumentAuthorizationService());
0134: this .setMaintDocDictionaryService(KNSServiceLocator
0135: .getMaintenanceDocumentDictionaryService());
0136: this .setWorkflowDocumentService(KNSServiceLocator
0137: .getWorkflowDocumentService());
0138: this .setUniversalUserService(KNSServiceLocator
0139: .getUniversalUserService());
0140: } catch (Exception ex) {
0141: // do nothing, avoid blowing up if called prior to spring initialization
0142: }
0143: }
0144:
0145: /**
0146: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRule#processSaveDocument(org.kuali.core.document.Document)
0147: */
0148: @Override
0149: public boolean processSaveDocument(Document document) {
0150:
0151: MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document;
0152:
0153: // remove all items from the errorPath temporarily (because it may not
0154: // be what we expect, or what we need)
0155: clearErrorPath();
0156:
0157: // setup convenience pointers to the old & new bo
0158: setupBaseConvenienceObjects(maintenanceDocument);
0159:
0160: // the document must be in a valid state for saving. this does not include business
0161: // rules, but just enough testing that the document is populated and in a valid state
0162: // to not cause exceptions when saved. if this passes, then the save will always occur,
0163: // regardless of business rules.
0164: if (!isDocumentValidForSave(maintenanceDocument)) {
0165: resumeErrorPath();
0166: return false;
0167: }
0168:
0169: // apply rules that are specific to the class of the maintenance document
0170: // (if implemented). this will always succeed if not overloaded by the
0171: // subclass
0172: processCustomSaveDocumentBusinessRules(maintenanceDocument);
0173:
0174: // return the original set of items to the errorPath
0175: resumeErrorPath();
0176:
0177: // return the original set of items to the errorPath, to ensure no impact
0178: // on other upstream or downstream items that rely on the errorPath
0179: return true;
0180: }
0181:
0182: /**
0183: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRule#processRouteDocument(org.kuali.core.document.Document)
0184: */
0185: @Override
0186: public boolean processRouteDocument(Document document) {
0187: LOG.info("processRouteDocument called");
0188:
0189: MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document;
0190:
0191: // remove all items from the errorPath temporarily (because it may not
0192: // be what we expect, or what we need)
0193: clearErrorPath();
0194:
0195: // setup convenience pointers to the old & new bo
0196: setupBaseConvenienceObjects(maintenanceDocument);
0197:
0198: // apply rules that are common across all maintenance documents, regardless of class
0199: processGlobalSaveDocumentBusinessRules(maintenanceDocument);
0200:
0201: // from here on, it is in a default-success mode, and will route unless one of the
0202: // business rules stop it.
0203: boolean success = true;
0204:
0205: // apply rules that are common across all maintenance documents, regardless of class
0206: success &= processGlobalRouteDocumentBusinessRules(maintenanceDocument);
0207:
0208: // apply rules that are specific to the class of the maintenance document
0209: // (if implemented). this will always succeed if not overloaded by the
0210: // subclass
0211: success &= processCustomRouteDocumentBusinessRules(maintenanceDocument);
0212:
0213: // return the original set of items to the errorPath, to ensure no impact
0214: // on other upstream or downstream items that rely on the errorPath
0215: resumeErrorPath();
0216:
0217: return success;
0218: }
0219:
0220: /**
0221: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRule#processApproveDocument(ApproveDocumentEvent)
0222: */
0223: @Override
0224: public boolean processApproveDocument(
0225: ApproveDocumentEvent approveEvent) {
0226:
0227: MaintenanceDocument maintenanceDocument = (MaintenanceDocument) approveEvent
0228: .getDocument();
0229:
0230: // remove all items from the errorPath temporarily (because it may not
0231: // be what we expect, or what we need)
0232: clearErrorPath();
0233:
0234: // setup convenience pointers to the old & new bo
0235: setupBaseConvenienceObjects(maintenanceDocument);
0236:
0237: // apply rules that are common across all maintenance documents, regardless of class
0238: processGlobalSaveDocumentBusinessRules(maintenanceDocument);
0239:
0240: // from here on, it is in a default-success mode, and will approve unless one of the
0241: // business rules stop it.
0242: boolean success = true;
0243:
0244: // apply rules that are common across all maintenance documents, regardless of class
0245: success &= processGlobalApproveDocumentBusinessRules(maintenanceDocument);
0246:
0247: // apply rules that are specific to the class of the maintenance document
0248: // (if implemented). this will always succeed if not overloaded by the
0249: // subclass
0250: success &= processCustomApproveDocumentBusinessRules(maintenanceDocument);
0251:
0252: // return the original set of items to the errorPath, to ensure no impact
0253: // on other upstream or downstream items that rely on the errorPath
0254: resumeErrorPath();
0255:
0256: return success;
0257: }
0258:
0259: /**
0260: *
0261: * This method is a convenience method to easily add a Document level error (ie, one not tied to a specific field, but
0262: * applicable to the whole document).
0263: *
0264: * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
0265: *
0266: */
0267: protected void putGlobalError(String errorConstant) {
0268: if (!errorAlreadyExists(RiceConstants.DOCUMENT_ERRORS,
0269: errorConstant)) {
0270: GlobalVariables.getErrorMap().putErrorWithoutFullErrorPath(
0271: RiceConstants.DOCUMENT_ERRORS, errorConstant);
0272: }
0273: }
0274:
0275: /**
0276: *
0277: * This method is a convenience method to easily add a Document level error (ie, one not tied to a specific field, but
0278: * applicable to the whole document).
0279: *
0280: * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
0281: * @param parameter - Replacement value for part of the error message.
0282: *
0283: */
0284: protected void putGlobalError(String errorConstant, String parameter) {
0285: if (!errorAlreadyExists(RiceConstants.DOCUMENT_ERRORS,
0286: errorConstant)) {
0287: GlobalVariables.getErrorMap().putErrorWithoutFullErrorPath(
0288: RiceConstants.DOCUMENT_ERRORS, errorConstant,
0289: parameter);
0290: }
0291: }
0292:
0293: /**
0294: *
0295: * This method is a convenience method to easily add a Document level error (ie, one not tied to a specific field, but
0296: * applicable to the whole document).
0297: *
0298: * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
0299: * @param parameters - Array of replacement values for part of the error message.
0300: *
0301: */
0302: protected void putGlobalError(String errorConstant,
0303: String[] parameters) {
0304: if (!errorAlreadyExists(RiceConstants.DOCUMENT_ERRORS,
0305: errorConstant)) {
0306: GlobalVariables.getErrorMap().putErrorWithoutFullErrorPath(
0307: RiceConstants.DOCUMENT_ERRORS, errorConstant,
0308: parameters);
0309: }
0310: }
0311:
0312: /**
0313: *
0314: * This method is a convenience method to add a property-specific error to the global errors list. This method makes sure that
0315: * the correct prefix is added to the property name so that it will display correctly on maintenance documents.
0316: *
0317: * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as errored in
0318: * the UI.
0319: * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
0320: *
0321: */
0322: protected void putFieldError(String propertyName,
0323: String errorConstant) {
0324: if (!errorAlreadyExists(MAINTAINABLE_ERROR_PREFIX
0325: + propertyName, errorConstant)) {
0326: GlobalVariables.getErrorMap().putErrorWithoutFullErrorPath(
0327: MAINTAINABLE_ERROR_PREFIX + propertyName,
0328: errorConstant);
0329: }
0330: }
0331:
0332: /**
0333: *
0334: * This method is a convenience method to add a property-specific error to the global errors list. This method makes sure that
0335: * the correct prefix is added to the property name so that it will display correctly on maintenance documents.
0336: *
0337: * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as errored in
0338: * the UI.
0339: * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
0340: * @param parameter - Single parameter value that can be used in the message so that you can display specific values to the
0341: * user.
0342: *
0343: */
0344: protected void putFieldError(String propertyName,
0345: String errorConstant, String parameter) {
0346: if (!errorAlreadyExists(MAINTAINABLE_ERROR_PREFIX
0347: + propertyName, errorConstant)) {
0348: GlobalVariables.getErrorMap().putErrorWithoutFullErrorPath(
0349: MAINTAINABLE_ERROR_PREFIX + propertyName,
0350: errorConstant, parameter);
0351: }
0352: }
0353:
0354: /**
0355: *
0356: * This method is a convenience method to add a property-specific error to the global errors list. This method makes sure that
0357: * the correct prefix is added to the property name so that it will display correctly on maintenance documents.
0358: *
0359: * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as errored in
0360: * the UI.
0361: * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
0362: * @param parameters - Array of strings holding values that can be used in the message so that you can display specific values
0363: * to the user.
0364: *
0365: */
0366: protected void putFieldError(String propertyName,
0367: String errorConstant, String[] parameters) {
0368: if (!errorAlreadyExists(MAINTAINABLE_ERROR_PREFIX
0369: + propertyName, errorConstant)) {
0370: GlobalVariables.getErrorMap().putErrorWithoutFullErrorPath(
0371: MAINTAINABLE_ERROR_PREFIX + propertyName,
0372: errorConstant, parameters);
0373: }
0374: }
0375:
0376: /**
0377: * Adds a property-specific error to the global errors list, with the DD short label as the single argument.
0378: *
0379: * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as errored in
0380: * the UI.
0381: * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
0382: */
0383: protected void putFieldErrorWithShortLabel(String propertyName,
0384: String errorConstant) {
0385: String shortLabel = ddService.getAttributeShortLabel(boClass,
0386: propertyName);
0387: putFieldError(propertyName, errorConstant, shortLabel);
0388: }
0389:
0390: /**
0391: *
0392: * This method is a convenience method to add a property-specific document error to the global errors list. This method makes
0393: * sure that the correct prefix is added to the property name so that it will display correctly on maintenance documents.
0394: *
0395: * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as errored in
0396: * the UI.
0397: * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
0398: * @param parameter - Single parameter value that can be used in the message so that you can display specific values to the
0399: * user.
0400: *
0401: */
0402: protected void putDocumentError(String propertyName,
0403: String errorConstant, String parameter) {
0404: if (!errorAlreadyExists(DOCUMENT_ERROR_PREFIX + propertyName,
0405: errorConstant)) {
0406: GlobalVariables.getErrorMap().putError(
0407: DOCUMENT_ERROR_PREFIX + propertyName,
0408: errorConstant, parameter);
0409: }
0410: }
0411:
0412: /**
0413: *
0414: * This method is a convenience method to add a property-specific document error to the global errors list. This method makes
0415: * sure that the correct prefix is added to the property name so that it will display correctly on maintenance documents.
0416: *
0417: * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as errored in
0418: * the UI.
0419: * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
0420: * @param parameters - Array of String parameters that can be used in the message so that you can display specific values to the
0421: * user.
0422: *
0423: */
0424: protected void putDocumentError(String propertyName,
0425: String errorConstant, String[] parameters) {
0426: GlobalVariables.getErrorMap().putError(
0427: DOCUMENT_ERROR_PREFIX + propertyName, errorConstant,
0428: parameters);
0429: }
0430:
0431: /**
0432: *
0433: * Convenience method to determine whether the field already has the message indicated.
0434: *
0435: * This is useful if you want to suppress duplicate error messages on the same field.
0436: *
0437: * @param propertyName - propertyName you want to test on
0438: * @param errorConstant - errorConstant you want to test
0439: * @return returns True if the propertyName indicated already has the errorConstant indicated, false otherwise
0440: *
0441: */
0442: private boolean errorAlreadyExists(String propertyName,
0443: String errorConstant) {
0444:
0445: if (GlobalVariables.getErrorMap().fieldHasMessage(propertyName,
0446: errorConstant)) {
0447: return true;
0448: } else {
0449: return false;
0450: }
0451: }
0452:
0453: /**
0454: *
0455: * This method specifically doesn't put any prefixes before the error so that the developer can do things specific to the
0456: * globals errors (like newDelegateChangeDocument errors)
0457: *
0458: * @param propertyName
0459: * @param errorConstant
0460: */
0461: protected void putGlobalsError(String propertyName,
0462: String errorConstant) {
0463: if (!errorAlreadyExists(propertyName, errorConstant)) {
0464: GlobalVariables.getErrorMap().putErrorWithoutFullErrorPath(
0465: propertyName, errorConstant);
0466: }
0467: }
0468:
0469: /**
0470: *
0471: * This method specifically doesn't put any prefixes before the error so that the developer can do things specific to the
0472: * globals errors (like newDelegateChangeDocument errors)
0473: *
0474: * @param propertyName
0475: * @param errorConstant
0476: * @param parameter
0477: */
0478: protected void putGlobalsError(String propertyName,
0479: String errorConstant, String parameter) {
0480: if (!errorAlreadyExists(propertyName, errorConstant)) {
0481: GlobalVariables.getErrorMap().putErrorWithoutFullErrorPath(
0482: propertyName, errorConstant, parameter);
0483: }
0484: }
0485:
0486: /**
0487: *
0488: * This method is used to deal with error paths that are not what we expect them to be. This method, along with
0489: * resumeErrorPath() are used to temporarily clear the errorPath, and then return it to the original state after the rule is
0490: * executed.
0491: *
0492: * This method is called at the very beginning of rule enforcement and pulls a copy of the contents of the errorPath ArrayList
0493: * to a local arrayList for temporary storage.
0494: *
0495: */
0496: protected void clearErrorPath() {
0497:
0498: // add all the items from the global list to the local list
0499: priorErrorPath.addAll(GlobalVariables.getErrorMap()
0500: .getErrorPath());
0501:
0502: // clear the global list
0503: GlobalVariables.getErrorMap().getErrorPath().clear();
0504:
0505: }
0506:
0507: /**
0508: *
0509: * This method is used to deal with error paths that are not what we expect them to be. This method, along with clearErrorPath()
0510: * are used to temporarily clear the errorPath, and then return it to the original state after the rule is executed.
0511: *
0512: * This method is called at the very end of the rule enforcement, and returns the temporarily stored copy of the errorPath to
0513: * the global errorPath, so that no other classes are interrupted.
0514: *
0515: */
0516: protected void resumeErrorPath() {
0517: // revert the global errorPath back to what it was when we entered this
0518: // class
0519: GlobalVariables.getErrorMap().getErrorPath().addAll(
0520: priorErrorPath);
0521: }
0522:
0523: /**
0524: *
0525: * This method executes the DataDictionary Validation against the document.
0526: *
0527: * @param document
0528: * @return true if it passes DD validation, false otherwise
0529: */
0530: protected boolean dataDictionaryValidate(
0531: MaintenanceDocument document) {
0532:
0533: LOG.debug("MaintenanceDocument validation beginning");
0534:
0535: // explicitly put the errorPath that the dictionaryValidationService requires
0536: GlobalVariables.getErrorMap().addToErrorPath(
0537: "document.newMaintainableObject");
0538:
0539: // document must have a newMaintainable object
0540: Maintainable newMaintainable = document
0541: .getNewMaintainableObject();
0542: if (newMaintainable == null) {
0543: GlobalVariables.getErrorMap().removeFromErrorPath(
0544: "document.newMaintainableObject");
0545: throw new ValidationException(
0546: "Maintainable object from Maintenance Document '"
0547: + document.getDocumentTitle()
0548: + "' is null, unable to proceed.");
0549: }
0550:
0551: // document's newMaintainable must contain an object (ie, not null)
0552: PersistableBusinessObject businessObject = newMaintainable
0553: .getBusinessObject();
0554: if (businessObject == null) {
0555: GlobalVariables.getErrorMap().removeFromErrorPath(
0556: "document.newMaintainableObject.");
0557: throw new ValidationException(
0558: "Maintainable's component business object is null.");
0559: }
0560:
0561: // run required check from maintenance data dictionary
0562: maintDocDictionaryService
0563: .validateMaintenanceRequiredFields(document);
0564:
0565: //check for duplicate entries in collections if necessary
0566: maintDocDictionaryService
0567: .validateMaintainableCollectionsForDuplicateEntries(document);
0568:
0569: // run the DD DictionaryValidation (non-recursive)
0570: dictionaryValidationService.validateBusinessObject(
0571: businessObject, false);
0572:
0573: // do default (ie, mandatory) existence checks
0574: dictionaryValidationService
0575: .validateDefaultExistenceChecks(businessObject);
0576:
0577: // do apc checks
0578: dictionaryValidationService.validateApcRules(businessObject);
0579:
0580: // explicitly remove the errorPath we've added
0581: GlobalVariables.getErrorMap().removeFromErrorPath(
0582: "document.newMaintainableObject");
0583:
0584: LOG.debug("MaintenanceDocument validation ending");
0585: return true;
0586: }
0587:
0588: /**
0589: *
0590: * This method checks the two major cases that may violate primary key integrity.
0591: *
0592: * 1. Disallow changing of the primary keys on an EDIT maintenance document. Other fields can be changed, but once the primary
0593: * keys have been set, they are permanent.
0594: *
0595: * 2. Disallow creating a new object whose primary key values are already present in the system on a CREATE NEW maintenance
0596: * document.
0597: *
0598: * This method also will add new Errors to the Global Error Map.
0599: *
0600: * @param document - The Maintenance Document being tested.
0601: * @return Returns false if either test failed, otherwise returns true.
0602: *
0603: */
0604: private boolean primaryKeyCheck(MaintenanceDocument document) {
0605:
0606: // default to success if no failures
0607: boolean success = true;
0608: Class boClass = document.getNewMaintainableObject()
0609: .getBoClass();
0610:
0611: PersistableBusinessObject oldBo = document
0612: .getOldMaintainableObject().getBusinessObject();
0613: PersistableBusinessObject newBo = document
0614: .getNewMaintainableObject().getBusinessObject();
0615:
0616: // We dont do primaryKeyChecks on Global Business Object maintenance documents. This is
0617: // because it doesnt really make any sense to do so, given the behavior of Globals. When a
0618: // Global Document completes, it will update or create a new record for each BO in the list.
0619: // As a result, there's no problem with having existing BO records in the system, they will
0620: // simply get updated.
0621: if (newBo instanceof GlobalBusinessObject) {
0622: return success;
0623: }
0624:
0625: // fail and complain if the person has changed the primary keys on
0626: // an EDIT maintenance document.
0627: if (document.isEdit()) {
0628: if (!ObjectUtils.equalByKeys(oldBo, newBo)) { // this is a very handy utility on our ObjectUtils
0629:
0630: // add a complaint to the errors
0631: putDocumentError(
0632: RiceConstants.DOCUMENT_ERRORS,
0633: RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_PRIMARY_KEYS_CHANGED_ON_EDIT,
0634: getHumanReadablePrimaryKeyFieldNames(boClass));
0635: success &= false;
0636: }
0637: }
0638:
0639: // fail and complain if the person has selected a new object with keys that already exist
0640: // in the DB.
0641: else if (document.isNew()) {
0642:
0643: // get a map of the pk field names and values
0644: Map newPkFields = persistenceService
0645: .getPrimaryKeyFieldValues(newBo);
0646:
0647: // TODO: Good suggestion from Aaron, dont bother checking the DB, if all of the
0648: // objects PK fields dont have values. If any are null or empty, then
0649: // we're done. The current way wont fail, but it will make a wasteful
0650: // DB call that may not be necessary, and we want to minimize these.
0651:
0652: // attempt to do a lookup, see if this object already exists by these Primary Keys
0653: PersistableBusinessObject testBo = boService
0654: .findByPrimaryKey(boClass, newPkFields);
0655:
0656: // if the retrieve was successful, then this object already exists, and we need
0657: // to complain
0658: if (testBo != null) {
0659: putDocumentError(
0660: RiceConstants.DOCUMENT_ERRORS,
0661: RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_KEYS_ALREADY_EXIST_ON_CREATE_NEW,
0662: getHumanReadablePrimaryKeyFieldNames(boClass));
0663: success &= false;
0664: }
0665: }
0666: return success;
0667: }
0668:
0669: /**
0670: *
0671: * This method creates a human-readable string of the class' primary key field names, as designated by the DataDictionary.
0672: *
0673: * @param boClass
0674: * @return
0675: */
0676: private String getHumanReadablePrimaryKeyFieldNames(Class boClass) {
0677:
0678: String delim = "";
0679: StringBuffer pkFieldNames = new StringBuffer();
0680:
0681: // get a list of all the primary key field names, walk through them
0682: List pkFields = persistenceStructureService
0683: .getPrimaryKeys(boClass);
0684: for (Iterator iter = pkFields.iterator(); iter.hasNext();) {
0685: String pkFieldName = (String) iter.next();
0686:
0687: // use the DataDictionary service to translate field name into human-readable label
0688: String humanReadableFieldName = ddService
0689: .getAttributeLabel(boClass, pkFieldName);
0690:
0691: // append the next field
0692: pkFieldNames.append(delim + humanReadableFieldName);
0693:
0694: // separate names with commas after the first one
0695: if (delim.equalsIgnoreCase("")) {
0696: delim = ", ";
0697: }
0698: }
0699:
0700: return pkFieldNames.toString();
0701: }
0702:
0703: /**
0704: * This method enforces all business rules that are common to all maintenance documents which must be tested before doing an
0705: * approval.
0706: *
0707: * It can be overloaded in special cases where a MaintenanceDocument has very special needs that would be contrary to what is
0708: * enforced here.
0709: *
0710: * @param document - a populated MaintenanceDocument instance
0711: * @return true if the document can be approved, false if not
0712: */
0713: protected boolean processGlobalApproveDocumentBusinessRules(
0714: MaintenanceDocument document) {
0715: boolean success = true;
0716:
0717: // enforce authorization restrictions on fields
0718: success &= checkAuthorizationRestrictions(document);
0719: return success;
0720: }
0721:
0722: /**
0723: *
0724: * This method enforces all business rules that are common to all maintenance documents which must be tested before doing a
0725: * route.
0726: *
0727: * It can be overloaded in special cases where a MaintenanceDocument has very special needs that would be contrary to what is
0728: * enforced here.
0729: *
0730: * @param document - a populated MaintenanceDocument instance
0731: * @return true if the document can be routed, false if not
0732: */
0733: protected boolean processGlobalRouteDocumentBusinessRules(
0734: MaintenanceDocument document) {
0735:
0736: boolean success = true;
0737:
0738: // require a document description field
0739: success &= checkEmptyDocumentField(
0740: "documentHeader.financialDocumentDescription", document
0741: .getDocumentHeader()
0742: .getFinancialDocumentDescription(),
0743: "Description");
0744:
0745: // enforce authorization restrictions on fields
0746: success &= checkAuthorizationRestrictions(document);
0747: return success;
0748: }
0749:
0750: /**
0751: *
0752: * This method enforces all business rules that are common to all maintenance documents which must be tested before doing a
0753: * save.
0754: *
0755: * It can be overloaded in special cases where a MaintenanceDocument has very special needs that would be contrary to what is
0756: * enforced here.
0757: *
0758: * Note that although this method returns a true or false to indicate whether the save should happen or not, this result may not
0759: * be followed by the calling method. In other words, the boolean result will likely be ignored, and the document saved,
0760: * regardless.
0761: *
0762: * @param document - a populated MaintenanceDocument instance
0763: * @return true if all business rules succeed, false if not
0764: */
0765: protected boolean processGlobalSaveDocumentBusinessRules(
0766: MaintenanceDocument document) {
0767:
0768: // default to success
0769: boolean success = true;
0770:
0771: // do generic checks that impact primary key violations
0772: primaryKeyCheck(document);
0773:
0774: // this is happening only on the processSave, since a Save happens in both the
0775: // Route and Save events.
0776: this .dataDictionaryValidate(document);
0777:
0778: // enforce authorization restrictions on fields
0779: checkAuthorizationRestrictions(document);
0780:
0781: return success;
0782: }
0783:
0784: /**
0785: * This method should be overridden to provide custom rules for processing document saving
0786: *
0787: * @param document
0788: * @return boolean
0789: */
0790: protected boolean processCustomSaveDocumentBusinessRules(
0791: MaintenanceDocument document) {
0792: return true;
0793: }
0794:
0795: /**
0796: *
0797: * This method should be overridden to provide custom rules for processing document routing
0798: *
0799: * @param document
0800: * @return boolean
0801: */
0802: protected boolean processCustomRouteDocumentBusinessRules(
0803: MaintenanceDocument document) {
0804: return true;
0805: }
0806:
0807: /**
0808: * This method should be overridden to provide custom rules for processing document approval.
0809: *
0810: * @param document
0811: * @return booelan
0812: */
0813: protected boolean processCustomApproveDocumentBusinessRules(
0814: MaintenanceDocument document) {
0815: return true;
0816: }
0817:
0818: // Document Validation Helper Methods
0819:
0820: /**
0821: *
0822: * This method checks to see if the document is in a state that it can be saved without causing exceptions.
0823: *
0824: * Note that Business Rules are NOT enforced here, only validity checks.
0825: *
0826: * This method will only return false if the document is in such a state that routing it will cause RunTimeExceptions.
0827: *
0828: * @param maintenanceDocument - a populated MaintenaceDocument instance.
0829: * @return boolean - returns true unless the object is in an invalid state.
0830: *
0831: */
0832: protected boolean isDocumentValidForSave(
0833: MaintenanceDocument maintenanceDocument) {
0834:
0835: boolean success = true;
0836:
0837: success &= super .isDocumentOverviewValid(maintenanceDocument);
0838: success &= validateDocumentStructure((Document) maintenanceDocument);
0839: success &= validateMaintenanceDocument(maintenanceDocument);
0840: success &= validateGlobalBusinessObjectPersistable(maintenanceDocument);
0841: return success;
0842: }
0843:
0844: /**
0845: *
0846: * This method makes sure the document itself is valid, and has the necessary fields populated to be routable.
0847: *
0848: * This is not a business rules test, rather its a structure test to make sure that the document will not cause exceptions
0849: * before routing.
0850: *
0851: * @param document - document to be tested
0852: * @return false if the document is missing key values, true otherwise
0853: */
0854: protected boolean validateDocumentStructure(Document document) {
0855: boolean success = true;
0856:
0857: // document must have a populated documentNumber
0858: String documentHeaderId = document.getDocumentNumber();
0859: if (documentHeaderId == null
0860: || StringUtils.isEmpty(documentHeaderId)) {
0861: throw new ValidationException(
0862: "Document has no document number, unable to proceed.");
0863: }
0864:
0865: return success;
0866: }
0867:
0868: /**
0869: *
0870: * This method checks to make sure the document is a valid maintenanceDocument, and has the necessary values populated such that
0871: * it will not cause exceptions in later routing or business rules testing.
0872: *
0873: * This is not a business rules test.
0874: *
0875: * @param maintenanceDocument - document to be tested
0876: * @return whether maintenance doc passes
0877: * @throws ValidationException
0878: */
0879: protected boolean validateMaintenanceDocument(
0880: MaintenanceDocument maintenanceDocument) {
0881: boolean success = true;
0882: Maintainable newMaintainable = maintenanceDocument
0883: .getNewMaintainableObject();
0884:
0885: // document must have a newMaintainable object
0886: if (newMaintainable == null) {
0887: throw new ValidationException(
0888: "Maintainable object from Maintenance Document '"
0889: + maintenanceDocument.getDocumentTitle()
0890: + "' is null, unable to proceed.");
0891: }
0892:
0893: // document's newMaintainable must contain an object (ie, not null)
0894: if (newMaintainable.getBusinessObject() == null) {
0895: throw new ValidationException(
0896: "Maintainable's component business object is null.");
0897: }
0898:
0899: // document's newMaintainable must contain a valid BusinessObject descendent
0900: if (!PersistableBusinessObject.class
0901: .isAssignableFrom(newMaintainable.getBoClass())) {
0902: throw new ValidationException(
0903: "Maintainable's component object is not descended from BusinessObject.");
0904: }
0905: return success;
0906: }
0907:
0908: /**
0909: *
0910: * This method checks whether this maint doc contains Global Business Objects, and if so, whether the GBOs are in a persistable
0911: * state. This will return false if this method determines that the GBO will cause a SQL Exception when the document is
0912: * persisted.
0913: *
0914: * @param document
0915: * @return False when the method determines that the contained Global Business Object will cause a SQL Exception, and the
0916: * document should not be saved. It will return True otherwise.
0917: *
0918: */
0919: protected boolean validateGlobalBusinessObjectPersistable(
0920: MaintenanceDocument document) {
0921: boolean success = true;
0922:
0923: if (document.getNewMaintainableObject() == null) {
0924: return success;
0925: }
0926: if (document.getNewMaintainableObject().getBusinessObject() == null) {
0927: return success;
0928: }
0929: if (!(document.getNewMaintainableObject().getBusinessObject() instanceof GlobalBusinessObject)) {
0930: return success;
0931: }
0932:
0933: PersistableBusinessObject bo = (PersistableBusinessObject) document
0934: .getNewMaintainableObject().getBusinessObject();
0935: GlobalBusinessObject gbo = (GlobalBusinessObject) bo;
0936: return gbo.isPersistable();
0937: }
0938:
0939: /**
0940: *
0941: * This method tests to make sure the MaintenanceDocument passed in is based on the class you are expecting.
0942: *
0943: * It does this based on the NewMaintainableObject of the MaintenanceDocument.
0944: *
0945: * @param document - MaintenanceDocument instance you want to test
0946: * @param clazz - class you are expecting the MaintenanceDocument to be based on
0947: * @return true if they match, false if not
0948: *
0949: */
0950: protected boolean isCorrectMaintenanceClass(
0951: MaintenanceDocument document, Class clazz) {
0952:
0953: // disallow null arguments
0954: if (document == null || clazz == null) {
0955: throw new IllegalArgumentException(
0956: "Null arguments were passed in.");
0957: }
0958:
0959: // compare the class names
0960: if (clazz.toString().equals(
0961: document.getNewMaintainableObject().getBoClass()
0962: .toString())) {
0963: return true;
0964: } else {
0965: return false;
0966: }
0967: }
0968:
0969: /**
0970: *
0971: * This method accepts an object, and attempts to determine whether it is empty by this method's definition.
0972: *
0973: * OBJECT RESULT null false empty-string false whitespace false otherwise true
0974: *
0975: * If the result is false, it will add an object field error to the Global Errors.
0976: *
0977: * @param valueToTest - any object to test, usually a String
0978: * @param propertyName - the name of the property being tested
0979: * @return true or false, by the description above
0980: *
0981: */
0982: protected boolean checkEmptyBOField(String propertyName,
0983: Object valueToTest, String parameter) {
0984:
0985: boolean success = true;
0986:
0987: success = checkEmptyValue(valueToTest);
0988:
0989: // if failed, then add a field error
0990: if (!success) {
0991: putFieldError(propertyName,
0992: RiceKeyConstants.ERROR_REQUIRED, parameter);
0993: }
0994:
0995: return success;
0996: }
0997:
0998: /**
0999: *
1000: * This method accepts document field (such as , and attempts to determine whether it is empty by this method's definition.
1001: *
1002: * OBJECT RESULT null false empty-string false whitespace false otherwise true
1003: *
1004: * If the result is false, it will add document field error to the Global Errors.
1005: *
1006: * @param valueToTest - any object to test, usually a String
1007: * @param propertyName - the name of the property being tested
1008: * @return true or false, by the description above
1009: *
1010: */
1011: protected boolean checkEmptyDocumentField(String propertyName,
1012: Object valueToTest, String parameter) {
1013: boolean success = true;
1014: success = checkEmptyValue(valueToTest);
1015: if (!success) {
1016: putDocumentError(propertyName,
1017: RiceKeyConstants.ERROR_REQUIRED, parameter);
1018: }
1019: return success;
1020: }
1021:
1022: /**
1023: *
1024: * This method accepts document field (such as , and attempts to determine whether it is empty by this method's definition.
1025: *
1026: * OBJECT RESULT null false empty-string false whitespace false otherwise true
1027: *
1028: * It will the result as a boolean
1029: *
1030: * @param valueToTest - any object to test, usually a String
1031: *
1032: */
1033: protected boolean checkEmptyValue(Object valueToTest) {
1034: boolean success = true;
1035:
1036: // if its not a string, only fail if its a null object
1037: if (valueToTest == null) {
1038: success = false;
1039: } else {
1040: // test for null, empty-string, or whitespace if its a string
1041: if (valueToTest instanceof String) {
1042: if (StringUtils.isBlank((String) valueToTest)) {
1043: success = false;
1044: }
1045: }
1046: }
1047:
1048: return success;
1049: }
1050:
1051: /**
1052: *
1053: * This method is used during debugging to dump the contents of the error map, including the key names. It is not used by the
1054: * application in normal circumstances at all.
1055: *
1056: */
1057: private void showErrorMap() {
1058:
1059: if (GlobalVariables.getErrorMap().isEmpty()) {
1060: return;
1061: }
1062:
1063: for (Iterator i = GlobalVariables.getErrorMap().entrySet()
1064: .iterator(); i.hasNext();) {
1065: Map.Entry e = (Map.Entry) i.next();
1066:
1067: TypedArrayList errorList = (TypedArrayList) e.getValue();
1068: for (Iterator j = errorList.iterator(); j.hasNext();) {
1069: ErrorMessage em = (ErrorMessage) j.next();
1070:
1071: if (em.getMessageParameters() == null) {
1072: LOG.error(e.getKey().toString() + " = "
1073: + em.getErrorKey());
1074: } else {
1075: LOG.error(e.getKey().toString() + " = "
1076: + em.getErrorKey() + " : "
1077: + em.getMessageParameters().toString());
1078: }
1079: }
1080: }
1081:
1082: }
1083:
1084: /**
1085: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRule#setupBaseConvenienceObjects(MaintenanceDocument)
1086: */
1087: public void setupBaseConvenienceObjects(MaintenanceDocument document) {
1088:
1089: // setup oldAccount convenience objects, make sure all possible sub-objects are populated
1090: oldBo = (PersistableBusinessObject) document
1091: .getOldMaintainableObject().getBusinessObject();
1092: if (oldBo != null) {
1093: oldBo.refreshNonUpdateableReferences();
1094: }
1095:
1096: // setup newAccount convenience objects, make sure all possible sub-objects are populated
1097: newBo = (PersistableBusinessObject) document
1098: .getNewMaintainableObject().getBusinessObject();
1099: newBo.refreshNonUpdateableReferences();
1100:
1101: boClass = document.getNewMaintainableObject().getBoClass();
1102:
1103: // call the setupConvenienceObjects in the subclass, if a subclass exists
1104: setupConvenienceObjects();
1105: }
1106:
1107: public void setupConvenienceObjects() {
1108: // should always be overriden by subclass
1109: }
1110:
1111: /**
1112: *
1113: * This method ensures that any fields that are restricted by the Authorization system in the system are also enforced on the
1114: * back-end, otherwise form manipulation could bypass authorization rules.
1115: *
1116: * This method will add errors to the Global ErrorMap if any problems are encountered.
1117: *
1118: * @param document - the maintenance document being evaluated
1119: * @return true if no failures occurred, false otherwise
1120: */
1121: protected boolean checkAuthorizationRestrictions(
1122: MaintenanceDocument document) {
1123:
1124: // Note that this method does what may at first appear to be a highly efficient loading
1125: // of the document that is already loaded, and compares against that. This is done to handle
1126: // situations where someone along the chain of approvers has rights to modify some fields, but
1127: // later approvers do not have similar rights. This is how we've made sure that this person,
1128: // only during this modification of the document, has not changed any fields on the newBo side,
1129: // without wiring something into the Struts that somehow tells us that a field was modified.
1130: // There may indeed be better ways to do this, but make sure you're solving all the problems
1131: // here, including ones like this: KULCOA-924, KULCOA-884, etc
1132:
1133: boolean success = true;
1134: boolean changed = false;
1135:
1136: boolean isInitiator = false;
1137: boolean isApprover = false;
1138:
1139: Object oldValue = null;
1140: Object newValue = null;
1141: Object savedValue = null;
1142:
1143: KualiWorkflowDocument workflowDocument = null;
1144: UniversalUser user = GlobalVariables.getUserSession()
1145: .getUniversalUser();
1146: try {
1147: workflowDocument = getWorkflowDocumentService()
1148: .createWorkflowDocument(
1149: Long.valueOf(document.getDocumentNumber()),
1150: user);
1151: } catch (WorkflowException e) {
1152: throw new UnknownDocumentIdException(
1153: "no document found for documentHeaderId '"
1154: + document.getDocumentNumber() + "'", e);
1155: }
1156: if (user.getPersonUserIdentifier().equalsIgnoreCase(
1157: workflowDocument.getInitiatorNetworkId())) {
1158: // if these are the same person then we know it is the initiator
1159: isInitiator = true;
1160: } else if (workflowDocument.isApprovalRequested()) {
1161: isApprover = true;
1162: }
1163:
1164: // get the correct documentAuthorizer for this document
1165: MaintenanceDocumentAuthorizer documentAuthorizer = (MaintenanceDocumentAuthorizer) documentAuthorizationService
1166: .getDocumentAuthorizer(document);
1167:
1168: // get a new instance of MaintenanceDocumentAuthorizations for this context
1169: MaintenanceDocumentAuthorizations auths = documentAuthorizer
1170: .getFieldAuthorizations(document, user);
1171:
1172: // load a temp copy of the document from the DB to compare to for changes
1173: MaintenanceDocument savedDoc = null;
1174: Maintainable savedNewMaintainable = null;
1175: PersistableBusinessObject savedNewBo = null;
1176:
1177: if (isApprover) {
1178: try {
1179: DocumentService docService = KNSServiceLocator
1180: .getDocumentService();
1181: savedDoc = (MaintenanceDocument) docService
1182: .getByDocumentHeaderId(document
1183: .getDocumentNumber());
1184: } catch (WorkflowException e) {
1185: throw new RuntimeException(
1186: "A WorkflowException was thrown which prevented the loading of "
1187: + "the comparison document ("
1188: + document.getDocumentNumber() + ")", e);
1189: }
1190:
1191: // attempt to retrieve the BO, but leave it blank if it or any of the objects on the path
1192: // to it are blank
1193: if (savedDoc != null) {
1194: savedNewMaintainable = savedDoc
1195: .getNewMaintainableObject();
1196: if (savedNewMaintainable != null) {
1197: savedNewBo = savedNewMaintainable
1198: .getBusinessObject();
1199: }
1200: }
1201: }
1202:
1203: // setup in-loop members
1204: FieldAuthorization fieldAuthorization = null;
1205:
1206: // walk through all the restrictions
1207: Collection restrictedFields = auths.getAuthFieldNames();
1208: for (Iterator iter = restrictedFields.iterator(); iter
1209: .hasNext();) {
1210: String fieldName = (String) iter.next();
1211:
1212: // get the specific field authorization structure
1213: fieldAuthorization = auths
1214: .getAuthFieldAuthorization(fieldName);
1215:
1216: // if there are any restrictions, then enforce them
1217: if (fieldAuthorization.isRestricted()) {
1218: // reset the changed flag
1219: changed = false;
1220:
1221: // new value should always be the same regardles of who is
1222: // making the request
1223: newValue = ObjectUtils.getNestedValue(newBo, fieldName);
1224:
1225: // first we need to handle the case of edit doc && initiator
1226: if (isInitiator && document.isEdit()) {
1227: // old value must equal new value
1228: oldValue = ObjectUtils.getNestedValue(oldBo,
1229: fieldName);
1230: } else if (isApprover && savedNewBo != null) {
1231: oldValue = ObjectUtils.getNestedValue(savedNewBo,
1232: fieldName);
1233: }
1234:
1235: // check to make sure nothing has changed
1236: if (oldValue == null && newValue == null) {
1237: changed = false;
1238: } else if ((oldValue == null && newValue != null)
1239: || (oldValue != null && newValue == null)) {
1240: changed = true;
1241: } else if (oldValue != null && newValue != null) {
1242: if (!oldValue.equals(newValue)) {
1243: changed = true;
1244: }
1245: }
1246:
1247: // if changed and a NEW doc, but the new value is the default value, then let it go
1248: // we dont allow changing to default values for EDIT docs though, only NEW
1249: if (changed && document.isNew()) {
1250: String defaultValue = maintDocDictionaryService
1251: .getFieldDefaultValue(document
1252: .getNewMaintainableObject()
1253: .getBoClass(), fieldName);
1254:
1255: // get the string value of newValue
1256: String newStringValue = newValue.toString();
1257:
1258: // if the newValue is the default value, then ignore
1259: if (newStringValue.equalsIgnoreCase(defaultValue)) {
1260: changed = false;
1261: }
1262: }
1263:
1264: // if anything has changed, complain
1265: if (changed) {
1266: String humanReadableFieldName = ddService
1267: .getAttributeLabel(document
1268: .getNewMaintainableObject()
1269: .getBoClass(), fieldName);
1270: putFieldError(
1271: fieldName,
1272: RiceKeyConstants.ERROR_DOCUMENT_AUTHORIZATION_RESTRICTED_FIELD_CHANGED,
1273: humanReadableFieldName);
1274: success &= false;
1275: }
1276: }
1277: }
1278: return success;
1279: }
1280:
1281: /**
1282: *
1283: * This method checks to make sure that if the foreign-key fields for the given reference attributes have any fields filled out,
1284: * that all fields are filled out.
1285: *
1286: * If any are filled out, but all are not, it will return false and add a global error message about the problem.
1287: *
1288: * @param referenceName - The name of the reference object, whose foreign-key fields must be all-or-none filled out.
1289: *
1290: * @return true if this is the case, false if not
1291: *
1292: */
1293: protected boolean checkForPartiallyFilledOutReferenceForeignKeys(
1294: String referenceName) {
1295:
1296: boolean success;
1297:
1298: ForeignKeyFieldsPopulationState fkFieldsState;
1299: fkFieldsState = persistenceStructureService
1300: .getForeignKeyFieldsPopulationState(newBo,
1301: referenceName);
1302:
1303: // determine result
1304: if (fkFieldsState.isAnyFieldsPopulated()
1305: && !fkFieldsState.isAllFieldsPopulated()) {
1306: success = false;
1307: } else {
1308: success = true;
1309: }
1310:
1311: // add errors if appropriate
1312: if (!success) {
1313:
1314: // get the full set of foreign-keys
1315: List fKeys = new ArrayList(persistenceStructureService
1316: .getForeignKeysForReference(newBo.getClass(),
1317: referenceName).keySet());
1318: String fKeysReadable = consolidateFieldNames(fKeys, ", ")
1319: .toString();
1320:
1321: // walk through the missing fields
1322: for (Iterator iter = fkFieldsState
1323: .getUnpopulatedFieldNames().iterator(); iter
1324: .hasNext();) {
1325: String fieldName = (String) iter.next();
1326:
1327: // get the human-readable name
1328: String fieldNameReadable = ddService.getAttributeLabel(
1329: newBo.getClass(), fieldName);
1330:
1331: // add a field error
1332: putFieldError(
1333: fieldName,
1334: RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_PARTIALLY_FILLED_OUT_REF_FKEYS,
1335: new String[] { fieldNameReadable, fKeysReadable });
1336: }
1337: }
1338:
1339: return success;
1340: }
1341:
1342: /**
1343: *
1344: * This method is a shallow inverse wrapper around applyApcRule, it simply reverses the return value, for better readability in
1345: * an if test.
1346: *
1347: * This method applies an APC rule based on the values provided.
1348: *
1349: * It will throw an ApplicationParameterException if the APC Group and Parm do not exist in the system.
1350: *
1351: * @param apcGroupName - The script or group name in the APC system. If the value is null or blank, an IllegalArgumentException
1352: * will be thrown.
1353: * @param parameterName - The name of the parm/rule in the APC system. If the value is null or blank, an IllegalArgumentException
1354: * will be thrown.
1355: * @param valueToTest - The String value to test against the APC rule. The value may be null or blank without throwing an error,
1356: * but the rule will likely fail if null or blank.
1357: * @return True if the rule fails, False if the rule passes.
1358: *
1359: */
1360: protected boolean apcRuleFails(String parameterNamespace,
1361: String parameterDetailTypeCode, String parameterName,
1362: String valueToTest) {
1363: if (applyApcRule(parameterNamespace, parameterDetailTypeCode,
1364: parameterName, valueToTest) == false) {
1365: return true;
1366: }
1367: return false;
1368: }
1369:
1370: /**
1371: *
1372: * This method applies an APC rule based on the values provided.
1373: *
1374: * It will throw an ApplicationParameterException if the APC Group and Parm do not exist in the system.
1375: *
1376: * @param apcGroupName - The script or group name in the APC system. If the value is null or blank, an IllegalArgumentException
1377: * will be thrown.
1378: * @param parameterName - The name of the parm/rule in the APC system. If the value is null or blank, an IllegalArgumentException
1379: * will be thrown.
1380: * @param valueToTest - The String value to test against the APC rule. The value may be null or blank without throwing an error,
1381: * but the rule will likely fail if null or blank.
1382: * @return True if the rule passes, False if the rule fails.
1383: *
1384: */
1385: protected boolean applyApcRule(String parameterNamespace,
1386: String parameterDetailTypeCode, String parameterName,
1387: String valueToTest) {
1388:
1389: // default to success
1390: boolean success = true;
1391:
1392: // apply the rule, see if it fails
1393: if (!configService.evaluateConstrainedValue(parameterNamespace,
1394: parameterDetailTypeCode, parameterName, valueToTest)) {
1395: success = false;
1396: }
1397:
1398: return success;
1399: }
1400:
1401: /**
1402: *
1403: * This method turns a list of field property names, into a delimited string of the human-readable names.
1404: *
1405: * @param fieldNames - List of fieldNames
1406: * @return A filled StringBuffer ready to go in an error message
1407: *
1408: */
1409: private StringBuffer consolidateFieldNames(List fieldNames,
1410: String delimiter) {
1411:
1412: StringBuffer sb = new StringBuffer();
1413:
1414: // setup some vars
1415: boolean firstPass = true;
1416: String delim = "";
1417:
1418: // walk through the list
1419: for (Iterator iter = fieldNames.iterator(); iter.hasNext();) {
1420: String fieldName = (String) iter.next();
1421:
1422: // get the human-readable name
1423: // add the new one, with the appropriate delimiter
1424: sb.append(delim
1425: + ddService.getAttributeLabel(newBo.getClass(),
1426: fieldName));
1427:
1428: // after the first item, start using a delimiter
1429: if (firstPass) {
1430: delim = delimiter;
1431: firstPass = false;
1432: }
1433: }
1434:
1435: return sb;
1436: }
1437:
1438: /**
1439: *
1440: * This method translates the passed in field name into a human-readable attribute label.
1441: *
1442: * It assumes the existing newBO's class as the class to examine the fieldName for.
1443: *
1444: * @param fieldName The fieldName you want a human-readable label for.
1445: * @return A human-readable label, pulled from the DataDictionary.
1446: *
1447: */
1448: protected String getFieldLabel(String fieldName) {
1449: return ddService.getAttributeLabel(newBo.getClass(), fieldName)
1450: + "("
1451: + ddService.getAttributeShortLabel(newBo.getClass(),
1452: fieldName) + ")";
1453: }
1454:
1455: /**
1456: *
1457: * This method translates the passed in field name into a human-readable attribute label.
1458: *
1459: * It assumes the existing newBO's class as the class to examine the fieldName for.
1460: *
1461: * @param boClass The class to use in combination with the fieldName.
1462: * @param fieldName The fieldName you want a human-readable label for.
1463: * @return A human-readable label, pulled from the DataDictionary.
1464: *
1465: */
1466: protected String getFieldLabel(Class boClass, String fieldName) {
1467: return ddService.getAttributeLabel(boClass, fieldName) + "("
1468: + ddService.getAttributeShortLabel(boClass, fieldName)
1469: + ")";
1470: }
1471:
1472: /**
1473: * Gets the boDictionaryService attribute.
1474: *
1475: * @return Returns the boDictionaryService.
1476: */
1477: protected final BusinessObjectDictionaryService getBoDictionaryService() {
1478: return boDictionaryService;
1479: }
1480:
1481: /**
1482: * Sets the boDictionaryService attribute value.
1483: *
1484: * @param boDictionaryService The boDictionaryService to set.
1485: */
1486: public final void setBoDictionaryService(
1487: BusinessObjectDictionaryService boDictionaryService) {
1488: this .boDictionaryService = boDictionaryService;
1489: }
1490:
1491: /**
1492: * Gets the boService attribute.
1493: *
1494: * @return Returns the boService.
1495: */
1496: protected final BusinessObjectService getBoService() {
1497: return boService;
1498: }
1499:
1500: /**
1501: * Sets the boService attribute value.
1502: *
1503: * @param boService The boService to set.
1504: */
1505: public final void setBoService(BusinessObjectService boService) {
1506: this .boService = boService;
1507: }
1508:
1509: /**
1510: * Gets the configService attribute.
1511: *
1512: * @return Returns the configService.
1513: */
1514: protected final KualiConfigurationService getConfigService() {
1515: return configService;
1516: }
1517:
1518: /**
1519: * Sets the configService attribute value.
1520: *
1521: * @param configService The configService to set.
1522: */
1523: public final void setConfigService(
1524: KualiConfigurationService configService) {
1525: this .configService = configService;
1526: }
1527:
1528: /**
1529: * Gets the ddService attribute.
1530: *
1531: * @return Returns the ddService.
1532: */
1533: protected final DataDictionaryService getDdService() {
1534: return ddService;
1535: }
1536:
1537: /**
1538: * Sets the ddService attribute value.
1539: *
1540: * @param ddService The ddService to set.
1541: */
1542: public final void setDdService(DataDictionaryService ddService) {
1543: this .ddService = ddService;
1544: }
1545:
1546: /**
1547: * Gets the dictionaryValidationService attribute.
1548: *
1549: * @return Returns the dictionaryValidationService.
1550: */
1551: protected final DictionaryValidationService getDictionaryValidationService() {
1552: return dictionaryValidationService;
1553: }
1554:
1555: /**
1556: * Sets the dictionaryValidationService attribute value.
1557: *
1558: * @param dictionaryValidationService The dictionaryValidationService to set.
1559: */
1560: public final void setDictionaryValidationService(
1561: DictionaryValidationService dictionaryValidationService) {
1562: this .dictionaryValidationService = dictionaryValidationService;
1563: }
1564:
1565: /**
1566: * Gets the documentAuthorizationService attribute.
1567: *
1568: * @return Returns the documentAuthorizationService.
1569: */
1570: protected final DocumentAuthorizationService getDocumentAuthorizationService() {
1571: return documentAuthorizationService;
1572: }
1573:
1574: /**
1575: * Sets the documentAuthorizationService attribute value.
1576: *
1577: * @param documentAuthorizationService The documentAuthorizationService to set.
1578: */
1579: public final void setDocumentAuthorizationService(
1580: DocumentAuthorizationService documentAuthorizationService) {
1581: this .documentAuthorizationService = documentAuthorizationService;
1582: }
1583:
1584: /**
1585: * Gets the maintDocDictionaryService attribute.
1586: *
1587: * @return Returns the maintDocDictionaryService.
1588: */
1589: protected final MaintenanceDocumentDictionaryService getMaintDocDictionaryService() {
1590: return maintDocDictionaryService;
1591: }
1592:
1593: /**
1594: * Sets the maintDocDictionaryService attribute value.
1595: *
1596: * @param maintDocDictionaryService The maintDocDictionaryService to set.
1597: */
1598: public final void setMaintDocDictionaryService(
1599: MaintenanceDocumentDictionaryService maintDocDictionaryService) {
1600: this .maintDocDictionaryService = maintDocDictionaryService;
1601: }
1602:
1603: /**
1604: * Gets the newBo attribute.
1605: *
1606: * @return Returns the newBo.
1607: */
1608: protected final PersistableBusinessObject getNewBo() {
1609: return newBo;
1610: }
1611:
1612: protected void setNewBo(PersistableBusinessObject newBo) {
1613: this .newBo = newBo;
1614: }
1615:
1616: /**
1617: * Gets the oldBo attribute.
1618: *
1619: * @return Returns the oldBo.
1620: */
1621: protected final PersistableBusinessObject getOldBo() {
1622: return oldBo;
1623: }
1624:
1625: /**
1626: * Gets the persistenceService attribute.
1627: *
1628: * @return Returns the persistenceService.
1629: */
1630: protected final PersistenceService getPersistenceService() {
1631: return persistenceService;
1632: }
1633:
1634: /**
1635: * Sets the persistenceService attribute value.
1636: *
1637: * @param persistenceService The persistenceService to set.
1638: */
1639: public final void setPersistenceService(
1640: PersistenceService persistenceService) {
1641: this .persistenceService = persistenceService;
1642: }
1643:
1644: /**
1645: * Gets the persistenceStructureService attribute.
1646: *
1647: * @return Returns the persistenceStructureService.
1648: */
1649: protected final PersistenceStructureService getPersistenceStructureService() {
1650: return persistenceStructureService;
1651: }
1652:
1653: /**
1654: * Sets the persistenceStructureService attribute value.
1655: *
1656: * @param persistenceStructureService The persistenceStructureService to set.
1657: */
1658: public final void setPersistenceStructureService(
1659: PersistenceStructureService persistenceStructureService) {
1660: this .persistenceStructureService = persistenceStructureService;
1661: }
1662:
1663: /**
1664: * Gets the workflowDocumentService attribute.
1665: *
1666: * @return Returns the workflowDocumentService.
1667: */
1668: public WorkflowDocumentService getWorkflowDocumentService() {
1669: return workflowDocumentService;
1670: }
1671:
1672: /**
1673: * Sets the workflowDocumentService attribute value.
1674: *
1675: * @param workflowDocumentService The workflowDocumentService to set.
1676: */
1677: public void setWorkflowDocumentService(
1678: WorkflowDocumentService workflowDocumentService) {
1679: this .workflowDocumentService = workflowDocumentService;
1680: }
1681:
1682: public boolean processAddCollectionLineBusinessRules(
1683: MaintenanceDocument document, String collectionName,
1684: PersistableBusinessObject bo) {
1685: LOG.debug("processAddCollectionLineBusinessRules");
1686:
1687: // setup convenience pointers to the old & new bo
1688: setupBaseConvenienceObjects(document);
1689:
1690: // enforce authorization restrictions on fields
1691: checkAuthorizationRestrictions(document);
1692:
1693: // sanity check on the document object
1694: this .validateMaintenanceDocument(document);
1695:
1696: boolean success = true;
1697: ErrorMap map = GlobalVariables.getErrorMap();
1698: int errorCount = map.getErrorCount();
1699: map.addToErrorPath(MAINTAINABLE_ERROR_PATH);
1700: if (LOG.isDebugEnabled()) {
1701: LOG.debug("processAddCollectionLineBusinessRules - BO: "
1702: + bo);
1703: LOG.debug("Before Validate: " + map);
1704: }
1705: getBoDictionaryService().performForceUppercase(bo);
1706: getMaintDocDictionaryService()
1707: .validateMaintainableCollectionsAddLineRequiredFields(
1708: document,
1709: document.getNewMaintainableObject()
1710: .getBusinessObject(), collectionName);
1711: String errorPath = RiceConstants.MAINTENANCE_ADD_PREFIX
1712: + collectionName;
1713: map.addToErrorPath(errorPath);
1714: getDictionaryValidationService().validateBusinessObject(bo,
1715: false);
1716: success &= map.getErrorCount() == errorCount;
1717: success &= validateDuplicateIdentifierInDataDictionary(
1718: document, collectionName, bo);
1719: success &= processCustomAddCollectionLineBusinessRules(
1720: document, collectionName, bo);
1721: map.removeFromErrorPath(errorPath);
1722: map.removeFromErrorPath(MAINTAINABLE_ERROR_PATH);
1723: if (LOG.isDebugEnabled()) {
1724: LOG.debug("After Validate: " + map);
1725: LOG
1726: .debug("processAddCollectionLineBusinessRules returning: "
1727: + success);
1728: }
1729:
1730: return success;
1731: }
1732:
1733: /**
1734: * This method validates that there should only exist one entry in the collection whose
1735: * fields match the fields specified within the duplicateIdentificationFields in the
1736: * maintenance document data dictionary.
1737: * If the duplicateIdentificationFields is not specified in the DD, by default it would
1738: * allow the addition to happen and return true.
1739: * It will return false if it fails the uniqueness validation.
1740: * @param document
1741: * @param collectionName
1742: * @param bo
1743: * @return
1744: */
1745: private boolean validateDuplicateIdentifierInDataDictionary(
1746: MaintenanceDocument document, String collectionName,
1747: PersistableBusinessObject bo) {
1748: boolean valid = true;
1749: PersistableBusinessObject maintBo = document
1750: .getNewMaintainableObject().getBusinessObject();
1751: Collection maintCollection = (Collection) ObjectUtils
1752: .getPropertyValue(maintBo, collectionName);
1753: List<String> duplicateIdentifier = document
1754: .getNewMaintainableObject()
1755: .getDuplicateIdentifierFieldsFromDataDictionary(
1756: document.getDocumentHeader()
1757: .getWorkflowDocument()
1758: .getDocumentType(), collectionName);
1759: if (duplicateIdentifier.size() > 0) {
1760: List<String> existingIdentifierString = document
1761: .getNewMaintainableObject()
1762: .getMultiValueIdentifierList(maintCollection,
1763: duplicateIdentifier);
1764: if (document.getNewMaintainableObject()
1765: .hasBusinessObjectExisted(bo,
1766: existingIdentifierString,
1767: duplicateIdentifier)) {
1768: valid = false;
1769: GlobalVariables.getErrorMap().putError(
1770: duplicateIdentifier.get(0),
1771: RiceKeyConstants.ERROR_DUPLICATE_ELEMENT,
1772: "entries in ",
1773: document.getDocumentHeader()
1774: .getWorkflowDocument()
1775: .getDocumentType());
1776: }
1777: }
1778: return valid;
1779: }
1780:
1781: public boolean processCustomAddCollectionLineBusinessRules(
1782: MaintenanceDocument document, String collectionName,
1783: PersistableBusinessObject line) {
1784: return true;
1785: }
1786:
1787: public UniversalUserService getUniversalUserService() {
1788: return universalUserService;
1789: }
1790:
1791: public void setUniversalUserService(
1792: UniversalUserService universalUserService) {
1793: this .universalUserService = universalUserService;
1794: }
1795:
1796: public DateTimeService getDateTimeService() {
1797: return KNSServiceLocator.getDateTimeService();
1798: }
1799:
1800: }
|