001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kim.document.rule;
017
018import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
019
020import java.sql.Timestamp;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.HashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.commons.collections.CollectionUtils;
029import org.apache.commons.lang.StringUtils;
030import org.kuali.rice.core.api.criteria.QueryByCriteria;
031import org.kuali.rice.core.api.uif.RemotableAttributeError;
032import org.kuali.rice.core.api.util.RiceKeyConstants;
033import org.kuali.rice.kim.api.KimConstants;
034import org.kuali.rice.kim.api.identity.IdentityService;
035import org.kuali.rice.kim.api.identity.entity.EntityDefault;
036import org.kuali.rice.kim.api.identity.principal.Principal;
037import org.kuali.rice.kim.api.role.RoleMember;
038import org.kuali.rice.kim.api.role.RoleService;
039import org.kuali.rice.kim.api.services.KimApiServiceLocator;
040import org.kuali.rice.kim.api.type.KimAttributeField;
041import org.kuali.rice.kim.api.type.KimType;
042import org.kuali.rice.kim.bo.ui.KimDocumentRoleMember;
043import org.kuali.rice.kim.bo.ui.KimDocumentRoleQualifier;
044import org.kuali.rice.kim.bo.ui.PersonDocumentAffiliation;
045import org.kuali.rice.kim.bo.ui.PersonDocumentBoDefaultBase;
046import org.kuali.rice.kim.bo.ui.PersonDocumentEmploymentInfo;
047import org.kuali.rice.kim.bo.ui.PersonDocumentGroup;
048import org.kuali.rice.kim.bo.ui.PersonDocumentName;
049import org.kuali.rice.kim.bo.ui.PersonDocumentRole;
050import org.kuali.rice.kim.bo.ui.RoleDocumentDelegationMember;
051import org.kuali.rice.kim.document.IdentityManagementPersonDocument;
052import org.kuali.rice.kim.document.authorization.IdentityManagementKimDocumentAuthorizer;
053import org.kuali.rice.kim.framework.services.KimFrameworkServiceLocator;
054import org.kuali.rice.kim.framework.type.KimTypeService;
055import org.kuali.rice.kim.impl.KIMPropertyConstants;
056import org.kuali.rice.kim.impl.identity.affiliation.EntityAffiliationTypeBo;
057import org.kuali.rice.kim.impl.identity.principal.PrincipalBo;
058import org.kuali.rice.kim.impl.type.KimTypeBo;
059import org.kuali.rice.kim.rule.event.ui.AddGroupEvent;
060import org.kuali.rice.kim.rule.event.ui.AddPersonDelegationMemberEvent;
061import org.kuali.rice.kim.rule.event.ui.AddRoleEvent;
062import org.kuali.rice.kim.rule.ui.AddGroupRule;
063import org.kuali.rice.kim.rule.ui.AddPersonDelegationMemberRule;
064import org.kuali.rice.kim.rule.ui.AddPersonDocumentRoleQualifierRule;
065import org.kuali.rice.kim.rule.ui.AddRoleRule;
066import org.kuali.rice.kim.rules.ui.PersonDocumentDelegationMemberRule;
067import org.kuali.rice.kim.rules.ui.PersonDocumentGroupRule;
068import org.kuali.rice.kim.rules.ui.PersonDocumentRoleRule;
069import org.kuali.rice.kns.kim.type.DataDictionaryTypeServiceHelper;
070import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
071import org.kuali.rice.krad.data.KradDataServiceLocator;
072import org.kuali.rice.krad.document.Document;
073import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
074import org.kuali.rice.krad.util.GlobalVariables;
075import org.kuali.rice.krad.util.KRADConstants;
076
077/**
078 * This is a description of what this class does - shyu don't forget to fill this in.
079 *
080 * @author Kuali Rice Team (rice.collab@kuali.org)
081 *
082 */
083public class IdentityManagementPersonDocumentRule extends TransactionalDocumentRuleBase implements AddGroupRule, AddRoleRule, AddPersonDocumentRoleQualifierRule, AddPersonDelegationMemberRule {
084
085        protected AddGroupRule addGroupRule;
086        protected AddRoleRule  addRoleRule;
087        protected AddPersonDelegationMemberRule addPersonDelegationMemberRule;
088        protected IdentityManagementKimDocumentAuthorizer authorizer;
089        private IdentityService identityService;
090        private RoleService roleService;
091        protected Class<? extends AddGroupRule> addGroupRuleClass = PersonDocumentGroupRule.class;
092        protected Class<? extends AddRoleRule> addRoleRuleClass = PersonDocumentRoleRule.class;
093        protected Class<? extends AddPersonDelegationMemberRule> addPersonDelegationMemberRuleClass = PersonDocumentDelegationMemberRule.class;
094    protected ActiveRoleMemberHelper activeRoleMemberHelper = new ActiveRoleMemberHelper();
095        protected AttributeValidationHelper attributeValidationHelper = new AttributeValidationHelper();
096
097    @Override
098    protected boolean processCustomSaveDocumentBusinessRules(Document document) {
099        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                        EntityAffiliationTypeBo newAffiliationType = KradDataServiceLocator.getDataObjectService().find(EntityAffiliationTypeBo.class, affiliation.getAffiliationTypeCode());
287                        if (!newAffiliationType.isEmploymentAffiliationType()
288                                && affiliation.getAffiliationType().isEmploymentAffiliationType()
289                                && !affiliation.getEmpInfos().isEmpty()) {
290                                GlobalVariables.getMessageMap().putError("affiliations[" + i + "].affiliationTypeCode",RiceKeyConstants.ERROR_NOT_EMPLOYMENT_AFFILIATION_TYPE,new String[] {affiliation.getAffiliationType().getName(), newAffiliationType.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    @Override
575    public boolean processAddGroup(AddGroupEvent addGroupEvent) {
576        return getAddGroupRule().processAddGroup(addGroupEvent);
577    }
578
579    @Override
580    public boolean processAddRole(AddRoleEvent addRoleEvent) {
581        return getAddRoleRule().processAddRole(addRoleEvent);
582    }
583
584    @Override
585    public boolean processAddPersonDelegationMember(AddPersonDelegationMemberEvent addPersonDelegationMemberEvent){
586        return getAddPersonDelegationMemberRule().processAddPersonDelegationMember(addPersonDelegationMemberEvent);
587    }
588
589    protected IdentityService getIdentityService() {
590                if ( identityService == null ) {
591                        identityService = KimApiServiceLocator.getIdentityService();
592                }
593                return identityService;
594        }
595
596        protected RoleService getRoleService() {
597                if ( roleService == null ) {
598                        roleService = KimApiServiceLocator.getRoleService();
599                }
600                return roleService;
601        }
602
603
604        protected IdentityManagementKimDocumentAuthorizer getAuthorizer(IdentityManagementPersonDocument document) {
605                if ( authorizer == null ) {
606                        authorizer = (IdentityManagementKimDocumentAuthorizer) KRADServiceLocatorWeb.getDocumentDictionaryService().getDocumentAuthorizer(document);
607                }
608                return authorizer;
609        }
610
611
612
613        /**
614         * @return the addGroupRuleClass
615         */
616        public Class<? extends AddGroupRule> getAddGroupRuleClass() {
617                return this.addGroupRuleClass;
618        }
619
620
621
622        /**
623         * Can be overridden by subclasses to indicate the rule class to use when adding groups.
624         *
625         * @param addGroupRuleClass the addGroupRuleClass to set
626         */
627        public void setAddGroupRuleClass(Class<? extends AddGroupRule> addGroupRuleClass) {
628                this.addGroupRuleClass = addGroupRuleClass;
629        }
630
631
632
633        /**
634         * @return the addRoleRuleClass
635         */
636        public Class<? extends AddRoleRule> getAddRoleRuleClass() {
637                return this.addRoleRuleClass;
638        }
639
640
641
642        /**
643         * Can be overridden by subclasses to indicate the rule class to use when adding roles.
644         *
645         * @param addRoleRuleClass the addRoleRuleClass to set
646         */
647        public void setAddRoleRuleClass(Class<? extends AddRoleRule> addRoleRuleClass) {
648                this.addRoleRuleClass = addRoleRuleClass;
649        }
650
651
652
653        /**
654         * @return the addGroupRule
655         */
656        public AddGroupRule getAddGroupRule() {
657                if ( addGroupRule == null ) {
658                        try {
659                                addGroupRule = addGroupRuleClass.newInstance();
660                        } catch ( Exception ex ) {
661                                throw new RuntimeException( "Unable to create AddGroupRule instance using class: " + addGroupRuleClass, ex );
662                        }
663                }
664                return addGroupRule;
665        }
666
667
668
669        /**
670         * @return the addRoleRule
671         */
672        public AddRoleRule getAddRoleRule() {
673                if ( addRoleRule == null ) {
674                        try {
675                                addRoleRule = addRoleRuleClass.newInstance();
676                        } catch ( Exception ex ) {
677                                throw new RuntimeException( "Unable to create AddRoleRule instance using class: " + addRoleRuleClass, ex );
678                        }
679                }
680                return addRoleRule;
681        }
682
683        /**
684         * @return the addRoleRule
685         */
686        public AddPersonDelegationMemberRule getAddPersonDelegationMemberRule() {
687                if(addPersonDelegationMemberRule == null){
688                        try {
689                                addPersonDelegationMemberRule = addPersonDelegationMemberRuleClass.newInstance();
690                        } catch ( Exception ex ) {
691                                throw new RuntimeException( "Unable to create AddPersonDelegationMemberRuleClass instance using class: " + addPersonDelegationMemberRuleClass, ex );
692                        }
693                }
694                return addPersonDelegationMemberRule;
695        }
696
697        @Override
698    public boolean processAddPersonDocumentRoleQualifier(IdentityManagementPersonDocument document, PersonDocumentRole role, KimDocumentRoleMember kimDocumentRoleMember, int selectedRoleIdx) {
699                boolean dateValidationSuccess = validateActiveDate("document.roles[" + selectedRoleIdx + "].newRolePrncpl.activeFromDate", kimDocumentRoleMember.getActiveFromDate(), kimDocumentRoleMember.getActiveToDate());
700                String errorPath = "roles[" + selectedRoleIdx + "].newRolePrncpl";
701                List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
702        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
703        KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(KimTypeBo.to(role.getKimRoleType()));
704
705                List<RemotableAttributeError> errorsAttributesAgainstExisting;
706            boolean rulePassed = true;
707            Map<String, String> newMemberQualifiers = attributeValidationHelper.convertQualifiersToMap(kimDocumentRoleMember.getQualifiers());
708            Map<String, String> oldMemberQualifiers;
709            List<String> roleIds = new ArrayList<String>();
710            roleIds.add(role.getRoleId());
711            for(KimDocumentRoleMember member: role.getRolePrncpls()){
712                oldMemberQualifiers = member.getQualifierAsMap();
713                errorsAttributesAgainstExisting = kimTypeService.validateUniqueAttributes(
714                                role.getKimRoleType().getId(), newMemberQualifiers, oldMemberQualifiers);
715                validationErrors.addAll(
716                                        attributeValidationHelper.convertErrors(errorPath, attributeValidationHelper
717                            .convertQualifiersToAttrIdxMap(kimDocumentRoleMember.getQualifiers()),
718                            errorsAttributesAgainstExisting));
719            }
720
721        if ( kimTypeService != null ) {
722                List<RemotableAttributeError> localErrors = kimTypeService.validateAttributes( role.getKimRoleType().getId(), newMemberQualifiers );
723            validationErrors.addAll( attributeValidationHelper.convertErrors(errorPath,
724                    attributeValidationHelper.convertQualifiersToAttrIdxMap(kimDocumentRoleMember.getQualifiers()),
725                    localErrors));
726        }
727
728        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
729        if (validationErrors.isEmpty()) {
730                rulePassed = dateValidationSuccess;
731        } else {
732                attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
733                rulePassed = false;
734        }
735        return rulePassed;
736        }
737
738    protected boolean validateDelegationMemberRoleQualifier(List<RoleDocumentDelegationMember> delegationMembers){
739                List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
740                boolean valid = false;
741                int memberCounter = 0;
742        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
743                for(RoleDocumentDelegationMember delegationMember: delegationMembers) {
744                    KimType kimType = KimTypeBo.to(delegationMember.getRoleBo().getKimRoleType());
745                    KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
746                        String errorPath = "delegationMembers["+memberCounter+"]";
747                Map<String, String> mapToValidate = attributeValidationHelper.convertQualifiersToMap(delegationMember.getQualifiers());
748                List<RemotableAttributeError> errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
749                        validationErrors.addAll(
750                                        attributeValidationHelper.convertErrors(errorPath,
751                            attributeValidationHelper.convertQualifiersToAttrIdxMap(delegationMember.getQualifiers()),
752                            errorsTemp));
753
754                        List<RoleMember> roleMembers = getRoleService().findRoleMembers(QueryByCriteria.Builder.fromPredicates(equal(KimConstants.PrimaryKeyConstants.ID, delegationMember.getRoleMemberId()))).getResults();
755                        if(roleMembers.isEmpty()){
756                                valid = false;
757                                GlobalVariables.getMessageMap().putError("document."+errorPath, RiceKeyConstants.ERROR_DELEGATE_ROLE_MEMBER_ASSOCIATION, new String[]{});
758                        } else{
759                                kimTypeService.validateUnmodifiableAttributes(kimType.getId(), roleMembers.get(0).getAttributes(), mapToValidate);
760                                validationErrors.addAll(
761                                                attributeValidationHelper.convertErrors(errorPath, attributeValidationHelper
762                                .convertQualifiersToAttrIdxMap(delegationMember.getQualifiers()), errorsTemp));
763                        }
764                memberCounter++;
765        }
766                GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
767        if (validationErrors.isEmpty()) {
768                valid = true;
769        } else {
770                attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
771                valid = false;
772        }
773        return valid;
774    }
775
776    public boolean canModifyEntity( String currentUserPrincipalId, String toModifyPrincipalId ){
777        return (StringUtils.isNotBlank(currentUserPrincipalId) && StringUtils.isNotBlank(toModifyPrincipalId) &&
778                currentUserPrincipalId.equals(toModifyPrincipalId)) ||
779                getPermissionService().isAuthorized(
780                        currentUserPrincipalId,
781                        KimConstants.NAMESPACE_CODE,
782                        KimConstants.PermissionNames.MODIFY_ENTITY,
783                        Collections.singletonMap(KimConstants.AttributeConstants.PRINCIPAL_ID, currentUserPrincipalId));
784    }
785
786    public boolean canOverrideEntityPrivacyPreferences( String currentUserPrincipalId, String toModifyPrincipalId ){
787        return (StringUtils.isNotBlank(currentUserPrincipalId) && StringUtils.isNotBlank(toModifyPrincipalId) &&
788                currentUserPrincipalId.equals(toModifyPrincipalId)) ||
789                getPermissionService().isAuthorized(
790                        currentUserPrincipalId,
791                        KimConstants.NAMESPACE_CODE,
792                        KimConstants.PermissionNames.OVERRIDE_ENTITY_PRIVACY_PREFERENCES,
793                        Collections.singletonMap(KimConstants.AttributeConstants.PRINCIPAL_ID, currentUserPrincipalId) );
794    }
795
796}