org.kuali.rice.kim.document.rule.IdentityManagementRoleDocumentRule.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.rice.kim.document.rule.IdentityManagementRoleDocumentRule.java

Source

/**
 * Copyright 2005-2014 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.rice.kim.document.rule;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.commons.lang.StringUtils;
import org.kuali.rice.core.api.CoreConstants;
import org.kuali.rice.core.api.membership.MemberType;
import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
import org.kuali.rice.core.api.uif.RemotableAttributeError;
import org.kuali.rice.core.api.util.RiceKeyConstants;
import org.kuali.rice.core.api.util.VersionHelper;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.identity.IdentityService;
import org.kuali.rice.kim.api.identity.principal.Principal;
import org.kuali.rice.kim.api.permission.Permission;
import org.kuali.rice.kim.api.responsibility.Responsibility;
import org.kuali.rice.kim.api.role.Role;
import org.kuali.rice.kim.api.role.RoleService;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.kim.api.type.KimAttributeField;
import org.kuali.rice.kim.api.type.KimType;
import org.kuali.rice.kim.bo.ui.KimDocumentRoleMember;
import org.kuali.rice.kim.bo.ui.KimDocumentRolePermission;
import org.kuali.rice.kim.bo.ui.KimDocumentRoleQualifier;
import org.kuali.rice.kim.bo.ui.KimDocumentRoleResponsibility;
import org.kuali.rice.kim.bo.ui.KimDocumentRoleResponsibilityAction;
import org.kuali.rice.kim.bo.ui.RoleDocumentDelegationMember;
import org.kuali.rice.kim.bo.ui.RoleDocumentDelegationMemberQualifier;
import org.kuali.rice.kim.document.IdentityManagementRoleDocument;
import org.kuali.rice.kim.framework.role.RoleTypeService;
import org.kuali.rice.kim.framework.services.KimFrameworkServiceLocator;
import org.kuali.rice.kim.framework.type.KimTypeService;
import org.kuali.rice.kim.impl.common.attribute.KimAttributeBo;
import org.kuali.rice.kim.impl.responsibility.AddResponsibilityEvent;
import org.kuali.rice.kim.impl.responsibility.AddResponsibilityRule;
import org.kuali.rice.kim.impl.responsibility.KimDocumentResponsibilityRule;
import org.kuali.rice.kim.impl.responsibility.ResponsibilityBo;
import org.kuali.rice.kim.impl.responsibility.ResponsibilityInternalService;
import org.kuali.rice.kim.impl.services.KimImplServiceLocator;
import org.kuali.rice.kim.impl.type.KimTypeLookupableHelperServiceImpl;
import org.kuali.rice.kim.rule.event.ui.AddDelegationEvent;
import org.kuali.rice.kim.rule.event.ui.AddDelegationMemberEvent;
import org.kuali.rice.kim.rule.event.ui.AddMemberEvent;
import org.kuali.rice.kim.rule.event.ui.AddPermissionEvent;
import org.kuali.rice.kim.rule.ui.AddDelegationMemberRule;
import org.kuali.rice.kim.rule.ui.AddDelegationRule;
import org.kuali.rice.kim.rule.ui.AddMemberRule;
import org.kuali.rice.kim.rule.ui.AddPermissionRule;
import org.kuali.rice.kim.rules.ui.KimDocumentMemberRule;
import org.kuali.rice.kim.rules.ui.KimDocumentPermissionRule;
import org.kuali.rice.kim.rules.ui.RoleDocumentDelegationMemberRule;
import org.kuali.rice.kim.rules.ui.RoleDocumentDelegationRule;
import org.kuali.rice.kns.kim.type.DataDictionaryTypeServiceHelper;
import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
import org.kuali.rice.krad.data.KradDataServiceLocator;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.KRADConstants;
import org.kuali.rice.krad.util.MessageMap;
import org.kuali.rice.ksb.api.KsbApiServiceLocator;
import org.kuali.rice.ksb.api.bus.Endpoint;
import org.kuali.rice.ksb.api.bus.ServiceBus;

/**
 * @author Kuali Rice Team (rice.collab@kuali.org)
 */
