View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kim.document.rule;
17  
18  import java.sql.Timestamp;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Set;
24  import java.util.List;
25  import java.util.Map;
26  
27  import javax.xml.namespace.QName;
28  
29  import org.apache.commons.lang.StringUtils;
30  import org.kuali.rice.core.api.CoreConstants;
31  import org.kuali.rice.core.api.membership.MemberType;
32  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
33  import org.kuali.rice.core.api.uif.RemotableAttributeError;
34  import org.kuali.rice.core.api.util.RiceKeyConstants;
35  import org.kuali.rice.core.api.util.VersionHelper;
36  import org.kuali.rice.kim.api.KimConstants;
37  import org.kuali.rice.kim.api.identity.IdentityService;
38  import org.kuali.rice.kim.api.identity.principal.Principal;
39  import org.kuali.rice.kim.api.permission.Permission;
40  import org.kuali.rice.kim.api.responsibility.Responsibility;
41  import org.kuali.rice.kim.api.role.Role;
42  import org.kuali.rice.kim.api.role.RoleService;
43  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
44  import org.kuali.rice.kim.api.type.KimAttributeField;
45  import org.kuali.rice.kim.api.type.KimType;
46  import org.kuali.rice.kim.bo.ui.KimDocumentRoleMember;
47  import org.kuali.rice.kim.bo.ui.KimDocumentRolePermission;
48  import org.kuali.rice.kim.bo.ui.KimDocumentRoleQualifier;
49  import org.kuali.rice.kim.bo.ui.KimDocumentRoleResponsibility;
50  import org.kuali.rice.kim.bo.ui.KimDocumentRoleResponsibilityAction;
51  import org.kuali.rice.kim.bo.ui.RoleDocumentDelegationMember;
52  import org.kuali.rice.kim.bo.ui.RoleDocumentDelegationMemberQualifier;
53  import org.kuali.rice.kim.document.IdentityManagementRoleDocument;
54  import org.kuali.rice.kim.framework.role.RoleTypeService;
55  import org.kuali.rice.kim.framework.services.KimFrameworkServiceLocator;
56  import org.kuali.rice.kim.framework.type.KimTypeService;
57  import org.kuali.rice.kim.impl.common.attribute.KimAttributeBo;
58  import org.kuali.rice.kim.impl.responsibility.AddResponsibilityEvent;
59  import org.kuali.rice.kim.impl.responsibility.AddResponsibilityRule;
60  import org.kuali.rice.kim.impl.responsibility.KimDocumentResponsibilityRule;
61  import org.kuali.rice.kim.impl.responsibility.ResponsibilityBo;
62  import org.kuali.rice.kim.impl.responsibility.ResponsibilityInternalService;
63  import org.kuali.rice.kim.impl.services.KimImplServiceLocator;
64  import org.kuali.rice.kim.impl.type.KimTypeLookupableHelperServiceImpl;
65  import org.kuali.rice.kim.rule.event.ui.AddDelegationEvent;
66  import org.kuali.rice.kim.rule.event.ui.AddDelegationMemberEvent;
67  import org.kuali.rice.kim.rule.event.ui.AddMemberEvent;
68  import org.kuali.rice.kim.rule.event.ui.AddPermissionEvent;
69  import org.kuali.rice.kim.rule.ui.AddDelegationMemberRule;
70  import org.kuali.rice.kim.rule.ui.AddDelegationRule;
71  import org.kuali.rice.kim.rule.ui.AddMemberRule;
72  import org.kuali.rice.kim.rule.ui.AddPermissionRule;
73  import org.kuali.rice.kim.rules.ui.KimDocumentMemberRule;
74  import org.kuali.rice.kim.rules.ui.KimDocumentPermissionRule;
75  import org.kuali.rice.kim.rules.ui.RoleDocumentDelegationMemberRule;
76  import org.kuali.rice.kim.rules.ui.RoleDocumentDelegationRule;
77  import org.kuali.rice.kns.kim.type.DataDictionaryTypeServiceHelper;
78  import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
79  import org.kuali.rice.krad.data.KradDataServiceLocator;
80  import org.kuali.rice.krad.document.Document;
81  import org.kuali.rice.krad.util.GlobalVariables;
82  import org.kuali.rice.krad.util.KRADConstants;
83  import org.kuali.rice.krad.util.MessageMap;
84  import org.kuali.rice.ksb.api.KsbApiServiceLocator;
85  import org.kuali.rice.ksb.api.bus.Endpoint;
86  import org.kuali.rice.ksb.api.bus.ServiceBus;
87  
88  /**
89   * @author Kuali Rice Team (rice.collab@kuali.org)
90   */
91  public class IdentityManagementRoleDocumentRule extends TransactionalDocumentRuleBase implements AddPermissionRule, AddResponsibilityRule, AddMemberRule, AddDelegationRule, AddDelegationMemberRule {
92  //	protected static final Logger LOG = Logger.getLogger( IdentityManagementRoleDocumentRule.class );
93  
94      public static final int PRIORITY_NUMBER_MIN_VALUE = 1;
95      public static final int PRIORITY_NUMBER_MAX_VALUE = 11;
96  
97  	protected AddResponsibilityRule addResponsibilityRule;
98  	protected AddPermissionRule  addPermissionRule;
99  	protected AddMemberRule  addMemberRule;
100 	protected AddDelegationRule addDelegationRule;
101 	protected AddDelegationMemberRule addDelegationMemberRule;
102 	protected Class<? extends AddResponsibilityRule> addResponsibilityRuleClass = KimDocumentResponsibilityRule.class;
103 	protected Class<? extends AddPermissionRule> addPermissionRuleClass = KimDocumentPermissionRule.class;
104 	protected Class<? extends AddMemberRule> addMemberRuleClass = KimDocumentMemberRule.class;
105 	protected Class<? extends AddDelegationRule> addDelegationRuleClass = RoleDocumentDelegationRule.class;
106 	protected Class<? extends AddDelegationMemberRule> addDelegationMemberRuleClass = RoleDocumentDelegationMemberRule.class;
107 
108 	private static IdentityService identityService;
109 	private static ResponsibilityInternalService responsibilityInternalService;
110 
111 	protected AttributeValidationHelper attributeValidationHelper = new AttributeValidationHelper();
112 
113 	// KULRICE-4153
114 	protected ActiveRoleMemberHelper activeRoleMemberHelper = new ActiveRoleMemberHelper();
115 
116     protected IdentityService getIdentityService() {
117         if ( identityService == null) {
118             identityService = KimApiServiceLocator.getIdentityService();
119         }
120         return identityService;
121     }
122 
123     @Override
124     protected boolean processCustomSaveDocumentBusinessRules(Document document) {
125         if (!(document instanceof IdentityManagementRoleDocument)) {
126             return false;
127         }
128 
129         IdentityManagementRoleDocument roleDoc = (IdentityManagementRoleDocument)document;
130 
131         boolean valid = true;
132         GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
133         boolean validRoleNamespace = validRoleNamespace(roleDoc);
134         boolean validRoleName = validRoleName(roleDoc);
135         if (!(validRoleNamespace && validRoleName)) {
136             return false;
137         }
138         valid &= validDuplicateRoleName(roleDoc);
139         valid &= validPermissions(roleDoc);
140         valid &= validResponsibilities(roleDoc);
141         //KULRICE-6858 Validate group members are in identity system
142         valid &= validRoleMemberPrincipalIDs(roleDoc.getModifiedMembers());
143         getDictionaryValidationService().validateDocumentAndUpdatableReferencesRecursively(document, getMaxDictionaryValidationDepth(), true, false);
144         if ( !KimTypeLookupableHelperServiceImpl.hasDerivedRoleTypeService(roleDoc.getKimType())
145                 && canUserAssignRoleMembers(roleDoc) ) {
146 	        //valid &= validAssignRole(roleDoc);
147         	// KULRICE-4153 & KULRICE-4818
148         	// Use the Active Role Member Helper to retrieve only those Role Members & Delegation member that are active
149         	// If a member or delegation is active on a Role, and they have an inactive Role Qualifier, Role Qualifier validation will fail
150         	// If a member or delegation is inactive on a Role, and they have an inactive Role Qualifier, Role Qualifier validation will pass
151             List<KimDocumentRoleMember> activeRoleMembers = activeRoleMemberHelper.getActiveRoleMembers(roleDoc.getMembers());
152             List<KimDocumentRoleMember> newActiveRoleMembers = activeRoleMemberHelper.getActiveRoleMembers(roleDoc.getModifiedMembers());
153             List<RoleDocumentDelegationMember> activeRoleDelegationMembers = activeRoleMemberHelper.getActiveDelegationRoleMembers(roleDoc.getDelegationMembers());
154 
155             valid &= validateRoleQualifier(newActiveRoleMembers, roleDoc.getKimType());
156 	        valid &= validRoleMemberActiveDates(roleDoc.getModifiedMembers());
157 	        valid &= validateDelegationMemberRoleQualifier(newActiveRoleMembers, activeRoleDelegationMembers, roleDoc.getKimType(), activeRoleMembers);
158 	        valid &= validDelegationMemberActiveDates(roleDoc.getDelegationMembers());
159 	        valid &= validRoleMembersResponsibilityActions(roleDoc.getModifiedMembers());
160         }
161         valid &= validRoleResponsibilitiesActions(roleDoc.getResponsibilities());
162         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
163 
164         return valid;
165     }
166     /**
167      * Ensures the {@link IdentityManagementRoleDocument} role namespace is not null or an empty string.
168      * @param roleDoc the {@link IdentityManagementRoleDocument} to validate.
169      * @return TRUE if the role namespace is not null or an empty string, FALSE otherwise.
170      */
171     protected boolean validRoleNamespace(IdentityManagementRoleDocument roleDoc) {
172         boolean validRoleNamespace = false;
173 
174         if(StringUtils.isNotBlank(roleDoc.getRoleNamespace())) {
175             validRoleNamespace = true;
176         }
177         else{
178             GlobalVariables.getMessageMap().putError("document.roleNamespace",
179                     RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Role Namespace"});
180         }
181 
182         return validRoleNamespace;
183     }
184     /**
185      * ensures the {@link IdentitymangaementRoleDocument} role name is not null or an empty string
186      * @param roleDoc the {@link IdentityManagementRoleDocument} to validate.
187      * @return TRUE if the role name is not null or an empty string, FALSE otherwise.
188      */
189     protected boolean validRoleName(IdentityManagementRoleDocument roleDoc) {
190         boolean validRoleName = false;
191 
192         if(StringUtils.isNotBlank(roleDoc.getRoleName())) {
193             validRoleName = true;
194         }
195         else{
196             GlobalVariables.getMessageMap().putError("document.roleName",
197                     RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Role Name"});
198         }
199 
200         return validRoleName;
201     }
202 
203 	protected boolean canUserAssignRoleMembers(IdentityManagementRoleDocument document){
204         boolean rulePassed = true;
205         Map<String,String> additionalPermissionDetails = new HashMap<String,String>();
206         additionalPermissionDetails.put(KimConstants.AttributeConstants.NAMESPACE_CODE, document.getRoleNamespace());
207         additionalPermissionDetails.put(KimConstants.AttributeConstants.ROLE_NAME, document.getRoleName());
208 		if((document.getMembers()!=null && document.getMembers().size()>0) ||
209 				(document.getDelegationMembers()!=null && document.getDelegationMembers().size()>0)){
210 			if(!getDocumentDictionaryService().getDocumentAuthorizer(document).isAuthorizedByTemplate(
211 					document, KimConstants.NAMESPACE_CODE, KimConstants.PermissionTemplateNames.ASSIGN_ROLE,
212 					GlobalVariables.getUserSession().getPrincipalId(), additionalPermissionDetails, null)){
213 	            rulePassed = false;
214 			}
215 		}
216 		return rulePassed;
217 	}
218 
219     protected boolean validRoleMemberPrincipalIDs(List<KimDocumentRoleMember> roleMembers) {
220         boolean valid = true;
221         List<String> principalIds = new ArrayList<String>();
222         for(KimDocumentRoleMember roleMember: roleMembers) {
223             if (StringUtils.equals(roleMember.getMemberTypeCode(), KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode()) ) {
224                 principalIds.add(roleMember.getMemberId());
225             }
226         }
227         if(!principalIds.isEmpty())       {
228             List<Principal> validPrincipals = getIdentityService().getPrincipals(principalIds);
229             for(KimDocumentRoleMember roleMember: roleMembers) {
230                 if (StringUtils.equals(roleMember.getMemberTypeCode(), MemberType.PRINCIPAL.getCode()) ) {
231                     boolean validPrincipalId = false;
232                     if(validPrincipals != null && !validPrincipals.isEmpty())       {
233                         for(Principal validPrincipal: validPrincipals) {
234                             if(roleMember.getMemberId().equals(validPrincipal.getPrincipalId()))     {
235                              validPrincipalId = true;
236                             }
237                         }
238                     }
239                     if(!validPrincipalId) {
240                         GlobalVariables.getMessageMap().putError("document.member.memberId", RiceKeyConstants.ERROR_MEMBERID_MEMBERTYPE_MISMATCH,
241                                 new String[] {roleMember.getMemberId()});
242                         valid = false;
243                     }
244                 }
245             }
246         }
247         return valid;
248     }
249 
250     @SuppressWarnings("unchecked")
251 	protected boolean validDuplicateRoleName(IdentityManagementRoleDocument roleDoc){
252         Role role = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName(roleDoc.getRoleNamespace(),
253                 roleDoc.getRoleName());
254     	boolean rulePassed = true;
255     	if(role!=null){
256     		if(role.getId().equals(roleDoc.getRoleId())) {
257                 rulePassed = true;
258             }
259     		else{
260 	    		GlobalVariables.getMessageMap().putError("document.roleName",
261 	    				RiceKeyConstants.ERROR_DUPLICATE_ENTRY, new String[] {"Role Name"});
262 	    		rulePassed = false;
263     		}
264     	}
265     	return rulePassed;
266     }
267 
268     protected boolean validRoleMemberActiveDates(List<KimDocumentRoleMember> roleMembers) {
269     	boolean valid = true;
270 		int i = 0;
271     	for(KimDocumentRoleMember roleMember: roleMembers) {
272    			valid &= validateActiveDate("document.members["+i+"].activeToDate", roleMember.getActiveFromDate(), roleMember.getActiveToDate());
273     		i++;
274     	}
275     	return valid;
276     }
277 
278     protected boolean validDelegationMemberActiveDates(List<RoleDocumentDelegationMember> delegationMembers) {
279     	boolean valid = true;
280 		int i = 0;
281     	for(RoleDocumentDelegationMember delegationMember: delegationMembers) {
282    			valid &= validateActiveDate("document.delegationMembers[" + i + "].activeToDate",
283                        delegationMember.getActiveFromDate(), delegationMember.getActiveToDate());
284     		i++;
285     	}
286     	return valid;
287     }
288 
289     protected boolean validPermissions(IdentityManagementRoleDocument document){
290     	Permission kimPermissionInfo;
291     	boolean valid = true;
292     	int i = 0;
293     	for(KimDocumentRolePermission permission: document.getPermissions()){
294     		kimPermissionInfo = permission.getPermission();
295     		if(!permission.isActive() && !hasPermissionToGrantPermission(permission.getPermission(), document)){
296     	        GlobalVariables.getMessageMap().putError("permissions["+i+"].active", RiceKeyConstants.ERROR_ASSIGN_PERMISSION,
297     	        		new String[] {kimPermissionInfo.getNamespaceCode(), kimPermissionInfo.getTemplate().getName()});
298     	        valid = false;
299     		}
300     		i++;
301     	}
302     	return valid;
303     }
304 
305     protected boolean validResponsibilities(IdentityManagementRoleDocument document){
306     	ResponsibilityBo kimResponsibilityImpl;
307     	boolean valid = true;
308     	int i = 0;
309     	for(KimDocumentRoleResponsibility responsibility: document.getResponsibilities()){
310     		kimResponsibilityImpl = responsibility.getKimResponsibility();
311     		if(!responsibility.isActive() && !hasPermissionToGrantResponsibility(ResponsibilityBo.to(responsibility.getKimResponsibility()), document)){
312     	        GlobalVariables.getMessageMap().putError("responsibilities["+i+"].active", RiceKeyConstants.ERROR_ASSIGN_RESPONSIBILITY,
313     	        		new String[] {kimResponsibilityImpl.getNamespaceCode(), kimResponsibilityImpl.getTemplate().getName()});
314     	        valid = false;
315     		}
316     		i++;
317     	}
318     	return valid;
319     }
320 
321     protected boolean validRoleResponsibilitiesActions(List<KimDocumentRoleResponsibility> roleResponsibilities){
322         int i = 0;
323         boolean rulePassed = true;
324     	for(KimDocumentRoleResponsibility roleResponsibility: roleResponsibilities){
325     		if(!getResponsibilityInternalService().areActionsAtAssignmentLevelById(roleResponsibility.getResponsibilityId())) {
326     			validateRoleResponsibilityAction("document.responsibilities["+i+"].roleRspActions[0].priorityNumber", roleResponsibility.getRoleRspActions().get(0));
327             }
328         	i++;
329     	}
330     	return rulePassed;
331     }
332 
333     protected boolean validRoleMembersResponsibilityActions(List<KimDocumentRoleMember> roleMembers){
334         int i = 0;
335         int j;
336         boolean rulePassed = true;
337     	for(KimDocumentRoleMember roleMember: roleMembers){
338     		j = 0;
339     		if(roleMember.getRoleRspActions()!=null && !roleMember.getRoleRspActions().isEmpty()){
340 	    		for(KimDocumentRoleResponsibilityAction roleRspAction: roleMember.getRoleRspActions()){
341 	    			validateRoleResponsibilityAction("document.members["+i+"].roleRspActions["+j+"].priorityNumber", roleRspAction);
342 		        	j++;
343 	    		}
344     		}
345     		i++;
346     	}
347     	return rulePassed;
348     }
349 
350     protected boolean validateRoleResponsibilityAction(String errorPath, KimDocumentRoleResponsibilityAction roleRspAction){
351     	boolean rulePassed = true;
352     	/*if(StringUtils.isBlank(roleRspAction.getActionPolicyCode())){
353     		GlobalVariables.getErrorMap().putError(errorPath,
354     				RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Action Policy Code"});
355     		rulePassed = false;
356     	}
357     	if(roleRspAction.getPriorityNumber()==null){
358     		GlobalVariables.getErrorMap().putError(errorPath,
359     				RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Priority Number"});
360     		rulePassed = false;
361     	}
362     	if(StringUtils.isBlank(roleRspAction.getActionTypeCode())){
363     		GlobalVariables.getErrorMap().putError(errorPath,
364     				RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Action Type Code"});
365     		rulePassed = false;
366     	}*/
367     	if(roleRspAction.getPriorityNumber()!=null &&
368     			(roleRspAction.getPriorityNumber()<PRIORITY_NUMBER_MIN_VALUE
369     					|| roleRspAction.getPriorityNumber()>PRIORITY_NUMBER_MAX_VALUE)){
370     		GlobalVariables.getMessageMap().putError(errorPath,
371    				RiceKeyConstants.ERROR_PRIORITY_NUMBER_RANGE, new String[] {PRIORITY_NUMBER_MIN_VALUE+"", PRIORITY_NUMBER_MAX_VALUE+""});
372     		rulePassed = false;
373     	}
374 
375     	return rulePassed;
376     }
377 
378     protected boolean validateRoleQualifier(List<KimDocumentRoleMember> roleMembers, KimType kimType){
379 		List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
380 
381 		int memberCounter = 0;
382 		int roleMemberCount = 0;
383 		List<RemotableAttributeError> errorsTemp;
384 		Map<String, String> mapToValidate;
385         KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
386         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
387         final List<KimAttributeField> attributeDefinitions = kimTypeService.getAttributeDefinitions(kimType.getId());
388         final Set<String> uniqueAttributeNames = figureOutUniqueQualificationSet(roleMembers, attributeDefinitions);
389 
390 		for(KimDocumentRoleMember roleMember: roleMembers) {
391 			errorsTemp = Collections.emptyList();
392 			mapToValidate = attributeValidationHelper.convertQualifiersToMap(roleMember.getQualifiers());
393 
394             VersionedService<RoleTypeService> versionedRoleTypeService = getVersionedRoleTypeService(kimType);
395             boolean shouldNotValidate = true;
396             if (versionedRoleTypeService != null) {
397                 boolean versionOk = VersionHelper.compareVersion(versionedRoleTypeService.getVersion(),
398                         CoreConstants.Versions.VERSION_2_1_2)!=-1? true:false;
399                 if(versionOk) {
400                     shouldNotValidate = versionedRoleTypeService.getService().shouldValidateQualifiersForMemberType( MemberType.fromCode(roleMember.getMemberTypeCode()));
401                 } else {
402                     shouldNotValidate = false;
403                 }
404             }
405             if(!shouldNotValidate){
406 				errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
407 				validationErrors.addAll(attributeValidationHelper.convertErrorsForMappedFields(
408                         "members[" + memberCounter + "]", errorsTemp));
409 		        memberCounter++;
410 			}
411 			if (uniqueAttributeNames.size() > 0) {
412 				validateUniquePersonRoleQualifiersUniqueForRoleMembership(roleMember, roleMemberCount, roleMembers, uniqueAttributeNames, validationErrors);
413 			}
414 
415 			roleMemberCount += 1;
416     	}
417 
418 		GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
419 
420     	if (validationErrors.isEmpty()) {
421     		return true;
422     	} 
423     	attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
424     	return false;
425     }
426 
427     /**
428      * Finds the names of the unique qualification attributes which this role should be checking against
429      *
430      * @param memberships the memberships (we take the qualification from the first)
431      * @param attributeDefinitions information about the attributeDefinitions
432      * @return a Set of unique attribute ids (with their indices, for error reporting)
433      */
434     protected Set<String> figureOutUniqueQualificationSet(List<KimDocumentRoleMember> memberships, List<KimAttributeField> attributeDefinitions) {
435     	Set<String> uniqueAttributeIds = new HashSet<String>();
436 
437     	if (memberships != null && memberships.size() > 1) { // if there aren't two or more members, doing this whole check is kinda silly
438     		KimDocumentRoleMember membership = memberships.get(0);
439 
440     		for (KimDocumentRoleQualifier qualifier : membership.getQualifiers()) {
441         		if (qualifier != null && qualifier.getKimAttribute() != null && !StringUtils.isBlank(qualifier.getKimAttribute().getAttributeName())) {
442     	    		final KimAttributeField relatedDefinition = DataDictionaryTypeServiceHelper.findAttributeField(
443                             qualifier.getKimAttribute().getAttributeName(), attributeDefinitions);
444 
445     	    		if (relatedDefinition != null && relatedDefinition.isUnique()) {
446     	    			uniqueAttributeIds.add(qualifier.getKimAttrDefnId()); // it's unique - add it to the Set
447     	    		}
448         		}
449     		}
450     	}
451 
452     	return uniqueAttributeIds;
453     }
454 
455     /**
456      * Checks all the qualifiers for the given membership, so that all qualifiers which should be unique are guaranteed to be unique
457      *
458      * @param membershipToCheck the membership to check
459      * @param membershipToCheckIndex the index of the person's membership in the role (for error reporting purposes)
460      * @param validationErrors Map<String, String> of errors to report
461      * @return true if all unique values are indeed unique, false otherwise
462      */
463     protected boolean validateUniquePersonRoleQualifiersUniqueForRoleMembership(KimDocumentRoleMember membershipToCheck, int membershipToCheckIndex, List<KimDocumentRoleMember> memberships, Set<String> uniqueQualifierIds, List<RemotableAttributeError> validationErrors) {
464     	boolean foundError = false;
465     	int count = 0;
466 
467     	for (KimDocumentRoleMember membership : memberships) {
468     		if (membershipToCheckIndex != count) {
469     			if (sameMembership(membershipToCheck, membership)) {
470     				if (sameUniqueMembershipQualifications(membershipToCheck, membership, uniqueQualifierIds)) {
471     					foundError = true;
472     					// add error to each qualifier which is supposed to be unique
473     					int qualifierCount = 0;
474 
475     					for (KimDocumentRoleQualifier qualifier : membership.getQualifiers()) {
476     						if (qualifier != null && uniqueQualifierIds.contains(qualifier.getKimAttrDefnId())) {
477                                 // for new member lines, KimAttribute is not preloaded
478                                 // make sure to load it here in order to obtain the name for use in error message
479                                 KimAttributeBo attr = qualifier.getKimAttribute();
480                                 String attrName = "<unknown>";
481                                 if (attr == null && qualifier.getKimAttrDefnId() != null) {
482                                     attr = KradDataServiceLocator.getDataObjectService().find(KimAttributeBo.class, qualifier.getKimAttrDefnId());
483                                 }
484                                 if (attr != null) {
485                                     attrName = attr.getAttributeName();
486                                 }
487                                 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());
488     						}
489     						qualifierCount += 1;
490     					}
491     				}
492     			}
493     		}
494     		count += 1;
495     	}
496 
497     	return foundError;
498     }
499 
500     /**
501      * Determines if two memberships represent the same member being added: that is, the two memberships have the same type code and id
502      *
503      * @param membershipA the first membership to check
504      * @param membershipB the second membership to check
505      * @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
506      */
507     protected boolean sameMembership(KimDocumentRoleMember membershipA, KimDocumentRoleMember membershipB) {
508     	if (!StringUtils.isBlank(membershipA.getMemberTypeCode()) && !StringUtils.isBlank(membershipB.getMemberTypeCode()) && !StringUtils.isBlank(membershipA.getMemberId()) && !StringUtils.isBlank(membershipB.getMemberId())) {
509     		return membershipA.getMemberTypeCode().equals(membershipB.getMemberTypeCode()) && membershipA.getMemberId().equals(membershipB.getMemberId());
510     	}
511     	return false;
512     }
513 
514     /**
515      * Given two memberships which represent the same member, do they share qualifications?
516      *
517      * @param membershipA the first membership to check
518      * @param membershipB the second membership to check
519      * @param uniqueAttributeIds the Set of attribute definition ids which should be unique
520      * @return
521      */
522     protected boolean sameUniqueMembershipQualifications(KimDocumentRoleMember membershipA, KimDocumentRoleMember membershipB, Set<String> uniqueAttributeIds) {
523     	boolean equalSoFar = true;
524     	for (String kimAttributeDefinitionId : uniqueAttributeIds) {
525     		final KimDocumentRoleQualifier qualifierA = membershipA.getQualifier(kimAttributeDefinitionId);
526     		final KimDocumentRoleQualifier qualifierB = membershipB.getQualifier(kimAttributeDefinitionId);
527 
528     		if (qualifierA != null && qualifierB != null) {
529     			equalSoFar &= (qualifierA.getAttrVal() == null && qualifierB.getAttrVal() == null) || (qualifierA.getAttrVal() == null || qualifierA.getAttrVal().equals(qualifierB.getAttrVal()));
530     		}
531     	}
532     	return equalSoFar;
533     }
534 
535     protected KimDocumentRoleMember getRoleMemberForDelegation(
536     		List<KimDocumentRoleMember> roleMembers, RoleDocumentDelegationMember delegationMember, List<KimDocumentRoleMember> modifiedRoleMembers) {
537     	if((roleMembers==null && modifiedRoleMembers==null) || delegationMember==null || delegationMember.getRoleMemberId()==null) { return null; }
538         for(KimDocumentRoleMember roleMember: modifiedRoleMembers){
539             if(delegationMember.getRoleMemberId().equals(roleMember.getRoleMemberId())) {
540                 return roleMember;
541             }
542         }
543     	for(KimDocumentRoleMember roleMember: roleMembers){
544     		if(delegationMember.getRoleMemberId().equals(roleMember.getRoleMemberId())) {
545                 return roleMember;
546             }
547     	}
548     	return null;
549     }
550 
551     protected boolean validateDelegationMemberRoleQualifier(List<KimDocumentRoleMember> modifiedRoleMembers,
552     		List<RoleDocumentDelegationMember> delegationMembers, KimType kimType, List<KimDocumentRoleMember> nonModifiedRoleMembers){
553 		List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
554 		boolean valid;
555 		int memberCounter = 0;
556 		List<RemotableAttributeError> errorsTemp;
557 		Map<String, String> mapToValidate;
558         KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
559         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
560         KimDocumentRoleMember roleMember;
561         String errorPath;
562         final List<KimAttributeField> attributeDefinitions = kimTypeService.getAttributeDefinitions(kimType.getId());
563         final Set<String> uniqueQualifierAttributes = figureOutUniqueQualificationSetForDelegation(delegationMembers, attributeDefinitions);
564 
565 		for(RoleDocumentDelegationMember delegationMember: delegationMembers) {
566 			errorPath = "delegationMembers["+memberCounter+"]";
567 			mapToValidate = attributeValidationHelper.convertQualifiersToMap(delegationMember.getQualifiers());
568 			if(!delegationMember.isRole()){
569 				errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
570 				validationErrors.addAll(
571 						attributeValidationHelper.convertErrorsForMappedFields(errorPath, errorsTemp));
572 			}
573 			roleMember = getRoleMemberForDelegation(nonModifiedRoleMembers, delegationMember, modifiedRoleMembers);
574 			if(roleMember==null){
575 				valid = false;
576 				GlobalVariables.getMessageMap().putError("document.delegationMembers["+memberCounter+"]", RiceKeyConstants.ERROR_DELEGATE_ROLE_MEMBER_ASSOCIATION, new String[]{});
577 			} else{
578 				errorsTemp = kimTypeService.validateUnmodifiableAttributes(
579 								kimType.getId(),
580 								attributeValidationHelper.convertQualifiersToMap(roleMember.getQualifiers()),
581 								mapToValidate);
582 				validationErrors.addAll(
583 						attributeValidationHelper.convertErrorsForMappedFields(errorPath, errorsTemp) );
584 			}
585 			if (uniqueQualifierAttributes.size() > 0) {
586 				validateUniquePersonRoleQualifiersUniqueForRoleDelegation(delegationMember, memberCounter, delegationMembers, uniqueQualifierAttributes, validationErrors);
587 			}
588 	        memberCounter++;
589     	}
590 		GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
591     	if (validationErrors.isEmpty()) {
592     		valid = true;
593     	} else {
594     		attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
595     		valid = false;
596     	}
597     	return valid;
598     }
599 
600     /**
601      * Finds the names of the unique qualification attributes which this role should be checking against
602      *
603      * @param memberships the memberships (we take the qualification from the first)
604      * @param attributeDefinitions information about the attributeDefinitions
605      * @return a Set of unique attribute ids (with their indices, for error reporting)
606      */
607     protected Set<String> figureOutUniqueQualificationSetForDelegation(List<RoleDocumentDelegationMember> memberships, List<KimAttributeField> attributeDefinitions) {
608     	Set<String> uniqueAttributeIds = new HashSet<String>();
609 
610     	if (memberships != null && memberships.size() > 1) { // if there aren't two or more members, doing this whole check is kinda silly
611     		RoleDocumentDelegationMember membership = memberships.get(0);
612 
613     		for (RoleDocumentDelegationMemberQualifier qualifier : membership.getQualifiers()) {
614         		if (qualifier != null && qualifier.getKimAttribute() != null && !StringUtils.isBlank(qualifier.getKimAttribute().getAttributeName())) {
615     	    		final KimAttributeField relatedDefinition = DataDictionaryTypeServiceHelper.findAttributeField(
616                             qualifier.getKimAttribute().getAttributeName(), attributeDefinitions);
617 
618     	    		if (relatedDefinition.isUnique()) {
619     	    			uniqueAttributeIds.add(qualifier.getKimAttrDefnId()); // it's unique - add it to the Set
620     	    		}
621         		}
622     		}
623     	}
624 
625     	return uniqueAttributeIds;
626     }
627 
628     /**
629      * Checks all the qualifiers for the given membership, so that all qualifiers which should be unique are guaranteed to be unique
630      *
631      * @param delegationMembershipToCheck the membership to check
632      * @param membershipToCheckIndex the index of the person's membership in the role (for error reporting purposes)
633      * @param validationErrors Map<String, String> of errors to report
634      * @return true if all unique values are indeed unique, false otherwise
635      */
636     protected boolean validateUniquePersonRoleQualifiersUniqueForRoleDelegation(RoleDocumentDelegationMember delegationMembershipToCheck, int membershipToCheckIndex, List<RoleDocumentDelegationMember> delegationMemberships, Set<String> uniqueQualifierIds, List<RemotableAttributeError> validationErrors) {
637     	boolean foundError = false;
638     	int count = 0;
639 
640     	for (RoleDocumentDelegationMember delegationMembership : delegationMemberships) {
641     		if (membershipToCheckIndex != count) {
642     			if (sameDelegationMembership(delegationMembershipToCheck, delegationMembership)) {
643     				if (sameUniqueDelegationMembershipQualifications(delegationMembershipToCheck, delegationMembership, uniqueQualifierIds)) {
644     					foundError = true;
645     					// add error to each qualifier which is supposed to be unique
646     					int qualifierCount = 0;
647 
648     					for (RoleDocumentDelegationMemberQualifier qualifier : delegationMembership.getQualifiers()) {
649     						if (qualifier != null && uniqueQualifierIds.contains(qualifier.getKimAttrDefnId())) {
650     							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());
651     						}
652     						qualifierCount += 1;
653     					}
654     				}
655     			}
656     		}
657     		count += 1;
658     	}
659 
660     	return foundError;
661     }
662 
663     /**
664      * Determines if two memberships represent the same member being added: that is, the two memberships have the same type code and id
665      *
666      * @param membershipA the first membership to check
667      * @param membershipB the second membership to check
668      * @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
669      */
670     protected boolean sameDelegationMembership(RoleDocumentDelegationMember membershipA, RoleDocumentDelegationMember membershipB) {
671     	if (!StringUtils.isBlank(membershipA.getMemberTypeCode()) && !StringUtils.isBlank(membershipB.getMemberTypeCode()) && !StringUtils.isBlank(membershipA.getMemberId()) && !StringUtils.isBlank(membershipB.getMemberId())) {
672     		return membershipA.getMemberTypeCode().equals(membershipB.getMemberTypeCode()) && membershipA.getMemberId().equals(membershipB.getMemberId());
673     	}
674     	return false;
675     }
676 
677     /**
678      * Given two memberships which represent the same member, do they share qualifications?
679      *
680      * @param membershipA the first membership to check
681      * @param membershipB the second membership to check
682      * @param uniqueAttributeIds the Set of attribute definition ids which should be unique
683      * @return
684      */
685     protected boolean sameUniqueDelegationMembershipQualifications(RoleDocumentDelegationMember membershipA, RoleDocumentDelegationMember membershipB, Set<String> uniqueAttributeIds) {
686     	boolean equalSoFar = true;
687     	for (String kimAttributeDefinitionId : uniqueAttributeIds) {
688     		final RoleDocumentDelegationMemberQualifier qualifierA = membershipA.getQualifier(kimAttributeDefinitionId);
689     		final RoleDocumentDelegationMemberQualifier qualifierB = membershipB.getQualifier(kimAttributeDefinitionId);
690 
691     		if (qualifierA != null && qualifierB != null) {
692     			equalSoFar &= (qualifierA.getAttrVal() == null && qualifierB.getAttrVal() == null) || (qualifierA.getAttrVal() == null || qualifierA.getAttrVal().equals(qualifierB.getAttrVal()));
693     		}
694     	}
695     	return equalSoFar;
696     }
697 
698 	protected boolean validateActiveDate(String errorPath, Timestamp activeFromDate, Timestamp activeToDate) {
699 		// TODO : do not have detail bus rule yet, so just check this for now.
700 		boolean valid = true;
701 		if (activeFromDate != null && activeToDate !=null && activeToDate.before(activeFromDate)) {
702 	        MessageMap errorMap = GlobalVariables.getMessageMap();
703             errorMap.putError(errorPath, RiceKeyConstants.ERROR_ACTIVE_TO_DATE_BEFORE_FROM_DATE);
704             valid = false;
705 
706 		}
707 		return valid;
708 	}
709 
710 	/**
711 	 *
712 	 * This method checks to see if adding a role to role membership
713 	 * creates a circular reference.
714 	 *
715 	 * @param addMemberEvent
716 	 * @return true  - ok to assign, no circular references
717 	 *         false - do not make assignment, will create circular reference.
718 	 */
719 	protected boolean checkForCircularRoleMembership(AddMemberEvent addMemberEvent)
720 	{
721 		KimDocumentRoleMember newMember = addMemberEvent.getMember();
722 		if (newMember == null || StringUtils.isBlank(newMember.getMemberId())){
723 			MessageMap errorMap = GlobalVariables.getMessageMap();
724 			errorMap.putError("member.memberId", RiceKeyConstants.ERROR_INVALID_ROLE, new String[] {""});
725 			return false;
726 		}
727 		Set<String> roleMemberIds = null;
728 		// if the role member is a role, check to make sure we won't be creating a circular reference.
729 		// Verify that the new role is not already related to the role either directly or indirectly
730 		if (newMember.isRole()){
731 			// get all nested role member ids that are of type role
732 			RoleService roleService = KimApiServiceLocator.getRoleService();
733 			roleMemberIds = roleService.getRoleTypeRoleMemberIds(newMember.getMemberId());
734 
735 			// check to see if the document role is not a member of the new member role
736 			IdentityManagementRoleDocument document = (IdentityManagementRoleDocument)addMemberEvent.getDocument();
737 			if (roleMemberIds.contains(document.getRoleId())){
738 				MessageMap errorMap = GlobalVariables.getMessageMap();
739 				errorMap.putError("member.memberId", RiceKeyConstants.ERROR_ASSIGN_ROLE_MEMBER_CIRCULAR, new String[] {newMember.getMemberId()});
740 				return false;
741 			}
742 		}
743 		return true;
744 	}
745 
746 	/**
747 	 * @return the addResponsibilityRule
748 	 */
749 	public AddResponsibilityRule getAddResponsibilityRule() {
750 		if(addResponsibilityRule == null){
751 			try {
752 				addResponsibilityRule = addResponsibilityRuleClass.newInstance();
753 			} catch ( Exception ex ) {
754 				throw new RuntimeException( "Unable to create AddResponsibilityRule instance using class: " + addResponsibilityRuleClass, ex );
755 			}
756 		}
757 		return addResponsibilityRule;
758 	}
759 
760 	/**
761 	 * @return the addPermissionRule
762 	 */
763 	public AddPermissionRule getAddPermissionRule() {
764 		if(addPermissionRule == null){
765 			try {
766 				addPermissionRule = addPermissionRuleClass.newInstance();
767 			} catch ( Exception ex ) {
768 				throw new RuntimeException( "Unable to create AddPermissionRule instance using class: " + addPermissionRuleClass, ex );
769 			}
770 		}
771 		return addPermissionRule;
772 	}
773 
774 	/**
775 	 * @return the addMemberRule
776 	 */
777 	public AddMemberRule getAddMemberRule() {
778 		if(addMemberRule == null){
779 			try {
780 				addMemberRule = addMemberRuleClass.newInstance();
781 			} catch ( Exception ex ) {
782 				throw new RuntimeException( "Unable to create AddMemberRule instance using class: " + addMemberRuleClass, ex );
783 			}
784 		}
785 		return addMemberRule;
786 	}
787 
788 	/**
789 	 * @return the addDelegationRule
790 	 */
791 	public AddDelegationRule getAddDelegationRule() {
792 		if(addDelegationRule == null){
793 			try {
794 				addDelegationRule = addDelegationRuleClass.newInstance();
795 			} catch ( Exception ex ) {
796 				throw new RuntimeException( "Unable to create AddDelegationRule instance using class: " + addDelegationRuleClass, ex );
797 			}
798 		}
799 		return addDelegationRule;
800 	}
801 
802 	/**
803 	 * @return the addDelegationMemberRule
804 	 */
805 	public AddDelegationMemberRule getAddDelegationMemberRule() {
806 		if(addDelegationMemberRule == null){
807 			try {
808 				addDelegationMemberRule = addDelegationMemberRuleClass.newInstance();
809 			} catch ( Exception ex ) {
810 				throw new RuntimeException( "Unable to create AddDelegationMemberRule instance using class: " + addDelegationMemberRuleClass, ex );
811 			}
812 		}
813 		return addDelegationMemberRule;
814 	}
815 
816     public boolean processAddPermission(AddPermissionEvent addPermissionEvent) {
817         return getAddPermissionRule().processAddPermission(addPermissionEvent);
818     }
819 
820     public boolean hasPermissionToGrantPermission(Permission kimPermissionInfo , IdentityManagementRoleDocument document){
821         return getAddPermissionRule().hasPermissionToGrantPermission(kimPermissionInfo, document);
822     }
823 
824     public boolean processAddResponsibility(AddResponsibilityEvent addResponsibilityEvent) {
825         return getAddResponsibilityRule().processAddResponsibility(addResponsibilityEvent);
826     }
827 
828     public boolean hasPermissionToGrantResponsibility(Responsibility kimResponsibilityInfo, IdentityManagementRoleDocument document) {
829         return getAddResponsibilityRule().hasPermissionToGrantResponsibility(kimResponsibilityInfo, document);
830     }
831 
832     public boolean processAddMember(AddMemberEvent addMemberEvent) {
833         boolean success = new KimDocumentMemberRule().processAddMember(addMemberEvent);
834         success &= validateActiveDate("member.activeFromDate", addMemberEvent.getMember().getActiveFromDate(), addMemberEvent.getMember().getActiveToDate());
835         success &= checkForCircularRoleMembership(addMemberEvent);
836         return success;
837     }
838 
839     public boolean processAddDelegation(AddDelegationEvent addDelegationEvent) {
840         return getAddDelegationRule().processAddDelegation(addDelegationEvent);
841     }
842 
843     public boolean processAddDelegationMember(AddDelegationMemberEvent addDelegationMemberEvent) {
844         boolean success = new RoleDocumentDelegationMemberRule().processAddDelegationMember(addDelegationMemberEvent);
845         RoleDocumentDelegationMember roleDocumentDelegationMember = addDelegationMemberEvent.getDelegationMember();
846         success &= validateActiveDate("delegationMember.activeFromDate", roleDocumentDelegationMember.getActiveFromDate(), roleDocumentDelegationMember.getActiveToDate());
847         return success;
848     }
849 
850     public ResponsibilityInternalService getResponsibilityInternalService() {
851     	if ( responsibilityInternalService == null ) {
852     		responsibilityInternalService = KimImplServiceLocator.getResponsibilityInternalService();
853     	}
854 		return responsibilityInternalService;
855 	}
856 
857     protected RoleTypeService getRoleTypeService(KimType typeInfo) {
858         String serviceName = typeInfo.getServiceName();
859         if (serviceName != null) {
860             try {
861                 KimTypeService service = (KimTypeService) GlobalResourceLoader.getService(QName.valueOf(serviceName));
862                 if (service != null && service instanceof RoleTypeService) {
863                     return (RoleTypeService) service;
864                 }
865                 return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
866             } catch (Exception ex) {
867                 return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
868             }
869         }
870         return null;
871     }
872 
873     private static class VersionedService<T> {
874 
875         String version;
876         T service;
877 
878         VersionedService(String version, T service) {
879             this.version = version;
880             this.service = service;
881         }
882 
883         T getService() {
884             return this.service;
885         }
886 
887         String getVersion() {
888             return this.version;
889         }
890 
891     }
892 
893     protected VersionedService<RoleTypeService> getVersionedRoleTypeService(KimType typeInfo) {
894         String serviceName = typeInfo.getServiceName();
895         if (serviceName != null) {
896             String version = "2.0.0"; // default version since the base services have been available since then
897             RoleTypeService roleTypeService = null;
898 
899             try {
900 
901                 ServiceBus serviceBus = KsbApiServiceLocator.getServiceBus();
902                 Endpoint endpoint = serviceBus.getEndpoint(QName.valueOf(serviceName));
903                 if (endpoint != null) {
904                     version = endpoint.getServiceConfiguration().getServiceVersion();
905                 }
906                 KimTypeService service = (KimTypeService) GlobalResourceLoader.getService(QName.valueOf(serviceName));
907                 if (service != null && service instanceof RoleTypeService) {
908                     roleTypeService = (RoleTypeService) service;
909                 } else {
910                     roleTypeService = (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
911                 }
912             } catch (Exception ex) {
913                 roleTypeService = (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
914             }
915 
916             return new VersionedService<RoleTypeService>(version, roleTypeService);
917         }
918 
919         return null;
920     }
921 
922 }