Java tutorial
/* * Copyright 2006 The Kuali Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ecl2.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kuali.ole.coa.document.validation.impl; import java.sql.Date; import java.sql.Timestamp; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DateUtils; import org.kuali.ole.coa.businessobject.Account; import org.kuali.ole.coa.businessobject.Organization; import org.kuali.ole.coa.service.OrganizationService; import org.kuali.ole.sys.OLEConstants; import org.kuali.ole.sys.OLEKeyConstants; import org.kuali.ole.sys.OLEPropertyConstants; import org.kuali.ole.sys.context.SpringContext; import org.kuali.ole.sys.identity.OleKimAttributes; import org.kuali.rice.core.api.parameter.ParameterEvaluatorService; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kim.api.KimConstants; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.kim.api.services.IdentityManagementService; import org.kuali.rice.kns.document.MaintenanceDocument; import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.ObjectUtils; /** * This class implements the business rules specific to the {@link Org} Maintenance Document. */ public class OrgRule extends MaintenanceDocumentRuleBase { protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OrgRule.class); protected static OrganizationService orgService; protected Organization oldOrg; protected Organization newOrg; protected boolean isHrmsOrgActivated; /** * Constructs a OrgRule and pseudo-injects services */ public OrgRule() { super(); // Pseudo-inject some services. // // This approach is being used to make it simpler to convert the Rule classes // to spring-managed with these services injected by Spring at some later date. // When this happens, just remove these calls to the setters with // SpringContext, and configure the bean defs for spring. if (orgService == null) { orgService = SpringContext.getBean(OrganizationService.class); } } /** * This performs the following checks on document approve: * <ul> * <li>{@link OrgRule#checkExistenceAndActive()}</li> * <li>{@link OrgRule#checkOrgClosureRules(MaintenanceDocument)}</li> * <li>{@link OrgRule#checkSimpleRules(MaintenanceDocument)}</li> * <li>{@link OrgRule#checkDefaultAccountNumber(MaintenanceDocument)}</li> * </ul> * This rule fails on rule failure * * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) { boolean success = true; LOG.debug("Entering processCustomApproveDocumentBusinessRules()"); // determine whether HRMS ORG is activated in this app instance isHrmsOrgActivated = isHrmsOrgActivated(); // check that all sub-objects whose keys are specified have matching objects in the db success &= checkExistenceAndActive(); success &= checkOrgClosureRules(document); // check that end date is greater than begin date and Reports To Chart/Org should not be same as this Chart/Org success &= checkSimpleRules(document); // check that defaultAccount is present unless // ( (orgType = U or C) and ( document is a "create new" )) //Code changes for JIRA OLE2344 don't check default account number as account no is made optional //success &= checkDefaultAccountNumber(document); return success; } /** * This performs the following checks on document route: * <ul> * <li>{@link OrgRule#checkExistenceAndActive()}</li> * <li>{@link OrgRule#checkOrgClosureRules(MaintenanceDocument)}</li> * <li>{@link OrgRule#checkSimpleRules(MaintenanceDocument)}</li> * <li>{@link OrgRule#checkDefaultAccountNumber(MaintenanceDocument)}</li> * </ul> * This rule fails on rule failure * * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { boolean success = true; LOG.debug("Entering processCustomRouteDocumentBusinessRules()"); // determine whether HRMS ORG is activated in this app instance isHrmsOrgActivated = isHrmsOrgActivated(); // check that all sub-objects whose keys are specified have matching objects in the db success &= checkExistenceAndActive(); // check that end date is greater than begin date and Reports To Chart/Org should not be same as this Chart/Org success &= checkSimpleRules(document); // check that defaultAccount is present unless // ( (orgType = U or C) and ( document is a "create new" )) //Code changes for JIRA OLE2344 don't check default account number as account no is made optional //success &= checkDefaultAccountNumber(document); success &= checkOrgClosureRules(document); return success; } /** * This performs the following checks on document save: * <ul> * <li>{@link OrgRule#checkExistenceAndActive()}</li> * <li>{@link OrgRule#checkOrgClosureRules(MaintenanceDocument)}</li> * <li>{@link OrgRule#checkSimpleRules(MaintenanceDocument)}</li> * <li>{@link OrgRule#checkDefaultAccountNumber(MaintenanceDocument)}</li> * </ul> * This rule does not fail on rule failure * * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { LOG.debug("Entering processCustomSaveDocumentBusinessRules()"); // determine whether HRMS ORG is activated in this app instance isHrmsOrgActivated = isHrmsOrgActivated(); // check that all sub-objects whose keys are specified have matching objects in the db checkExistenceAndActive(); checkOrgClosureRules(document); // check that end date is greater than begin date and Reports To Chart/Org should not be same as this Chart/Org checkSimpleRules(document); // check that defaultAccount is present unless // ( (orgType = U or C) and ( document is a "create new" )) //Code changes for JIRA OLE2344 don't check default account number as account no is made optional //checkDefaultAccountNumber(document); return true; } /** * This checks to see if the org is active * * @return true if the org is inactive or false otherwise */ protected boolean checkExistenceAndActive() { LOG.debug("Entering checkExistenceAndActive()"); boolean success = true; // shortcut out with no enforcement if this org is closed if (!newOrg.isActive()) { return success; } // Disabling plant attribute checking // success &= checkPlantAttributes(); return success; } /** * This checks to see if a user is authorized for plant fields modification. If not then it returns true (without activating * fields). If the org does not have to report to itself then it checks to see if the plant fields have been filled out * correctly and fails if they haven't * * @return false if user can edit plant fields but they have not been filled out correctly */ protected boolean checkPlantAttributes() { boolean success = true; /* * KULCOA-1132 - exit if the user is not a member of the plant maintainer work group. */ // get user Person user = GlobalVariables.getUserSession().getPerson(); // if not authroized to edit plant fields, exit with true if (isPlantAuthorized(user) == false) { return true; } // relax this edit for if (!getOrgMustReportToSelf(newOrg)) { // require Org Plant ChartCode success &= checkEmptyBOField("organizationPlantChartCode", newOrg.getOrganizationPlantChartCode(), "Organization Plant Chart of Accounts Code"); // require Org Plant AccountNumber success &= checkEmptyBOField("organizationPlantAccountNumber", newOrg.getOrganizationPlantAccountNumber(), "Organization Plant Account Number"); // require Campus Plant ChartCode success &= checkEmptyBOField("campusPlantChartCode", newOrg.getCampusPlantChartCode(), "Campus Plant Chart of Accounts Code"); // require Org Plant ChartCode success &= checkEmptyBOField("campusPlantAccountNumber", newOrg.getCampusPlantAccountNumber(), "Campus Plant Account Number"); // validate Org Plant Account success &= getDictionaryValidationService().validateReferenceExistsAndIsActive(newOrg, "organizationPlantAccount", MAINTAINABLE_ERROR_PREFIX + "organizationPlantAccountNumber", "Organization Plant Account"); // validate Campus Plant Account success &= getDictionaryValidationService().validateReferenceExistsAndIsActive(newOrg, "campusPlantAccount", MAINTAINABLE_ERROR_PREFIX + "campusPlantAccountNumber", "Campus Plant Account"); } return success; } /** * This method enforces the business rules surrounding when an Org becomes closed/inactive. If we are editing and switching the * org to inactive or if it is a new doc and it is marked as inactive then we assume we are closing the org. If we are not then * we return true. If we are then we return false if there are still active accounts tied to the org * * @param document * @return false if trying to close org but it still has accounts that are active linked to it */ protected boolean checkOrgClosureRules(MaintenanceDocument document) { boolean success = true; boolean orgBeingClosed = false; boolean checkForChildObjects = true; // if its an edit, and its being closed if (document.isEdit()) { if (oldOrg.isActive() && !newOrg.isActive()) { orgBeingClosed = true; } } // if its new, and is being created as closed if (document.isNew()) { if (!newOrg.isActive()) { orgBeingClosed = true; // Since it's new, we don't need to check for accounts and orgs checkForChildObjects = false; } } // if the org isnt being closed, stop processing here if (!orgBeingClosed) { return success; } // FROM HERE ON WE'RE ASSUMING THE ORG IS BEING CLOSED // do not allow the org to be closed while there are active accounts tied // to this org if (checkForChildObjects) { List childAccounts = orgService.getActiveAccountsByOrg(newOrg.getChartOfAccountsCode(), newOrg.getOrganizationCode()); if (childAccounts.size() > 0) { // get the first three accounts on the list for display StringBuffer childAccountList = new StringBuffer(); int count = 0; String delim = ""; for (Iterator iter = childAccounts.iterator(); iter.hasNext();) { Account account = (Account) iter.next(); childAccountList .append(delim + account.getChartOfAccountsCode() + "-" + account.getAccountNumber()); count++; if (count >= 1) { delim = ", "; } if (count >= 3) { break; } } if (childAccounts.size() > count) { childAccountList.append(", ... (" + (childAccounts.size() - count) + " more)"); } putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_OPEN_CHILD_ACCOUNTS_ON_ORG_CLOSURE, childAccountList.toString()); success &= false; } // do not allow this org to be closed while there are still active orgs // that have this org as their reportsToOrg List childOrgs = orgService.getActiveChildOrgs(newOrg.getChartOfAccountsCode(), newOrg.getOrganizationCode()); if (childOrgs.size() > 0) { // get the first three orgs on the list for display StringBuffer childOrgsList = new StringBuffer(); int count = 0; String delim = ""; for (Iterator iter = childOrgs.iterator(); iter.hasNext();) { Organization org = (Organization) iter.next(); childOrgsList.append(delim + org.getChartOfAccountsCode() + "-" + org.getOrganizationCode()); count++; if (count >= 1) { delim = ", "; } if (count >= 3) { break; } } if (childOrgs.size() > count) { childOrgsList.append(", ... (" + (childOrgs.size() - count) + " more)"); } putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_OPEN_CHILD_ORGS_ON_ORG_CLOSURE, childOrgsList.toString()); success &= false; } } // if org is being closed, end-date must be valid and present if (ObjectUtils.isNull(newOrg.getOrganizationEndDate())) { success &= false; putFieldError("organizationEndDate", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_END_DATE_REQUIRED_ON_ORG_CLOSURE); } return success; } /** * This checks to see if the org is active and if it the HRMS org is active * * @param document * @return true if either the org is inactive or isHrmsOrgActivated is false */ protected boolean checkHrmsOrgRules(MaintenanceDocument document) { boolean success = true; // shortcut out with no enforcement if this org is closed if (!newOrg.isActive()) { return success; } // short circuit and fail if HRMSOrg is turned off if (!isHrmsOrgActivated) { return success; } // if the system has a HRMS Org record attached to this org record, then prompt the // user to fill out the HRMS Org info // HRMS Org Campus == Org Campus // HRMS Org campus code must be the same as Org campus code // if the return success; } /** * This checks our {@link Parameter} rules to see if this org needs to report to itself * * @param organization * @return true if it does */ protected boolean getOrgMustReportToSelf(Organization organization) { return /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class) .getParameterEvaluator(Organization.class, OLEConstants.ChartApcParms.ORG_MUST_REPORT_TO_SELF_ORG_TYPES, organization.getOrganizationTypeCode()) .evaluationSucceeds(); } /** * This checks the following conditions: * <ul> * <li>begin date must be greater than or equal to end date</li> * <li>start date must be greater than or equal to today if new Document</li> * <li>Reports To Chart/Org should not be same as this Chart/Org</li> * </ul> * * @param document * @return true if it passes all the rules, false otherwise */ protected boolean checkSimpleRules(MaintenanceDocument document) { boolean success = true; String lastReportsToChartOfAccountsCode; String lastReportsToOrganizationCode; boolean continueSearch; Organization tempOrg; Integer loopCount; Integer maxLoopCount = 40; // begin date must be greater than or equal to end date if ((ObjectUtils.isNotNull(newOrg.getOrganizationBeginDate()) && (ObjectUtils.isNotNull(newOrg.getOrganizationEndDate())))) { Date beginDate = newOrg.getOrganizationBeginDate(); Date endDate = newOrg.getOrganizationEndDate(); if (endDate.before(beginDate)) { putFieldError("organizationEndDate", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_END_DATE_GREATER_THAN_BEGIN_DATE); success &= false; } } // start date must be greater than or equal to today if new Document if ((ObjectUtils.isNotNull(newOrg.getOrganizationBeginDate()) && (document.isNew()))) { Timestamp today = getDateTimeService().getCurrentTimestamp(); today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); if (newOrg.getOrganizationBeginDate().before(today)) { putFieldError("organizationBeginDate", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_STARTDATE_IN_PAST); success &= false; } } // Reports To Chart/Org should not be same as this Chart/Org // However, allow special case where organization type is listed in the business rules if (ObjectUtils.isNotNull(newOrg.getReportsToChartOfAccountsCode()) && ObjectUtils.isNotNull(newOrg.getReportsToOrganizationCode()) && ObjectUtils.isNotNull(newOrg.getChartOfAccountsCode()) && ObjectUtils.isNotNull(newOrg.getOrganizationCode())) { if (!getOrgMustReportToSelf(newOrg)) { if ((newOrg.getReportsToChartOfAccountsCode().equals(newOrg.getChartOfAccountsCode())) && (newOrg.getReportsToOrganizationCode().equals(newOrg.getOrganizationCode()))) { putFieldError("reportsToOrganizationCode", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_CANNOT_BE_SAME_ORG); success = false; } else { // Don't allow a circular reference on Reports to Chart/Org // terminate the search when a top-level org is found lastReportsToChartOfAccountsCode = newOrg.getReportsToChartOfAccountsCode(); lastReportsToOrganizationCode = newOrg.getReportsToOrganizationCode(); continueSearch = true; loopCount = 0; do { tempOrg = orgService.getByPrimaryId(lastReportsToChartOfAccountsCode, lastReportsToOrganizationCode); loopCount++; ; if (ObjectUtils.isNull(tempOrg)) { continueSearch = false; // if a null is returned on the first iteration, then the reports-to org does not exist // fail the validation if (loopCount == 1) { putFieldError("reportsToOrganizationCode", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_MUST_EXIST); success = false; } } else { // on the first iteration, check whether the reports-to organization is active if (loopCount == 1 && !tempOrg.isActive()) { putFieldError("reportsToOrganizationCode", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_MUST_EXIST); success = false; continueSearch = false; } else { // LOG.info("Found Org = " + lastReportsToChartOfAccountsCode + "/" + // lastReportsToOrganizationCode); lastReportsToChartOfAccountsCode = tempOrg.getReportsToChartOfAccountsCode(); lastReportsToOrganizationCode = tempOrg.getReportsToOrganizationCode(); if ((tempOrg.getReportsToChartOfAccountsCode() .equals(newOrg.getChartOfAccountsCode())) && (tempOrg.getReportsToOrganizationCode() .equals(newOrg.getOrganizationCode()))) { putFieldError("reportsToOrganizationCode", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_CANNOT_BE_CIRCULAR_REF_TO_SAME_ORG); success = false; continueSearch = false; } } } if (loopCount > maxLoopCount) { continueSearch = false; } // stop the search if we reach an org that must report to itself if (continueSearch && /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class) .getParameterEvaluator(Organization.class, OLEConstants.ChartApcParms.ORG_MUST_REPORT_TO_SELF_ORG_TYPES, tempOrg.getOrganizationTypeCode()) .evaluationSucceeds()) { continueSearch = false; } } while (continueSearch == true); } // end else (checking for circular ref) } else { // org must report to self (university level organization) if (!(newOrg.getReportsToChartOfAccountsCode().equals(newOrg.getChartOfAccountsCode()) && newOrg.getReportsToOrganizationCode().equals(newOrg.getOrganizationCode()))) { putFieldError("reportsToOrganizationCode", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_MUST_BE_SAME_ORG); success = false; } // org must be the only one of that type String topLevelOrgTypeCode = SpringContext.getBean(ParameterService.class) .getParameterValueAsString(Organization.class, OLEConstants.ChartApcParms.ORG_MUST_REPORT_TO_SELF_ORG_TYPES); List<Organization> topLevelOrgs = orgService.getActiveOrgsByType(topLevelOrgTypeCode); if (!topLevelOrgs.isEmpty()) { // is the new org in the topLevelOrgs list? If not, then there's an error; if so, we're editing the top level // org if (!topLevelOrgs.contains(newOrg)) { putFieldError("organizationTypeCode", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_ONLY_ONE_TOP_LEVEL_ORG, topLevelOrgs.get(0).getChartOfAccountsCode() + "-" + topLevelOrgs.get(0).getOrganizationCode()); success = false; } } } } return success; } /** * This checks that defaultAccount is present unless ( (orgType = U or C) and ( document is a "create new" or "edit" )) * * @param document * @return false if missing default account number and it is not an exempt type code */ protected boolean checkDefaultAccountNumber(MaintenanceDocument document) { boolean success = true; boolean exemptOrganizationTypeCode = false; boolean missingDefaultAccountNumber = StringUtils.isBlank(newOrg.getOrganizationDefaultAccountNumber()); if (ObjectUtils.isNotNull(newOrg.getOrganizationTypeCode())) { String organizationTypeCode = newOrg.getOrganizationTypeCode(); if (/*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class) .getParameterEvaluator(Organization.class, OLEConstants.ChartApcParms.DEFAULT_ACCOUNT_NOT_REQUIRED_ORG_TYPES, newOrg.getOrganizationTypeCode()) .evaluationSucceeds()) { exemptOrganizationTypeCode = true; } } if (missingDefaultAccountNumber && (!exemptOrganizationTypeCode || (!document.isNew() && !document.isEdit()))) { putFieldError("organizationDefaultAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_ORGMAINT_DEFAULT_ACCOUNT_NUMBER_REQUIRED); success &= false; } return success; } /** * This method compares an old and new value, and determines if they've changed. If the old was null/blank, and the new is not, * return true. If the old had a value, and the new is null/blank, return true. If both old and new had a value, and the values * are different (excluding trailing or leading whitespaces, and excluding case changes), return true. If none of the above, * return false. * * @param oldValue - Old value to test. * @param newValue - New value to test. * @return true or false, based on the algorithm described above. */ protected boolean fieldsHaveChanged(String oldValue, String newValue) { // if old was null/blank and new is not if (StringUtils.isBlank(oldValue) && StringUtils.isNotBlank(newValue)) { return true; } // if old had a value, but new is null/blank if (StringUtils.isNotBlank(oldValue) && StringUtils.isBlank(newValue)) { return true; } // at this point, we know that we had a value before, and we have a // value now, so we need to test whether this value has changed if (oldValue != null && newValue != null) { if (!oldValue.trim().equalsIgnoreCase(newValue.trim())) { return true; } } // if we've made it to here, then no changes have happened to the values return false; } /** * This method looks up in the ParameterService whether ther HRMS Org system is turned on. * * @return true or false depending on the app configuration */ protected boolean isHrmsOrgActivated() { return SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(Organization.class, OLEConstants.ChartApcParms.APC_HRMS_ACTIVE_KEY); } /** * This method sets the convenience objects like newOrg and oldOrg, so you have short and easy handles to the new and old * objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load all * sub-objects from the DB by their primary keys, if available. * * @param document - the maintenanceDocument being evaluated */ @Override public void setupConvenienceObjects() { // setup oldAccount convenience objects, make sure all possible sub-objects are populated oldOrg = (Organization) super.getOldBo(); // setup newAccount convenience objects, make sure all possible sub-objects are populated newOrg = (Organization) super.getNewBo(); } /** * This method tests whether the specified user is part of the group that grants authorization to the Plant fields. * * @param user - the user to test * @return true if user is part of the group, false otherwise */ protected boolean isPlantAuthorized(Person user) { String principalId = user.getPrincipalId(); String namespaceCode = OLEConstants.ParameterNamespaces.KNS; String permissionTemplateName = KimConstants.PermissionTemplateNames.MODIFY_FIELD; Map<String, String> roleQualifiers = new HashMap<String, String>(); roleQualifiers.put(OleKimAttributes.CHART_OF_ACCOUNTS_CODE, newOrg.getChartOfAccountsCode()); Map<String, String> permissionDetails = new HashMap<String, String>(); permissionDetails.put(KimConstants.AttributeConstants.COMPONENT_NAME, Organization.class.getSimpleName()); permissionDetails.put(KimConstants.AttributeConstants.PROPERTY_NAME, OLEPropertyConstants.ORGANIZATION_PLANT_CHART_CODE); IdentityManagementService identityManagementService = SpringContext .getBean(IdentityManagementService.class); Boolean isAuthorized = identityManagementService.isAuthorizedByTemplateName(principalId, namespaceCode, permissionTemplateName, permissionDetails, roleQualifiers); if (!isAuthorized) { if (LOG.isDebugEnabled()) { LOG.debug("User '" + user.getPrincipalName() + "' has no access to the Plant Chart."); } return false; } permissionDetails.put(KimConstants.AttributeConstants.PROPERTY_NAME, OLEPropertyConstants.ORGANIZATION_PLANT_ACCOUNT_NUMBER); isAuthorized = identityManagementService.isAuthorizedByTemplateName(principalId, namespaceCode, permissionTemplateName, permissionDetails, roleQualifiers); if (!isAuthorized) { if (LOG.isDebugEnabled()) { LOG.debug("User '" + user.getPrincipalName() + "' has no access to the Plant account."); } return false; } if (LOG.isDebugEnabled()) { LOG.debug("User '" + user.getPrincipalName() + "' has access to the Plant fields."); } return true; } }