public class IdentityManagementRoleDocumentRule extends TransactionalDocumentRuleBase implements AddPermissionRule,
        AddResponsibilityRule, AddMemberRule, AddDelegationRule, AddDelegationMemberRule {
    //   protected static final Logger LOG = Logger.getLogger( IdentityManagementRoleDocumentRule.class );

    public static final int PRIORITY_NUMBER_MIN_VALUE = 1;
    public static final int PRIORITY_NUMBER_MAX_VALUE = 11;

    protected AddResponsibilityRule addResponsibilityRule;
    protected AddPermissionRule addPermissionRule;
    protected AddMemberRule addMemberRule;
    protected AddDelegationRule addDelegationRule;
    protected AddDelegationMemberRule addDelegationMemberRule;
    protected Class<? extends AddResponsibilityRule> addResponsibilityRuleClass = KimDocumentResponsibilityRule.class;
    protected Class<? extends AddPermissionRule> addPermissionRuleClass = KimDocumentPermissionRule.class;
    protected Class<? extends AddMemberRule> addMemberRuleClass = KimDocumentMemberRule.class;
    protected Class<? extends AddDelegationRule> addDelegationRuleClass = RoleDocumentDelegationRule.class;
    protected Class<? extends AddDelegationMemberRule> addDelegationMemberRuleClass = RoleDocumentDelegationMemberRule.class;

    private static IdentityService identityService;
    private static ResponsibilityInternalService responsibilityInternalService;

    protected AttributeValidationHelper attributeValidationHelper = new AttributeValidationHelper();

    // KULRICE-4153
    protected ActiveRoleMemberHelper activeRoleMemberHelper = new ActiveRoleMemberHelper();

    protected IdentityService getIdentityService() {
        if (identityService == null) {
            identityService = KimApiServiceLocator.getIdentityService();
        }
        return identityService;
    }

    @Override
    protected boolean processCustomSaveDocumentBusinessRules(Document document) {
        if (!(document instanceof IdentityManagementRoleDocument)) {
            return false;
        }

        IdentityManagementRoleDocument roleDoc = (IdentityManagementRoleDocument) document;

        boolean valid = true;
        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
        boolean validRoleNamespace = validRoleNamespace(roleDoc);
        boolean validRoleName = validRoleName(roleDoc);
        if (!(validRoleNamespace && validRoleName)) {
            return false;
        }
        valid &= validDuplicateRoleName(roleDoc);
        valid &= validPermissions(roleDoc);
        valid &= validResponsibilities(roleDoc);
        //KULRICE-6858 Validate group members are in identity system
        valid &= validRoleMemberPrincipalIDs(roleDoc.getModifiedMembers());
        getDictionaryValidationService().validateDocumentAndUpdatableReferencesRecursively(document,
                getMaxDictionaryValidationDepth(), true, false);
        if (!KimTypeLookupableHelperServiceImpl.hasDerivedRoleTypeService(roleDoc.getKimType())
                && canUserAssignRoleMembers(roleDoc)) {
            //valid &= validAssignRole(roleDoc);
            // KULRICE-4153 & KULRICE-4818
            // Use the Active Role Member Helper to retrieve only those Role Members & Delegation member that are active
            // If a member or delegation is active on a Role, and they have an inactive Role Qualifier, Role Qualifier validation will fail
            // If a member or delegation is inactive on a Role, and they have an inactive Role Qualifier, Role Qualifier validation will pass
            List<KimDocumentRoleMember> activeRoleMembers = activeRoleMemberHelper
                    .getActiveRoleMembers(roleDoc.getMembers());
            List<KimDocumentRoleMember> newActiveRoleMembers = activeRoleMemberHelper
                    .getActiveRoleMembers(roleDoc.getModifiedMembers());
            List<RoleDocumentDelegationMember> activeRoleDelegationMembers = activeRoleMemberHelper
                    .getActiveDelegationRoleMembers(roleDoc.getDelegationMembers());

            valid &= validateRoleQualifier(newActiveRoleMembers, roleDoc.getKimType());
            valid &= validRoleMemberActiveDates(roleDoc.getModifiedMembers());
            valid &= validateDelegationMemberRoleQualifier(newActiveRoleMembers, activeRoleDelegationMembers,
                    roleDoc.getKimType(), activeRoleMembers);
            valid &= validDelegationMemberActiveDates(roleDoc.getDelegationMembers());
            valid &= validRoleMembersResponsibilityActions(roleDoc.getModifiedMembers());
        }
        valid &= validRoleResponsibilitiesActions(roleDoc.getResponsibilities());
        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);

        return valid;
    }

    /**
     * Ensures the {@link IdentityManagementRoleDocument} role namespace is not null or an empty string.
     * @param roleDoc the {@link IdentityManagementRoleDocument} to validate.
     * @return TRUE if the role namespace is not null or an empty string, FALSE otherwise.
     */
    protected boolean validRoleNamespace(IdentityManagementRoleDocument roleDoc) {
        boolean validRoleNamespace = false;

        if (StringUtils.isNotBlank(roleDoc.getRoleNamespace())) {
            validRoleNamespace = true;
        } else {
            GlobalVariables.getMessageMap().putError("document.roleNamespace", RiceKeyConstants.ERROR_EMPTY_ENTRY,
                    new String[] { "Role Namespace" });
        }

        return validRoleNamespace;
    }

    /**
     * ensures the {@link IdentitymangaementRoleDocument} role name is not null or an empty string
     * @param roleDoc the {@link IdentityManagementRoleDocument} to validate.
     * @return TRUE if the role name is not null or an empty string, FALSE otherwise.
     */
    protected boolean validRoleName(IdentityManagementRoleDocument roleDoc) {
        boolean validRoleName = false;

        if (StringUtils.isNotBlank(roleDoc.getRoleName())) {
            validRoleName = true;
        } else {
            GlobalVariables.getMessageMap().putError("document.roleName", RiceKeyConstants.ERROR_EMPTY_ENTRY,
                    new String[] { "Role Name" });
        }

        return validRoleName;
    }

    protected boolean canUserAssignRoleMembers(IdentityManagementRoleDocument document) {
        boolean rulePassed = true;
        Map<String, String> additionalPermissionDetails = new HashMap<String, String>();
        additionalPermissionDetails.put(KimConstants.AttributeConstants.NAMESPACE_CODE,
                document.getRoleNamespace());
        additionalPermissionDetails.put(KimConstants.AttributeConstants.ROLE_NAME, document.getRoleName());
        if ((document.getMembers() != null && document.getMembers().size() > 0)
                || (document.getDelegationMembers() != null && document.getDelegationMembers().size() > 0)) {
            if (!getDocumentDictionaryService().getDocumentAuthorizer(document).isAuthorizedByTemplate(document,
                    KimConstants.NAMESPACE_CODE, KimConstants.PermissionTemplateNames.ASSIGN_ROLE,
                    GlobalVariables.getUserSession().getPrincipalId(), additionalPermissionDetails, null)) {
                rulePassed = false;
            }
        }
        return rulePassed;
    }

    protected boolean validRoleMemberPrincipalIDs(List<KimDocumentRoleMember> roleMembers) {
        boolean valid = true;
        List<String> principalIds = new ArrayList<String>();
        for (KimDocumentRoleMember roleMember : roleMembers) {
            if (StringUtils.equals(roleMember.getMemberTypeCode(),
                    KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode())) {
                principalIds.add(roleMember.getMemberId());
            }
        }
        if (!principalIds.isEmpty()) {
            List<Principal> validPrincipals = getIdentityService().getPrincipals(principalIds);
            for (KimDocumentRoleMember roleMember : roleMembers) {
                if (StringUtils.equals(roleMember.getMemberTypeCode(), MemberType.PRINCIPAL.getCode())) {
                    boolean validPrincipalId = false;
                    if (validPrincipals != null && !validPrincipals.isEmpty()) {
                        for (Principal validPrincipal : validPrincipals) {
                            if (roleMember.getMemberId().equals(validPrincipal.getPrincipalId())) {
                                validPrincipalId = true;
                            }
                        }
                    }
                    if (!validPrincipalId) {
                        GlobalVariables.getMessageMap().putError("document.member.memberId",
                                RiceKeyConstants.ERROR_MEMBERID_MEMBERTYPE_MISMATCH,
                                new String[] { roleMember.getMemberId() });
                        valid = false;
                    }
                }
            }
        }
        return valid;
    }

    @SuppressWarnings("unchecked")
    protected boolean validDuplicateRoleName(IdentityManagementRoleDocument roleDoc) {
        Role role = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName(roleDoc.getRoleNamespace(),
                roleDoc.getRoleName());
        boolean rulePassed = true;
        if (role != null) {
            if (role.getId().equals(roleDoc.getRoleId())) {
                rulePassed = true;
            } else {
                GlobalVariables.getMessageMap().putError("document.roleName",
                        RiceKeyConstants.ERROR_DUPLICATE_ENTRY, new String[] { "Role Name" });
                rulePassed = false;
            }
        }
        return rulePassed;
    }

    protected boolean validRoleMemberActiveDates(List<KimDocumentRoleMember> roleMembers) {
        boolean valid = true;
        int i = 0;
        for (KimDocumentRoleMember roleMember : roleMembers) {
            valid &= validateActiveDate("document.members[" + i + "].activeToDate", roleMember.getActiveFromDate(),
                    roleMember.getActiveToDate());
            i++;
        }
        return valid;
    }

    protected boolean validDelegationMemberActiveDates(List<RoleDocumentDelegationMember> delegationMembers) {
        boolean valid = true;
        int i = 0;
        for (RoleDocumentDelegationMember delegationMember : delegationMembers) {
            valid &= validateActiveDate("document.delegationMembers[" + i + "].activeToDate",
                    delegationMember.getActiveFromDate(), delegationMember.getActiveToDate());
            i++;
        }
        return valid;
    }

    protected boolean validPermissions(IdentityManagementRoleDocument document) {
        Permission kimPermissionInfo;
        boolean valid = true;
        int i = 0;
        for (KimDocumentRolePermission permission : document.getPermissions()) {
            kimPermissionInfo = permission.getPermission();
            if (!permission.isActive() && !hasPermissionToGrantPermission(permission.getPermission(), document)) {
                GlobalVariables.getMessageMap().putError("permissions[" + i + "].active",
                        RiceKeyConstants.ERROR_ASSIGN_PERMISSION, new String[] {
                                kimPermissionInfo.getNamespaceCode(), kimPermissionInfo.getTemplate().getName() });
                valid = false;
            }
            i++;
        }
        return valid;
    }

    protected boolean validResponsibilities(IdentityManagementRoleDocument document) {
        ResponsibilityBo kimResponsibilityImpl;
        boolean valid = true;
        int i = 0;
        for (KimDocumentRoleResponsibility responsibility : document.getResponsibilities()) {
            kimResponsibilityImpl = responsibility.getKimResponsibility();
            if (!responsibility.isActive() && !hasPermissionToGrantResponsibility(
                    ResponsibilityBo.to(responsibility.getKimResponsibility()), document)) {
                GlobalVariables.getMessageMap().putError("responsibilities[" + i + "].active",
                        RiceKeyConstants.ERROR_ASSIGN_RESPONSIBILITY,
                        new String[] { kimResponsibilityImpl.getNamespaceCode(),
                                kimResponsibilityImpl.getTemplate().getName() });
                valid = false;
            }
            i++;
        }
        return valid;
    }

    protected boolean validRoleResponsibilitiesActions(List<KimDocumentRoleResponsibility> roleResponsibilities) {
        int i = 0;
        boolean rulePassed = true;
        for (KimDocumentRoleResponsibility roleResponsibility : roleResponsibilities) {
            if (!getResponsibilityInternalService()
                    .areActionsAtAssignmentLevelById(roleResponsibility.getResponsibilityId())) {
                validateRoleResponsibilityAction(
                        "document.responsibilities[" + i + "].roleRspActions[0].priorityNumber",
                        roleResponsibility.getRoleRspActions().get(0));
            }
            i++;
        }
        return rulePassed;
    }

    protected boolean validRoleMembersResponsibilityActions(List<KimDocumentRoleMember> roleMembers) {
        int i = 0;
        int j;
        boolean rulePassed = true;
        for (KimDocumentRoleMember roleMember : roleMembers) {
            j = 0;
            if (roleMember.getRoleRspActions() != null && !roleMember.getRoleRspActions().isEmpty()) {
                for (KimDocumentRoleResponsibilityAction roleRspAction : roleMember.getRoleRspActions()) {
                    validateRoleResponsibilityAction(
                            "document.members[" + i + "].roleRspActions[" + j + "].priorityNumber", roleRspAction);
                    j++;
                }
            }
            i++;
        }
        return rulePassed;
    }

    protected boolean validateRoleResponsibilityAction(String errorPath,
            KimDocumentRoleResponsibilityAction roleRspAction) {
        boolean rulePassed = true;
        /*if(StringUtils.isBlank(roleRspAction.getActionPolicyCode())){
           GlobalVariables.getErrorMap().putError(errorPath,
            RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Action Policy Code"});
           rulePassed = false;
        }
        if(roleRspAction.getPriorityNumber()==null){
           GlobalVariables.getErrorMap().putError(errorPath,
            RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Priority Number"});
           rulePassed = false;
        }
        if(StringUtils.isBlank(roleRspAction.getActionTypeCode())){
           GlobalVariables.getErrorMap().putError(errorPath,
            RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Action Type Code"});
           rulePassed = false;
        }*/
        if (roleRspAction.getPriorityNumber() != null
                && (roleRspAction.getPriorityNumber() < PRIORITY_NUMBER_MIN_VALUE
                        || roleRspAction.getPriorityNumber() > PRIORITY_NUMBER_MAX_VALUE)) {
            GlobalVariables.getMessageMap().putError(errorPath, RiceKeyConstants.ERROR_PRIORITY_NUMBER_RANGE,
                    new String[] { PRIORITY_NUMBER_MIN_VALUE + "", PRIORITY_NUMBER_MAX_VALUE + "" });
            rulePassed = false;
        }

        return rulePassed;
    }

    protected boolean validateRoleQualifier(List<KimDocumentRoleMember> roleMembers, KimType kimType) {
        List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();

        int memberCounter = 0;
        int roleMemberCount = 0;
        List<RemotableAttributeError> errorsTemp;
        Map<String, String> mapToValidate;
        KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
        final List<KimAttributeField> attributeDefinitions = kimTypeService
                .getAttributeDefinitions(kimType.getId());
        final Set<String> uniqueAttributeNames = figureOutUniqueQualificationSet(roleMembers, attributeDefinitions);

        for (KimDocumentRoleMember roleMember : roleMembers) {
            errorsTemp = Collections.emptyList();
            mapToValidate = attributeValidationHelper.convertQualifiersToMap(roleMember.getQualifiers());

            VersionedService<RoleTypeService> versionedRoleTypeService = getVersionedRoleTypeService(kimType);
            boolean shouldNotValidate = true;
            if (versionedRoleTypeService != null) {
                boolean versionOk = VersionHelper.compareVersion(versionedRoleTypeService.getVersion(),
                        CoreConstants.Versions.VERSION_2_1_2) != -1 ? true : false;
                if (versionOk) {
                    shouldNotValidate = versionedRoleTypeService.getService().shouldValidateQualifiersForMemberType(
                            MemberType.fromCode(roleMember.getMemberTypeCode()));
                } else {
                    shouldNotValidate = false;
                }
            }
            if (!shouldNotValidate) {
                errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
                validationErrors.addAll(attributeValidationHelper
                        .convertErrorsForMappedFields("members[" + memberCounter + "]", errorsTemp));
                memberCounter++;
            }
            if (uniqueAttributeNames.size() > 0) {
                validateUniquePersonRoleQualifiersUniqueForRoleMembership(roleMember, roleMemberCount, roleMembers,
                        uniqueAttributeNames, validationErrors);
            }

            roleMemberCount += 1;
        }

        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);

        if (validationErrors.isEmpty()) {
            return true;
        }
        attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
        return false;
    }

    /**
     * Finds the names of the unique qualification attributes which this role should be checking against
     *
     * @param memberships the memberships (we take the qualification from the first)
     * @param attributeDefinitions information about the attributeDefinitions
     * @return a Set of unique attribute ids (with their indices, for error reporting)
     */
    protected Set<String> figureOutUniqueQualificationSet(List<KimDocumentRoleMember> memberships,
            List<KimAttributeField> attributeDefinitions) {
        Set<String> uniqueAttributeIds = new HashSet<String>();

        if (memberships != null && memberships.size() > 1) { // if there aren't two or more members, doing this whole check is kinda silly
            KimDocumentRoleMember membership = memberships.get(0);

            for (KimDocumentRoleQualifier qualifier : membership.getQualifiers()) {
                if (qualifier != null && qualifier.getKimAttribute() != null
                        && !StringUtils.isBlank(qualifier.getKimAttribute().getAttributeName())) {
                    final KimAttributeField relatedDefinition = DataDictionaryTypeServiceHelper.findAttributeField(
                            qualifier.getKimAttribute().getAttributeName(), attributeDefinitions);

                    if (relatedDefinition != null && relatedDefinition.isUnique()) {
                        uniqueAttributeIds.add(qualifier.getKimAttrDefnId()); // it's unique - add it to the Set
                    }
                }
            }
        }

        return uniqueAttributeIds;
    }

    /**
     * Checks all the qualifiers for the given membership, so that all qualifiers which should be unique are guaranteed to be unique
     *
     * @param membershipToCheck the membership to check
     * @param membershipToCheckIndex the index of the person's membership in the role (for error reporting purposes)
     * @param validationErrors Map<String, String> of errors to report
     * @return true if all unique values are indeed unique, false otherwise
     */
    protected boolean validateUniquePersonRoleQualifiersUniqueForRoleMembership(
            KimDocumentRoleMember membershipToCheck, int membershipToCheckIndex,
            List<KimDocumentRoleMember> memberships, Set<String> uniqueQualifierIds,
            List<RemotableAttributeError> validationErrors) {
        boolean foundError = false;
        int count = 0;

        for (KimDocumentRoleMember membership : memberships) {
            if (membershipToCheckIndex != count) {
                if (sameMembership(membershipToCheck, membership)) {
                    if (sameUniqueMembershipQualifications(membershipToCheck, membership, uniqueQualifierIds)) {
                        foundError = true;
                        // add error to each qualifier which is supposed to be unique
                        int qualifierCount = 0;

                        for (KimDocumentRoleQualifier qualifier : membership.getQualifiers()) {
                            if (qualifier != null && uniqueQualifierIds.contains(qualifier.getKimAttrDefnId())) {
                                // for new member lines, KimAttribute is not preloaded
                                // make sure to load it here in order to obtain the name for use in error message
                                KimAttributeBo attr = qualifier.getKimAttribute();
                                String attrName = "<unknown>";
                                if (attr == null && qualifier.getKimAttrDefnId() != null) {
                                    attr = KradDataServiceLocator.getDataObjectService().find(KimAttributeBo.class,
                                            qualifier.getKimAttrDefnId());
                                }
                                if (attr != null) {
                                    attrName = attr.getAttributeName();
                                }
                                validationErrors.add(RemotableAttributeError.Builder.create(
                                        "document.members[" + membershipToCheckIndex + "].qualifiers["
                                                + qualifierCount + "].attrVal",
                                        RiceKeyConstants.ERROR_DOCUMENT_IDENTITY_MANAGEMENT_PERSON_QUALIFIER_VALUE_NOT_UNIQUE
                                                + ":" + membership.getMemberId() + ";" + attrName + ";"
                                                + qualifier.getAttrVal())
                                        .build());
                            }
                            qualifierCount += 1;
                        }
                    }
                }
            }
            count += 1;
        }

        return foundError;
    }

    /**
     * Determines if two memberships represent the same member being added: that is, the two memberships have the same type code and id
     *
     * @param membershipA the first membership to check
     * @param membershipB the second membership to check
     * @return true if the two memberships represent the same member; false if they do not, or if it could not be profitably determined if the members were the same
     */
    protected boolean sameMembership(KimDocumentRoleMember membershipA, KimDocumentRoleMember membershipB) {
        if (!StringUtils.isBlank(membershipA.getMemberTypeCode())
                && !StringUtils.isBlank(membershipB.getMemberTypeCode())
                && !StringUtils.isBlank(membershipA.getMemberId())
                && !StringUtils.isBlank(membershipB.getMemberId())) {
            return membershipA.getMemberTypeCode().equals(membershipB.getMemberTypeCode())
                    && membershipA.getMemberId().equals(membershipB.getMemberId());
        }
        return false;
    }

    /**
     * Given two memberships which represent the same member, do they share qualifications?
     *
     * @param membershipA the first membership to check
     * @param membershipB the second membership to check
     * @param uniqueAttributeIds the Set of attribute definition ids which should be unique
     * @return
     */
    protected boolean sameUniqueMembershipQualifications(KimDocumentRoleMember membershipA,
            KimDocumentRoleMember membershipB, Set<String> uniqueAttributeIds) {
        boolean equalSoFar = true;
        for (String kimAttributeDefinitionId : uniqueAttributeIds) {
            final KimDocumentRoleQualifier qualifierA = membershipA.getQualifier(kimAttributeDefinitionId);
            final KimDocumentRoleQualifier qualifierB = membershipB.getQualifier(kimAttributeDefinitionId);

            if (qualifierA != null && qualifierB != null) {
                equalSoFar &= (qualifierA.getAttrVal() == null && qualifierB.getAttrVal() == null)
                        || (qualifierA.getAttrVal() == null
                                || qualifierA.getAttrVal().equals(qualifierB.getAttrVal()));
            }
        }
        return equalSoFar;
    }

    protected KimDocumentRoleMember getRoleMemberForDelegation(List<KimDocumentRoleMember> roleMembers,
            RoleDocumentDelegationMember delegationMember, List<KimDocumentRoleMember> modifiedRoleMembers) {
        if ((roleMembers == null && modifiedRoleMembers == null) || delegationMember == null
                || delegationMember.getRoleMemberId() == null) {
            return null;
        }
        for (KimDocumentRoleMember roleMember : modifiedRoleMembers) {
            if (delegationMember.getRoleMemberId().equals(roleMember.getRoleMemberId())) {
                return roleMember;
            }
        }
        for (KimDocumentRoleMember roleMember : roleMembers) {
            if (delegationMember.getRoleMemberId().equals(roleMember.getRoleMemberId())) {
                return roleMember;
            }
        }
        return null;
    }

    protected boolean validateDelegationMemberRoleQualifier(List<KimDocumentRoleMember> modifiedRoleMembers,
            List<RoleDocumentDelegationMember> delegationMembers, KimType kimType,
            List<KimDocumentRoleMember> nonModifiedRoleMembers) {
        List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
        boolean valid;
        int memberCounter = 0;
        List<RemotableAttributeError> errorsTemp;
        Map<String, String> mapToValidate;
        KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
        KimDocumentRoleMember roleMember;
        String errorPath;
        final List<KimAttributeField> attributeDefinitions = kimTypeService
                .getAttributeDefinitions(kimType.getId());
        final Set<String> uniqueQualifierAttributes = figureOutUniqueQualificationSetForDelegation(
                delegationMembers, attributeDefinitions);

        for (RoleDocumentDelegationMember delegationMember : delegationMembers) {
            errorPath = "delegationMembers[" + memberCounter + "]";
            mapToValidate = attributeValidationHelper.convertQualifiersToMap(delegationMember.getQualifiers());
            if (!delegationMember.isRole()) {
                errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
                validationErrors
                        .addAll(attributeValidationHelper.convertErrorsForMappedFields(errorPath, errorsTemp));
            }
            roleMember = getRoleMemberForDelegation(nonModifiedRoleMembers, delegationMember, modifiedRoleMembers);
            if (roleMember == null) {
                valid = false;
                GlobalVariables.getMessageMap().putError("document.delegationMembers[" + memberCounter + "]",
                        RiceKeyConstants.ERROR_DELEGATE_ROLE_MEMBER_ASSOCIATION, new String[] {});
            } else {
                errorsTemp = kimTypeService.validateUnmodifiableAttributes(kimType.getId(),
                        attributeValidationHelper.convertQualifiersToMap(roleMember.getQualifiers()),
                        mapToValidate);
                validationErrors
                        .addAll(attributeValidationHelper.convertErrorsForMappedFields(errorPath, errorsTemp));
            }
            if (uniqueQualifierAttributes.size() > 0) {
                validateUniquePersonRoleQualifiersUniqueForRoleDelegation(delegationMember, memberCounter,
                        delegationMembers, uniqueQualifierAttributes, validationErrors);
            }
            memberCounter++;
        }
        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
        if (validationErrors.isEmpty()) {
            valid = true;
        } else {
            attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
            valid = false;
        }
        return valid;
    }

    /**
     * Finds the names of the unique qualification attributes which this role should be checking against
     *
     * @param memberships the memberships (we take the qualification from the first)
     * @param attributeDefinitions information about the attributeDefinitions
     * @return a Set of unique attribute ids (with their indices, for error reporting)
     */
    protected Set<String> figureOutUniqueQualificationSetForDelegation(
            List<RoleDocumentDelegationMember> memberships, List<KimAttributeField> attributeDefinitions) {
        Set<String> uniqueAttributeIds = new HashSet<String>();

        if (memberships != null && memberships.size() > 1) { // if there aren't two or more members, doing this whole check is kinda silly
            RoleDocumentDelegationMember membership = memberships.get(0);

            for (RoleDocumentDelegationMemberQualifier qualifier : membership.getQualifiers()) {
                if (qualifier != null && qualifier.getKimAttribute() != null
                        && !StringUtils.isBlank(qualifier.getKimAttribute().getAttributeName())) {
                    final KimAttributeField relatedDefinition = DataDictionaryTypeServiceHelper.findAttributeField(
                            qualifier.getKimAttribute().getAttributeName(), attributeDefinitions);

                    if (relatedDefinition.isUnique()) {
                        uniqueAttributeIds.add(qualifier.getKimAttrDefnId()); // it's unique - add it to the Set
                    }
                }
            }
        }

        return uniqueAttributeIds;
    }

    /**
     * Checks all the qualifiers for the given membership, so that all qualifiers which should be unique are guaranteed to be unique
     *
     * @param delegationMembershipToCheck the membership to check
     * @param membershipToCheckIndex the index of the person's membership in the role (for error reporting purposes)
     * @param validationErrors Map<String, String> of errors to report
     * @return true if all unique values are indeed unique, false otherwise
     */
    protected boolean validateUniquePersonRoleQualifiersUniqueForRoleDelegation(
            RoleDocumentDelegationMember delegationMembershipToCheck, int membershipToCheckIndex,
            List<RoleDocumentDelegationMember> delegationMemberships, Set<String> uniqueQualifierIds,
            List<RemotableAttributeError> validationErrors) {
        boolean foundError = false;
        int count = 0;

        for (RoleDocumentDelegationMember delegationMembership : delegationMemberships) {
            if (membershipToCheckIndex != count) {
                if (sameDelegationMembership(delegationMembershipToCheck, delegationMembership)) {
                    if (sameUniqueDelegationMembershipQualifications(delegationMembershipToCheck,
                            delegationMembership, uniqueQualifierIds)) {
                        foundError = true;
                        // add error to each qualifier which is supposed to be unique
                        int qualifierCount = 0;

                        for (RoleDocumentDelegationMemberQualifier qualifier : delegationMembership
                                .getQualifiers()) {
                            if (qualifier != null && uniqueQualifierIds.contains(qualifier.getKimAttrDefnId())) {
                                validationErrors.add(RemotableAttributeError.Builder.create(
                                        "document.delegationMembers[" + membershipToCheckIndex + "].qualifiers["
                                                + qualifierCount + "].attrVal",
                                        RiceKeyConstants.ERROR_DOCUMENT_IDENTITY_MANAGEMENT_PERSON_QUALIFIER_VALUE_NOT_UNIQUE
                                                + ":" + qualifier.getKimAttribute().getAttributeName() + ";"
                                                + qualifier.getAttrVal())
                                        .build());
                            }
                            qualifierCount += 1;
                        }
                    }
                }
            }
            count += 1;
        }

        return foundError;
    }

    /**
     * Determines if two memberships represent the same member being added: that is, the two memberships have the same type code and id
     *
     * @param membershipA the first membership to check
     * @param membershipB the second membership to check
     * @return true if the two memberships represent the same member; false if they do not, or if it could not be profitably determined if the members were the same
     */
    protected boolean sameDelegationMembership(RoleDocumentDelegationMember membershipA,
            RoleDocumentDelegationMember membershipB) {
        if (!StringUtils.isBlank(membershipA.getMemberTypeCode())
                && !StringUtils.isBlank(membershipB.getMemberTypeCode())
                && !StringUtils.isBlank(membershipA.getMemberId())
                && !StringUtils.isBlank(membershipB.getMemberId())) {
            return membershipA.getMemberTypeCode().equals(membershipB.getMemberTypeCode())
                    && membershipA.getMemberId().equals(membershipB.getMemberId());
        }
        return false;
    }

    /**
     * Given two memberships which represent the same member, do they share qualifications?
     *
     * @param membershipA the first membership to check
     * @param membershipB the second membership to check
     * @param uniqueAttributeIds the Set of attribute definition ids which should be unique
     * @return
     */
    protected boolean sameUniqueDelegationMembershipQualifications(RoleDocumentDelegationMember membershipA,
            RoleDocumentDelegationMember membershipB, Set<String> uniqueAttributeIds) {
        boolean equalSoFar = true;
        for (String kimAttributeDefinitionId : uniqueAttributeIds) {
            final RoleDocumentDelegationMemberQualifier qualifierA = membershipA
                    .getQualifier(kimAttributeDefinitionId);
            final RoleDocumentDelegationMemberQualifier qualifierB = membershipB
                    .getQualifier(kimAttributeDefinitionId);

            if (qualifierA != null && qualifierB != null) {
                equalSoFar &= (qualifierA.getAttrVal() == null && qualifierB.getAttrVal() == null)
                        || (qualifierA.getAttrVal() == null
                                || qualifierA.getAttrVal().equals(qualifierB.getAttrVal()));
            }
        }
        return equalSoFar;
    }

    protected boolean validateActiveDate(String errorPath, Timestamp activeFromDate, Timestamp activeToDate) {
        // TODO : do not have detail bus rule yet, so just check this for now.
        boolean valid = true;
        if (activeFromDate != null && activeToDate != null && activeToDate.before(activeFromDate)) {
            MessageMap errorMap = GlobalVariables.getMessageMap();
            errorMap.putError(errorPath, RiceKeyConstants.ERROR_ACTIVE_TO_DATE_BEFORE_FROM_DATE);
            valid = false;

        }
        return valid;
    }

    /**
     *
     * This method checks to see if adding a role to role membership
     * creates a circular reference.
     *
     * @param addMemberEvent
     * @return true  - ok to assign, no circular references
     *         false - do not make assignment, will create circular reference.
     */
    protected boolean checkForCircularRoleMembership(AddMemberEvent addMemberEvent) {
        KimDocumentRoleMember newMember = addMemberEvent.getMember();
        if (newMember == null || StringUtils.isBlank(newMember.getMemberId())) {
            MessageMap errorMap = GlobalVariables.getMessageMap();
            errorMap.putError("member.memberId", RiceKeyConstants.ERROR_INVALID_ROLE, new String[] { "" });
            return false;
        }
        Set<String> roleMemberIds = null;
        // if the role member is a role, check to make sure we won't be creating a circular reference.
        // Verify that the new role is not already related to the role either directly or indirectly
        if (newMember.isRole()) {
            // get all nested role member ids that are of type role
            RoleService roleService = KimApiServiceLocator.getRoleService();
            roleMemberIds = roleService.getRoleTypeRoleMemberIds(newMember.getMemberId());

            // check to see if the document role is not a member of the new member role
            IdentityManagementRoleDocument document = (IdentityManagementRoleDocument) addMemberEvent.getDocument();
            if (roleMemberIds.contains(document.getRoleId())) {
                MessageMap errorMap = GlobalVariables.getMessageMap();
                errorMap.putError("member.memberId", RiceKeyConstants.ERROR_ASSIGN_ROLE_MEMBER_CIRCULAR,
                        new String[] { newMember.getMemberId() });
                return false;
            }
        }
        return true;
    }

    /**
     * @return the addResponsibilityRule
     */
    public AddResponsibilityRule getAddResponsibilityRule() {
        if (addResponsibilityRule == null) {
            try {
                addResponsibilityRule = addResponsibilityRuleClass.newInstance();
            } catch (Exception ex) {
                throw new RuntimeException("Unable to create AddResponsibilityRule instance using class: "
                        + addResponsibilityRuleClass, ex);
            }
        }
        return addResponsibilityRule;
    }

    /**
     * @return the addPermissionRule
     */
    public AddPermissionRule getAddPermissionRule() {
        if (addPermissionRule == null) {
            try {
                addPermissionRule = addPermissionRuleClass.newInstance();
            } catch (Exception ex) {
                throw new RuntimeException(
                        "Unable to create AddPermissionRule instance using class: " + addPermissionRuleClass, ex);
            }
        }
        return addPermissionRule;
    }

    /**
     * @return the addMemberRule
     */
    public AddMemberRule getAddMemberRule() {
        if (addMemberRule == null) {
            try {
                addMemberRule = addMemberRuleClass.newInstance();
            } catch (Exception ex) {
                throw new RuntimeException(
                        "Unable to create AddMemberRule instance using class: " + addMemberRuleClass, ex);
            }
        }
        return addMemberRule;
    }

    /**
     * @return the addDelegationRule
     */
    public AddDelegationRule getAddDelegationRule() {
        if (addDelegationRule == null) {
            try {
                addDelegationRule = addDelegationRuleClass.newInstance();
            } catch (Exception ex) {
                throw new RuntimeException(
                        "Unable to create AddDelegationRule instance using class: " + addDelegationRuleClass, ex);
            }
        }
        return addDelegationRule;
    }

    /**
     * @return the addDelegationMemberRule
     */
    public AddDelegationMemberRule getAddDelegationMemberRule() {
        if (addDelegationMemberRule == null) {
            try {
                addDelegationMemberRule = addDelegationMemberRuleClass.newInstance();
            } catch (Exception ex) {
                throw new RuntimeException("Unable to create AddDelegationMemberRule instance using class: "
                        + addDelegationMemberRuleClass, ex);
            }
        }
        return addDelegationMemberRule;
    }

    public boolean processAddPermission(AddPermissionEvent addPermissionEvent) {
        return getAddPermissionRule().processAddPermission(addPermissionEvent);
    }

    public boolean hasPermissionToGrantPermission(Permission kimPermissionInfo,
            IdentityManagementRoleDocument document) {
        return getAddPermissionRule().hasPermissionToGrantPermission(kimPermissionInfo, document);
    }

    public boolean processAddResponsibility(AddResponsibilityEvent addResponsibilityEvent) {
        return getAddResponsibilityRule().processAddResponsibility(addResponsibilityEvent);
    }

    public boolean hasPermissionToGrantResponsibility(Responsibility kimResponsibilityInfo,
            IdentityManagementRoleDocument document) {
        return getAddResponsibilityRule().hasPermissionToGrantResponsibility(kimResponsibilityInfo, document);
    }

    public boolean processAddMember(AddMemberEvent addMemberEvent) {
        boolean success = new KimDocumentMemberRule().processAddMember(addMemberEvent);
        success &= validateActiveDate("member.activeFromDate", addMemberEvent.getMember().getActiveFromDate(),
                addMemberEvent.getMember().getActiveToDate());
        success &= checkForCircularRoleMembership(addMemberEvent);
        return success;
    }

    public boolean processAddDelegation(AddDelegationEvent addDelegationEvent) {
        return getAddDelegationRule().processAddDelegation(addDelegationEvent);
    }

    public boolean processAddDelegationMember(AddDelegationMemberEvent addDelegationMemberEvent) {
        boolean success = new RoleDocumentDelegationMemberRule()
                .processAddDelegationMember(addDelegationMemberEvent);
        RoleDocumentDelegationMember roleDocumentDelegationMember = addDelegationMemberEvent.getDelegationMember();
        success &= validateActiveDate("delegationMember.activeFromDate",
                roleDocumentDelegationMember.getActiveFromDate(), roleDocumentDelegationMember.getActiveToDate());
        return success;
    }

    public ResponsibilityInternalService getResponsibilityInternalService() {
        if (responsibilityInternalService == null) {
            responsibilityInternalService = KimImplServiceLocator.getResponsibilityInternalService();
        }
        return responsibilityInternalService;
    }

    protected RoleTypeService getRoleTypeService(KimType typeInfo) {
        String serviceName = typeInfo.getServiceName();
        if (serviceName != null) {
            try {
                KimTypeService service = (KimTypeService) GlobalResourceLoader
                        .getService(QName.valueOf(serviceName));
                if (service != null && service instanceof RoleTypeService) {
                    return (RoleTypeService) service;
                }
                return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
            } catch (Exception ex) {
                return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
            }
        }
        return null;
    }

    private static class VersionedService<T> {

        String version;
        T service;

        VersionedService(String version, T service) {
            this.version = version;
            this.service = service;
        }

        T getService() {
            return this.service;
        }

        String getVersion() {
            return this.version;
        }

    }

    protected VersionedService<RoleTypeService> getVersionedRoleTypeService(KimType typeInfo) {
        String serviceName = typeInfo.getServiceName();
        if (serviceName != null) {
            String version = "2.0.0"; // default version since the base services have been available since then
            RoleTypeService roleTypeService = null;

            try {

                ServiceBus serviceBus = KsbApiServiceLocator.getServiceBus();
                Endpoint endpoint = serviceBus.getEndpoint(QName.valueOf(serviceName));
                if (endpoint != null) {
                    version = endpoint.getServiceConfiguration().getServiceVersion();
                }
                KimTypeService service = (KimTypeService) GlobalResourceLoader
                        .getService(QName.valueOf(serviceName));
                if (service != null && service instanceof RoleTypeService) {
                    roleTypeService = (RoleTypeService) service;
                } else {
                    roleTypeService = (RoleTypeService) KimImplServiceLocator
                            .getService("kimNoMembersRoleTypeService");
                }
            } catch (Exception ex) {
                roleTypeService = (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
            }

            return new VersionedService<RoleTypeService>(version, roleTypeService);
        }

        return null;
    }

}