View Javadoc
1   /**
2    * Copyright 2005-2014 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 static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
19  
20  import java.sql.Timestamp;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.commons.collections.CollectionUtils;
29  import org.apache.commons.lang.StringUtils;
30  import org.kuali.rice.core.api.criteria.QueryByCriteria;
31  import org.kuali.rice.core.api.uif.RemotableAttributeError;
32  import org.kuali.rice.core.api.util.RiceKeyConstants;
33  import org.kuali.rice.core.api.util.io.SerializationUtils;
34  import org.kuali.rice.kim.api.KimConstants;
35  import org.kuali.rice.kim.api.identity.IdentityService;
36  import org.kuali.rice.kim.api.identity.entity.EntityDefault;
37  import org.kuali.rice.kim.api.identity.principal.Principal;
38  import org.kuali.rice.kim.api.role.RoleMember;
39  import org.kuali.rice.kim.api.role.RoleService;
40  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
41  import org.kuali.rice.kim.api.type.KimAttributeField;
42  import org.kuali.rice.kim.api.type.KimType;
43  import org.kuali.rice.kim.bo.ui.KimDocumentRoleMember;
44  import org.kuali.rice.kim.bo.ui.KimDocumentRoleQualifier;
45  import org.kuali.rice.kim.bo.ui.PersonDocumentAffiliation;
46  import org.kuali.rice.kim.bo.ui.PersonDocumentBoDefaultBase;
47  import org.kuali.rice.kim.bo.ui.PersonDocumentEmploymentInfo;
48  import org.kuali.rice.kim.bo.ui.PersonDocumentGroup;
49  import org.kuali.rice.kim.bo.ui.PersonDocumentName;
50  import org.kuali.rice.kim.bo.ui.PersonDocumentRole;
51  import org.kuali.rice.kim.bo.ui.RoleDocumentDelegationMember;
52  import org.kuali.rice.kim.document.IdentityManagementPersonDocument;
53  import org.kuali.rice.kim.document.authorization.IdentityManagementKimDocumentAuthorizer;
54  import org.kuali.rice.kim.framework.services.KimFrameworkServiceLocator;
55  import org.kuali.rice.kim.framework.type.KimTypeService;
56  import org.kuali.rice.kim.impl.KIMPropertyConstants;
57  import org.kuali.rice.kim.impl.identity.principal.PrincipalBo;
58  import org.kuali.rice.kim.impl.type.KimTypeBo;
59  import org.kuali.rice.kim.rule.event.ui.AddGroupEvent;
60  import org.kuali.rice.kim.rule.event.ui.AddPersonDelegationMemberEvent;
61  import org.kuali.rice.kim.rule.event.ui.AddRoleEvent;
62  import org.kuali.rice.kim.rule.ui.AddGroupRule;
63  import org.kuali.rice.kim.rule.ui.AddPersonDelegationMemberRule;
64  import org.kuali.rice.kim.rule.ui.AddPersonDocumentRoleQualifierRule;
65  import org.kuali.rice.kim.rule.ui.AddRoleRule;
66  import org.kuali.rice.kim.rules.ui.PersonDocumentDelegationMemberRule;
67  import org.kuali.rice.kim.rules.ui.PersonDocumentGroupRule;
68  import org.kuali.rice.kim.rules.ui.PersonDocumentRoleRule;
69  import org.kuali.rice.kns.kim.type.DataDictionaryTypeServiceHelper;
70  import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
71  import org.kuali.rice.krad.data.KradDataServiceLocator;
72  import org.kuali.rice.krad.document.Document;
73  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
74  import org.kuali.rice.krad.util.GlobalVariables;
75  import org.kuali.rice.krad.util.KRADConstants;
76  
77  /**
78   * This is a description of what this class does - shyu don't forget to fill this in.
79   *
80   * @author Kuali Rice Team (rice.collab@kuali.org)
81   *
82   */
83  public class IdentityManagementPersonDocumentRule extends TransactionalDocumentRuleBase implements AddGroupRule, AddRoleRule, AddPersonDocumentRoleQualifierRule, AddPersonDelegationMemberRule {
84  
85  	protected AddGroupRule addGroupRule;
86  	protected AddRoleRule  addRoleRule;
87  	protected AddPersonDelegationMemberRule addPersonDelegationMemberRule;
88  	protected IdentityManagementKimDocumentAuthorizer authorizer;
89  	private IdentityService identityService;
90  	private RoleService roleService;
91  	protected Class<? extends AddGroupRule> addGroupRuleClass = PersonDocumentGroupRule.class;
92  	protected Class<? extends AddRoleRule> addRoleRuleClass = PersonDocumentRoleRule.class;
93  	protected Class<? extends AddPersonDelegationMemberRule> addPersonDelegationMemberRuleClass = PersonDocumentDelegationMemberRule.class;
94      protected ActiveRoleMemberHelper activeRoleMemberHelper = new ActiveRoleMemberHelper();
95  	protected AttributeValidationHelper attributeValidationHelper = new AttributeValidationHelper();
96  
97      @Override
98      protected boolean processCustomSaveDocumentBusinessRules(Document document) {
99          if (!(document instanceof IdentityManagementPersonDocument)) {
100             return false;
101         }
102 
103         IdentityManagementPersonDocument personDoc = (IdentityManagementPersonDocument)document;
104         boolean valid = true;
105 
106         GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
107 
108         //KRADServiceLocatorInternal.getDictionaryValidationService().validateDocument(document);
109         getDictionaryValidationService().validateDocumentAndUpdatableReferencesRecursively(document, getMaxDictionaryValidationDepth(), true, false);
110         valid &= validDuplicatePrincipalName(personDoc);
111         EntityDefault origEntity = getIdentityService().getEntityDefault(personDoc.getEntityId());
112         boolean isCreatingNew = origEntity==null?true:false;
113         if(canModifyEntity(GlobalVariables.getUserSession().getPrincipalId(), personDoc.getPrincipalId()) || isCreatingNew) {
114         	valid &= validateEntityInformation(isCreatingNew, personDoc);
115         }
116         // kimtypeservice.validateAttributes is not working yet.
117         valid &= validateRoleQualifier (personDoc.getRoles());
118         valid &= validateDelegationMemberRoleQualifier(personDoc.getDelegationMembers());
119         if (StringUtils.isNotBlank(personDoc.getPrincipalName())) {
120         	valid &= doesPrincipalNameExist (personDoc.getPrincipalName(), personDoc.getPrincipalId());
121         }
122 
123         valid &= validActiveDatesForRole (personDoc.getRoles());
124         valid &= validActiveDatesForGroup (personDoc.getGroups());
125         valid &= validActiveDatesForDelegations (personDoc.getDelegationMembers());
126 
127 
128         // all failed at this point.
129 //        valid &= checkUnassignableRoles(personDoc);
130 //        valid &= checkUnpopulatableGroups(personDoc);
131 
132         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
133 
134         return valid;
135     }
136 
137     protected boolean validateEntityInformation(boolean isCreatingNew, IdentityManagementPersonDocument personDoc){
138         boolean valid = true;
139         boolean canOverridePrivacyPreferences = canOverrideEntityPrivacyPreferences(GlobalVariables.getUserSession().getPrincipalId(), personDoc.getPrincipalId());
140         valid &= checkMultipleDefault (personDoc.getAffiliations(), "affiliations");
141         if(isCreatingNew || canOverridePrivacyPreferences || !personDoc.getPrivacy().isSuppressName()) {
142         	valid &= checkMultipleDefault (personDoc.getNames(), "names");
143         }
144         if(isCreatingNew || canOverridePrivacyPreferences || !personDoc.getPrivacy().isSuppressAddress()) {
145         	valid &= checkMultipleDefault (personDoc.getAddrs(), "addrs");
146         }
147         if(isCreatingNew || canOverridePrivacyPreferences || !personDoc.getPrivacy().isSuppressPhone()) {
148         	valid &= checkMultipleDefault (personDoc.getPhones(), "phones");
149         }
150         if(isCreatingNew || canOverridePrivacyPreferences || !personDoc.getPrivacy().isSuppressEmail()) {
151         	valid &= checkMultipleDefault (personDoc.getEmails(), "emails");
152         }
153         valid &= checkPrimaryEmploymentInfo (personDoc.getAffiliations());
154         valid &= validEmployeeIDForAffiliation(personDoc.getAffiliations());
155         valid &= checkAffiliationTypeChange (personDoc.getAffiliations());
156         valid &= checkUniqueAffiliationTypePerCampus(personDoc.getAffiliations());
157     	return valid;
158     }
159 
160 	protected boolean validDuplicatePrincipalName(IdentityManagementPersonDocument personDoc){
161     	List<PrincipalBo> prncplImpls = KradDataServiceLocator.getDataObjectService().findMatching(
162     	        PrincipalBo.class, 
163     	        QueryByCriteria.Builder.forAttribute( KIMPropertyConstants.Principal.PRINCIPAL_NAME, 
164     	                personDoc.getPrincipalName()).build() ).getResults();
165     	boolean rulePassed = true;
166     	if( !prncplImpls.isEmpty() ){
167     		if(prncplImpls.size() == 1 
168     		        && StringUtils.equals( prncplImpls.get(0).getPrincipalId(), personDoc.getPrincipalId()) ) {
169     			rulePassed = true;
170             } else {
171 	    		GlobalVariables.getMessageMap().putError("document.principalName",
172 	    				RiceKeyConstants.ERROR_DUPLICATE_ENTRY, new String[] {"Principal Name"});
173 	    		rulePassed = false;
174     		}
175     	}
176     	return rulePassed;
177     }
178 
179 //	protected boolean checkUnassignableRoles(IdentityManagementPersonDocument document) {
180 //		boolean valid = true;
181 //    	Map<String,Set<String>> unassignableRoles = getAuthorizer( document ).getUnassignableRoles(document, GlobalVariables.getUserSession().getPerson());
182 //        for (String namespaceCode : unassignableRoles.keySet()) {
183 //        	for (String roleName : unassignableRoles.get(namespaceCode)) {
184 //        		int i = 0;
185 //        		for (PersonDocumentRole role : document.getRoles()) {
186 //        			if (role.isEditable() && namespaceCode.endsWith(role.getNamespaceCode()) && roleName.equals(role.getRoleName())) {
187 //        				GlobalVariables.getMessageMap().putError("roles["+i+"].roleId", RiceKeyConstants.ERROR_ASSIGN_ROLE, new String[] {namespaceCode, roleName});
188 //        	        	valid = false;
189 //        			}
190 //        			i++;
191 //        		}
192 //        	}
193 //        }
194 //        return valid;
195 //	}
196 //
197 //	protected boolean checkUnpopulatableGroups(IdentityManagementPersonDocument document) {
198 //		boolean valid = true;
199 //    	Map<String,Set<String>> unpopulatableGroups = getAuthorizer( document ).getUnpopulateableGroups(document, GlobalVariables.getUserSession().getPerson());
200 //        for (String namespaceCode : unpopulatableGroups.keySet()) {
201 //        	for (String groupName : unpopulatableGroups.get(namespaceCode)) {
202 //        		int i = 0;
203 //        		for (PersonDocumentGroup group : document.getGroups()) {
204 //        			if ( (group.getNamespaceCode() != null && namespaceCode.endsWith(group.getNamespaceCode())) && (group.getGroupName() != null && groupName.equals(group.getGroupName()))) {
205 //        				GlobalVariables.getMessageMap().putError("groups["+i+"].groupId", RiceKeyConstants.ERROR_POPULATE_GROUP, new String[] {namespaceCode, groupName});
206 //        			}
207 //        			i++;
208 //        		}
209 //        	}
210 //        	valid = false;
211 //        }
212 //        return valid;
213 //	}
214 
215     @Override
216 	protected boolean processCustomRouteDocumentBusinessRules(Document document) {
217 		super.processCustomRouteDocumentBusinessRules(document);
218         IdentityManagementPersonDocument personDoc = (IdentityManagementPersonDocument)document;
219         boolean valid = true;
220         GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
221         valid &= validateAffiliationAndName( personDoc );
222         valid &= checkAffiliationEithOneEMpInfo (personDoc.getAffiliations());
223         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
224 
225         return valid;
226 	}
227 
228 
229 	protected boolean checkMultipleDefault (List <? extends PersonDocumentBoDefaultBase> boList, String listName) {
230     	boolean valid = true;
231     	boolean isDefaultSet = false;
232     	int i = 0;
233     	for (PersonDocumentBoDefaultBase item : boList) {
234      		if (item.isDflt()) {
235      			if (isDefaultSet) {
236      				GlobalVariables.getMessageMap().putError(listName+"[" + i + "].dflt",RiceKeyConstants.ERROR_MULTIPLE_DEFAULT_SELETION);
237      				valid = false;
238      			} else {
239      				isDefaultSet = true;
240      			}
241      		}
242      		i++;
243     	}
244     	if (!boList.isEmpty() && !isDefaultSet) {
245 				GlobalVariables.getMessageMap().putError(listName+"[0].dflt",RiceKeyConstants.ERROR_NO_DEFAULT_SELETION);
246     	}
247     	return valid;
248     }
249 
250     protected boolean checkPrimaryEmploymentInfo (List <PersonDocumentAffiliation> affiliations) {
251     	boolean valid = true;
252     	int i = 0;
253     	int firstAfflnCounter = -1;
254     	boolean isPrimarySet = false;
255     	for (PersonDocumentAffiliation affiliation : affiliations) {
256     		int j = 0;
257     		for (PersonDocumentEmploymentInfo empInfo : affiliation.getEmpInfos()) {
258      			if(firstAfflnCounter==-1) {
259      				firstAfflnCounter = i;
260                 }
261     			if (empInfo.isPrimary()) {
262 	     			if (isPrimarySet) {
263 	     				// primary per principal or primary per affiliation ?
264 	     				GlobalVariables.getMessageMap().putError("affiliations[" + i + "].empInfos["+ j +"].primary",RiceKeyConstants.ERROR_MULTIPLE_PRIMARY_EMPLOYMENT);
265 	     				valid = false;
266 	     			} else {
267 	     				isPrimarySet = true;
268 	     			}
269 	     			j++;
270 	     		}
271     		}
272      		i++;
273     	}
274     	if(!isPrimarySet && firstAfflnCounter!=-1){
275     		GlobalVariables.getMessageMap().putError("affiliations[" + firstAfflnCounter + "].empInfos[0].primary",RiceKeyConstants.ERROR_NO_PRIMARY_EMPLOYMENT);
276     		valid = false;
277     	}
278     	return valid;
279     }
280 
281     protected boolean checkAffiliationTypeChange (List <PersonDocumentAffiliation> affiliations) {
282     	boolean valid = true;
283     	int i = 0;
284     	for (PersonDocumentAffiliation affiliation : affiliations) {
285     		if (affiliation.getAffiliationType() != null && !affiliation.getAffiliationTypeCode().equals(affiliation.getAffiliationType().getCode())) {
286     			PersonDocumentAffiliation copiedAffiliation = (PersonDocumentAffiliation) SerializationUtils.deepCopy(
287                         affiliation);
288     			KradDataServiceLocator.getDataObjectService().wrap(copiedAffiliation).fetchRelationship("affiliationType");
289     			if (!copiedAffiliation.getAffiliationType().isEmploymentAffiliationType() && affiliation.getAffiliationType().isEmploymentAffiliationType() && !copiedAffiliation.getEmpInfos().isEmpty()) {
290 		     		GlobalVariables.getMessageMap().putError("affiliations[" + i + "].affiliationTypeCode",RiceKeyConstants.ERROR_NOT_EMPLOYMENT_AFFILIATION_TYPE,new String[] {affiliation.getAffiliationType().getName(), copiedAffiliation.getAffiliationType().getName()});
291 		     		valid = false;
292 	    		}
293     		}
294         	i++;
295     	}
296     	return valid;
297     }
298 
299     protected boolean validEmployeeIDForAffiliation(List <PersonDocumentAffiliation> affiliations) {
300     	boolean valid = true;
301     	int i = 0;
302     	int j = 0;
303     	for(PersonDocumentAffiliation affiliation : affiliations) {
304     		if(affiliation.getAffiliationType() != null && affiliation.getAffiliationType().isEmploymentAffiliationType()){
305     			if(affiliation.getEmpInfos()!=null){
306     	    		j = 0;
307     	    		for (PersonDocumentEmploymentInfo empInfo : affiliation.getEmpInfos()) {
308     	    			if (StringUtils.isEmpty(empInfo.getEmployeeId())) {
309    		     				GlobalVariables.getMessageMap().putError("affiliations[" + i + "].empInfos["+ j +"].employeeId", RiceKeyConstants.ERROR_REQUIRED_CONDITIONALLY, new String[] {"Employee ID", "an employee"});
310    		     				valid = false;
311     		     			j++;
312     		     		}
313     	    		}
314 	    		}
315     		}
316         	i++;
317     	}
318     	return valid;
319     }
320 
321     protected boolean isPersonAnEmployee(List<PersonDocumentAffiliation> affiliations){
322     	boolean isEmployee = false;
323     	for (PersonDocumentAffiliation affiliation : affiliations){
324     		if (affiliation.getAffiliationType() != null && affiliation.getAffiliationType().isEmploymentAffiliationType()){
325     			isEmployee = true;
326     			break;
327     		}
328     	}
329     	return isEmployee;
330     }
331 
332     protected boolean checkUniqueAffiliationTypePerCampus (List <PersonDocumentAffiliation> affiliations) {
333     	boolean valid = true;
334     	int i = 0;
335     	for (PersonDocumentAffiliation affiliation : affiliations) {
336     		int j = 0;
337         	for (PersonDocumentAffiliation affiliation1 : affiliations) {
338 	    		if (j > i && affiliation.getAffiliationTypeCode() .equals(affiliation1.getAffiliationTypeCode()) && affiliation.getCampusCode().equals(affiliation1.getCampusCode())) {
339 			     		GlobalVariables.getMessageMap().putError("affiliations[" + j + "].affiliationTypeCode",RiceKeyConstants.ERROR_NOT_UNIQUE_AFFILIATION_TYPE_PER_CAMPUS, affiliation.getAffiliationType().getName());
340 			     		valid = false;
341 	    		}
342 	    		j++;
343         	}
344         	i++;
345     	}
346     	return valid;
347     }
348 
349     protected boolean checkAffiliationEithOneEMpInfo (List <PersonDocumentAffiliation> affiliations) {
350     	boolean valid = true;
351     	int i = 0;
352     	for (PersonDocumentAffiliation affiliation : affiliations) {
353 	    		if (affiliation.getAffiliationType() .isEmploymentAffiliationType() && affiliation.getEmpInfos().isEmpty()) {
354 			     		GlobalVariables.getMessageMap().putError("affiliations[" + i + "].affiliationTypeCode",RiceKeyConstants.ERROR_ONE_ITEM_REQUIRED, "Employment Information");
355 			     		valid = false;
356 	    		}
357         	i++;
358     	}
359     	return valid;
360     }
361 
362     /*
363      * Verify at least one affiliation and one default name
364      */
365     protected boolean validateAffiliationAndName(IdentityManagementPersonDocument personDoc) {
366     	boolean valid = true;
367     	if (personDoc.getAffiliations().isEmpty()) {
368      		GlobalVariables.getMessageMap().putError("affiliations[0]",RiceKeyConstants.ERROR_ONE_ITEM_REQUIRED, "affiliation");
369      		valid = false;
370     	}
371     	if (personDoc.getNames().isEmpty()) {
372      		GlobalVariables.getMessageMap().putError("names[0]",RiceKeyConstants.ERROR_ONE_ITEM_REQUIRED, "name");
373      		valid = false;
374     	} else{
375         	boolean activeExists = false;
376         	for(PersonDocumentName name: personDoc.getNames()){
377     	    	if(name.isActive()){
378     	    		activeExists = true;
379    	    		}
380         	}
381         	if(!activeExists){
382         		GlobalVariables.getMessageMap().putError("names[0]", RiceKeyConstants.ERROR_ONE_ACTIVE_ITEM_REQUIRED, "name");
383 	     		valid = false;
384         	}
385         	return valid;
386 
387     	}
388     	return valid;
389     }
390 
391     protected boolean doesPrincipalNameExist (String principalName, String principalId) {
392     	Principal principal = getIdentityService().getPrincipalByPrincipalName(principalName);
393     	if (principal != null && (StringUtils.isBlank(principalId) || !principal.getPrincipalId().equals(principalId))) {
394         	GlobalVariables.getMessageMap().putError(KIMPropertyConstants.Person.PRINCIPAL_NAME,RiceKeyConstants.ERROR_EXIST_PRINCIPAL_NAME, principalName);
395 			return false;
396     	}
397     	return true;
398     }
399 
400     protected boolean validateRoleQualifier( List<PersonDocumentRole> roles ) {
401 		List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
402         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
403         int i = 0;
404     	for(PersonDocumentRole role : roles ) {
405     		KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(KimTypeBo.to(
406                     role.getKimRoleType()));
407         	if(CollectionUtils.isEmpty(role.getRolePrncpls()) && !role.getDefinitions().isEmpty()){
408         		KimType kimTypeInfo = KimApiServiceLocator.getKimTypeInfoService().getKimType(role.getKimRoleType().getId());
409         		Map<String, String> blankQualifiers = attributeValidationHelper.getBlankValueQualifiersMap(kimTypeInfo.getAttributeDefinitions());
410         		List<RemotableAttributeError> localErrors = kimTypeService.validateAttributes(
411         			role.getKimRoleType().getId(), blankQualifiers);
412         		if(localErrors!=null && !localErrors.isEmpty()){
413         			GlobalVariables.getMessageMap().putError("document.roles["+i+"].newRolePrncpl.qualifiers[0].attrVal",
414         					RiceKeyConstants.ERROR_ONE_ITEM_REQUIRED, "Role Qualifier");
415         			return false;
416         		}
417         	}
418 
419         	final List<KimAttributeField> attributeDefinitions = role.getDefinitions();
420         	final Set<String> uniqueQualifierAttributes = findUniqueQualificationAttributes(role, attributeDefinitions);
421 
422 	        if ( kimTypeService != null ) {
423 		        int j = 0;
424 
425 	        	for ( KimDocumentRoleMember rolePrincipal : activeRoleMemberHelper.getActiveRoleMembers(role.getRolePrncpls()) ) {
426 	        		List<RemotableAttributeError> localErrors = kimTypeService.validateAttributes( role.getKimRoleType().getId(), attributeValidationHelper.convertQualifiersToMap( rolePrincipal.getQualifiers() ) );
427 			        validationErrors.addAll( attributeValidationHelper.convertErrors(
428                             "roles[" + i + "].rolePrncpls[" + j + "]",
429                             attributeValidationHelper.convertQualifiersToAttrIdxMap(rolePrincipal.getQualifiers()),
430                             localErrors));
431 
432 			        if (uniqueQualifierAttributes.size() > 0) {
433 			        	validateUniquePersonRoleQualifiersUniqueForMembership(role, rolePrincipal, j, uniqueQualifierAttributes, i, validationErrors);
434 			        }
435 
436 			        j++;
437 		        }
438 	        }
439         	i++;
440     	}
441         GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
442     	if (validationErrors.isEmpty()) {
443     		return true;
444     	} else {
445     		attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
446     		return false;
447     	}
448     }
449 
450     /**
451      * Checks all the qualifiers for the given membership, so that all qualifiers which should be unique are guaranteed to be unique
452      *
453      * @param roleIndex the index of the role on the document (for error reporting purposes)
454      * @param membershipToCheckIndex the index of the person's membership in the role (for error reporting purposes)
455      * @return true if all unique values are indeed unique, false otherwise
456      */
457     protected boolean validateUniquePersonRoleQualifiersUniqueForMembership(PersonDocumentRole role, KimDocumentRoleMember membershipToCheck, int membershipToCheckIndex, Set<String> uniqueQualifierAttributes, int roleIndex, List<RemotableAttributeError> validationErrors) {
458     	boolean foundError = false;
459     	int count = 0;
460 
461     	for (KimDocumentRoleMember membership : role.getRolePrncpls()) {
462             if (sameMembershipQualifications(membershipToCheck, membership, uniqueQualifierAttributes)) {
463                 if (count == 0 ) {
464                     count +=1;
465                 } else {
466                     count += 1;
467                     foundError = true;
468 
469         		    int qualifierCount = 0;
470 
471 		    	    for (KimDocumentRoleQualifier qualifier : membership.getQualifiers()) {
472 			    	    if (qualifier != null && uniqueQualifierAttributes.contains(qualifier.getKimAttrDefnId())) {
473 						    validationErrors.add(RemotableAttributeError.Builder.create("document.roles["+roleIndex+"].rolePrncpls["+membershipToCheckIndex+"].qualifiers["+qualifierCount+"].attrVal", RiceKeyConstants.ERROR_DOCUMENT_IDENTITY_MANAGEMENT_PERSON_QUALIFIER_VALUE_NOT_UNIQUE+":"+qualifier.getKimAttribute().getAttributeName()+";"+qualifier.getAttrVal()).build());
474 				   	    }
475 				        qualifierCount += 1;
476 				    }
477                 }
478     		}
479     	}
480     	return foundError;
481     }
482 
483     /**
484      * Determines if two seperate memberships have the same qualifications
485      * @param membershipA the first membership to check
486      * @param membershipB the second membership to check
487      * @param uniqueQualifierAttributes the set of qualifier attributes which need to be unique
488      * @return true if equal, false if otherwise
489      */
490     protected boolean sameMembershipQualifications(KimDocumentRoleMember membershipA, KimDocumentRoleMember membershipB, Set<String> uniqueQualifierAttributes) {
491     	boolean equalSoFar = true;
492     	for (String uniqueQualifierAttributeDefinitionId : uniqueQualifierAttributes) {
493     		final KimDocumentRoleQualifier qualifierA = membershipA.getQualifier(uniqueQualifierAttributeDefinitionId);
494     		final KimDocumentRoleQualifier qualifierB = membershipB.getQualifier(uniqueQualifierAttributeDefinitionId);
495 
496     		if (qualifierA != null && qualifierB != null) {
497     			equalSoFar &= (qualifierA.getAttrVal() == null && qualifierB.getAttrVal() == null) || (qualifierA.getAttrVal() == null || qualifierA.getAttrVal().equals(qualifierB.getAttrVal()));
498     		}
499     	}
500     	return equalSoFar;
501     }
502 
503     /**
504      * Finds the set of unique qualification attributes for the given role
505      *
506      * @param role the role associated with this person
507      * @param attributeDefinitions the Map of attribute definitions where we can find out if a KimAttribute is supposed to be unique
508      * @return a Set of attribute definition ids for qualifications which are supposed to be unique
509      */
510     public Set<String> findUniqueQualificationAttributes(PersonDocumentRole role, List<KimAttributeField> attributeDefinitions) {
511     	Set<String> uniqueQualifications = new HashSet<String>();
512 
513     	if (role.getRolePrncpls() != null && role.getRolePrncpls().size() > 1) {
514     		final KimDocumentRoleMember membership = role.getRolePrncpls().get(0);
515     		for (KimDocumentRoleQualifier qualifier: membership.getQualifiers()) {
516     			if (qualifier != null && qualifier.getKimAttribute() != null && !StringUtils.isBlank(qualifier.getKimAttribute().getAttributeName())) {
517     	    		final KimAttributeField relatedDefinition = DataDictionaryTypeServiceHelper.findAttributeField(
518                             qualifier.getKimAttribute().getAttributeName(), attributeDefinitions);
519 
520     	    		if (relatedDefinition != null && relatedDefinition.isUnique()) {
521     	    			uniqueQualifications.add(qualifier.getKimAttrDefnId());
522     	    		}
523     			}
524     		}
525     	}
526 
527     	return uniqueQualifications;
528     }
529 
530     protected boolean validActiveDatesForRole (List<PersonDocumentRole> roles ) {
531     	boolean valid = true;
532 		int i = 0;
533     	for(PersonDocumentRole role : roles ) {
534 			int j = 0;
535     		for (KimDocumentRoleMember principal : role.getRolePrncpls()) {
536     			valid &= validateActiveDate("roles["+i+"].rolePrncpls["+j+"].activeToDate",principal.getActiveFromDate(), principal.getActiveToDate());
537     			j++;
538     		}
539     		i++;
540     	}
541     	return valid;
542     }
543 
544     protected boolean validActiveDatesForGroup (List<PersonDocumentGroup> groups ) {
545     	boolean valid = true;
546 		int i = 0;
547     	for(PersonDocumentGroup group : groups ) {
548      		valid &= validateActiveDate("groups["+i+"].activeToDate",group.getActiveFromDate(), group.getActiveToDate());
549     		i++;
550     	}
551     	return valid;
552     }
553 
554     protected boolean validActiveDatesForDelegations(List<RoleDocumentDelegationMember> delegationMembers) {
555     	boolean valid = true;
556 		int i = 0;
557 		for(RoleDocumentDelegationMember delegationMember: delegationMembers){
558      		valid &= validateActiveDate("delegationMembers["+i+"].activeToDate", delegationMember.getActiveFromDate(), delegationMember.getActiveToDate());
559     		i++;
560 		}
561     	return valid;
562     }
563 
564 	protected boolean validateActiveDate(String errorPath, Timestamp activeFromDate, Timestamp activeToDate) {
565 		// TODO : do not have detail bus rule yet, so just check this for now.
566 		boolean valid = true;
567 		if (activeFromDate != null && activeToDate !=null && activeToDate.before(activeFromDate)) {
568 	        GlobalVariables.getMessageMap().putError(errorPath, RiceKeyConstants.ERROR_ACTIVE_TO_DATE_BEFORE_FROM_DATE);
569             valid = false;
570 		}
571 		return valid;
572 	}
573 
574     public boolean processAddGroup(AddGroupEvent addGroupEvent) {
575         return getAddGroupRule().processAddGroup(addGroupEvent);
576     }
577 
578     public boolean processAddRole(AddRoleEvent addRoleEvent) {
579         return getAddRoleRule().processAddRole(addRoleEvent);
580     }
581 
582     public boolean processAddPersonDelegationMember(AddPersonDelegationMemberEvent addPersonDelegationMemberEvent){
583     	return getAddPersonDelegationMemberRule().processAddPersonDelegationMember(addPersonDelegationMemberEvent);
584     }
585 
586     protected IdentityService getIdentityService() {
587 		if ( identityService == null ) {
588 			identityService = KimApiServiceLocator.getIdentityService();
589 		}
590 		return identityService;
591 	}
592 
593 	protected RoleService getRoleService() {
594 		if ( roleService == null ) {
595 			roleService = KimApiServiceLocator.getRoleService();
596 		}
597 		return roleService;
598 	}
599 
600 
601 	protected IdentityManagementKimDocumentAuthorizer getAuthorizer(IdentityManagementPersonDocument document) {
602 		if ( authorizer == null ) {
603 			authorizer = (IdentityManagementKimDocumentAuthorizer) KRADServiceLocatorWeb.getDocumentDictionaryService().getDocumentAuthorizer(document);
604 		}
605 		return authorizer;
606 	}
607 
608 
609 
610 	/**
611 	 * @return the addGroupRuleClass
612 	 */
613 	public Class<? extends AddGroupRule> getAddGroupRuleClass() {
614 		return this.addGroupRuleClass;
615 	}
616 
617 
618 
619 	/**
620 	 * Can be overridden by subclasses to indicate the rule class to use when adding groups.
621 	 *
622 	 * @param addGroupRuleClass the addGroupRuleClass to set
623 	 */
624 	public void setAddGroupRuleClass(Class<? extends AddGroupRule> addGroupRuleClass) {
625 		this.addGroupRuleClass = addGroupRuleClass;
626 	}
627 
628 
629 
630 	/**
631 	 * @return the addRoleRuleClass
632 	 */
633 	public Class<? extends AddRoleRule> getAddRoleRuleClass() {
634 		return this.addRoleRuleClass;
635 	}
636 
637 
638 
639 	/**
640 	 * Can be overridden by subclasses to indicate the rule class to use when adding roles.
641 	 *
642 	 * @param addRoleRuleClass the addRoleRuleClass to set
643 	 */
644 	public void setAddRoleRuleClass(Class<? extends AddRoleRule> addRoleRuleClass) {
645 		this.addRoleRuleClass = addRoleRuleClass;
646 	}
647 
648 
649 
650 	/**
651 	 * @return the addGroupRule
652 	 */
653 	public AddGroupRule getAddGroupRule() {
654 		if ( addGroupRule == null ) {
655 			try {
656 				addGroupRule = addGroupRuleClass.newInstance();
657 			} catch ( Exception ex ) {
658 				throw new RuntimeException( "Unable to create AddGroupRule instance using class: " + addGroupRuleClass, ex );
659 			}
660 		}
661 		return addGroupRule;
662 	}
663 
664 
665 
666 	/**
667 	 * @return the addRoleRule
668 	 */
669 	public AddRoleRule getAddRoleRule() {
670 		if ( addRoleRule == null ) {
671 			try {
672 				addRoleRule = addRoleRuleClass.newInstance();
673 			} catch ( Exception ex ) {
674 				throw new RuntimeException( "Unable to create AddRoleRule instance using class: " + addRoleRuleClass, ex );
675 			}
676 		}
677 		return addRoleRule;
678 	}
679 
680 	/**
681 	 * @return the addRoleRule
682 	 */
683 	public AddPersonDelegationMemberRule getAddPersonDelegationMemberRule() {
684 		if(addPersonDelegationMemberRule == null){
685 			try {
686 				addPersonDelegationMemberRule = addPersonDelegationMemberRuleClass.newInstance();
687 			} catch ( Exception ex ) {
688 				throw new RuntimeException( "Unable to create AddPersonDelegationMemberRuleClass instance using class: " + addPersonDelegationMemberRuleClass, ex );
689 			}
690 		}
691 		return addPersonDelegationMemberRule;
692 	}
693 
694 	public boolean processAddPersonDocumentRoleQualifier(IdentityManagementPersonDocument document, PersonDocumentRole role, KimDocumentRoleMember kimDocumentRoleMember, int selectedRoleIdx) {
695 		boolean dateValidationSuccess = validateActiveDate("document.roles[" + selectedRoleIdx + "].newRolePrncpl.activeFromDate", kimDocumentRoleMember.getActiveFromDate(), kimDocumentRoleMember.getActiveToDate());
696 		String errorPath = "roles[" + selectedRoleIdx + "].newRolePrncpl";
697 		List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
698         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
699         KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(KimTypeBo.to(role.getKimRoleType()));
700 
701 		List<RemotableAttributeError> errorsAttributesAgainstExisting;
702 	    boolean rulePassed = true;
703 	    Map<String, String> newMemberQualifiers = attributeValidationHelper.convertQualifiersToMap(kimDocumentRoleMember.getQualifiers());
704 	    Map<String, String> oldMemberQualifiers;
705 	    List<String> roleIds = new ArrayList<String>();
706 	    roleIds.add(role.getRoleId());
707 	    for(KimDocumentRoleMember member: role.getRolePrncpls()){
708 	    	oldMemberQualifiers = member.getQualifierAsMap();
709 	    	errorsAttributesAgainstExisting = kimTypeService.validateUniqueAttributes(
710 	    			role.getKimRoleType().getId(), newMemberQualifiers, oldMemberQualifiers);
711 	    	validationErrors.addAll(
712 					attributeValidationHelper.convertErrors(errorPath, attributeValidationHelper
713                             .convertQualifiersToAttrIdxMap(kimDocumentRoleMember.getQualifiers()),
714                             errorsAttributesAgainstExisting));
715 	    }
716 
717         if ( kimTypeService != null ) {
718         	List<RemotableAttributeError> localErrors = kimTypeService.validateAttributes( role.getKimRoleType().getId(), newMemberQualifiers );
719     	    validationErrors.addAll( attributeValidationHelper.convertErrors(errorPath,
720                     attributeValidationHelper.convertQualifiersToAttrIdxMap(kimDocumentRoleMember.getQualifiers()),
721                     localErrors));
722         }
723 
724         GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
725     	if (validationErrors.isEmpty()) {
726     		rulePassed = dateValidationSuccess;
727     	} else {
728     		attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
729     		rulePassed = false;
730     	}
731     	return rulePassed;
732 	}
733 
734     protected boolean validateDelegationMemberRoleQualifier(List<RoleDocumentDelegationMember> delegationMembers){
735 		List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
736 		boolean valid = false;
737 		int memberCounter = 0;
738         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
739 		for(RoleDocumentDelegationMember delegationMember: delegationMembers) {
740 		    KimType kimType = KimTypeBo.to(delegationMember.getRoleBo().getKimRoleType());
741 		    KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
742 			String errorPath = "delegationMembers["+memberCounter+"]";
743 	        Map<String, String> mapToValidate = attributeValidationHelper.convertQualifiersToMap(delegationMember.getQualifiers());
744 	        List<RemotableAttributeError> errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
745 			validationErrors.addAll(
746 					attributeValidationHelper.convertErrors(errorPath,
747                             attributeValidationHelper.convertQualifiersToAttrIdxMap(delegationMember.getQualifiers()),
748                             errorsTemp));
749 
750 			List<RoleMember> roleMembers = getRoleService().findRoleMembers(QueryByCriteria.Builder.fromPredicates(equal(KimConstants.PrimaryKeyConstants.ID, delegationMember.getRoleMemberId()))).getResults();
751 			if(roleMembers.isEmpty()){
752 				valid = false;
753 				GlobalVariables.getMessageMap().putError("document."+errorPath, RiceKeyConstants.ERROR_DELEGATE_ROLE_MEMBER_ASSOCIATION, new String[]{});
754 			} else{
755 				kimTypeService.validateUnmodifiableAttributes(kimType.getId(), roleMembers.get(0).getAttributes(), mapToValidate);
756 				validationErrors.addAll(
757 						attributeValidationHelper.convertErrors(errorPath, attributeValidationHelper
758                                 .convertQualifiersToAttrIdxMap(delegationMember.getQualifiers()), errorsTemp));
759 			}
760 	        memberCounter++;
761     	}
762 		GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
763     	if (validationErrors.isEmpty()) {
764     		valid = true;
765     	} else {
766     		attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
767     		valid = false;
768     	}
769     	return valid;
770     }
771 
772     public boolean canModifyEntity( String currentUserPrincipalId, String toModifyPrincipalId ){
773         return (StringUtils.isNotBlank(currentUserPrincipalId) && StringUtils.isNotBlank(toModifyPrincipalId) &&
774                 currentUserPrincipalId.equals(toModifyPrincipalId)) ||
775                 getPermissionService().isAuthorized(
776                         currentUserPrincipalId,
777                         KimConstants.NAMESPACE_CODE,
778                         KimConstants.PermissionNames.MODIFY_ENTITY,
779                         Collections.singletonMap(KimConstants.AttributeConstants.PRINCIPAL_ID, currentUserPrincipalId));
780     }
781 
782     public boolean canOverrideEntityPrivacyPreferences( String currentUserPrincipalId, String toModifyPrincipalId ){
783         return (StringUtils.isNotBlank(currentUserPrincipalId) && StringUtils.isNotBlank(toModifyPrincipalId) &&
784                 currentUserPrincipalId.equals(toModifyPrincipalId)) ||
785                 getPermissionService().isAuthorized(
786                         currentUserPrincipalId,
787                         KimConstants.NAMESPACE_CODE,
788                         KimConstants.PermissionNames.OVERRIDE_ENTITY_PRIVACY_PREFERENCES,
789                         Collections.singletonMap(KimConstants.AttributeConstants.PRINCIPAL_ID, currentUserPrincipalId) );
790     }
791 
792 }