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.impl.role;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import javax.persistence.NonUniqueResultException;
28  import javax.xml.namespace.QName;
29  
30  import org.apache.commons.collections.CollectionUtils;
31  import org.apache.commons.lang.StringUtils;
32  import org.apache.log4j.Logger;
33  import org.joda.time.DateTime;
34  import org.kuali.rice.core.api.CoreApiServiceLocator;
35  import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator;
36  import org.kuali.rice.core.api.criteria.Predicate;
37  import org.kuali.rice.core.api.criteria.PredicateFactory;
38  import org.kuali.rice.core.api.criteria.QueryByCriteria;
39  import org.kuali.rice.core.api.criteria.QueryResults;
40  import org.kuali.rice.core.api.datetime.DateTimeService;
41  import org.kuali.rice.core.api.delegation.DelegationType;
42  import org.kuali.rice.core.api.membership.MemberType;
43  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
44  import org.kuali.rice.kim.api.KimConstants;
45  import org.kuali.rice.kim.api.group.Group;
46  import org.kuali.rice.kim.api.group.GroupService;
47  import org.kuali.rice.kim.api.identity.IdentityService;
48  import org.kuali.rice.kim.api.identity.principal.Principal;
49  import org.kuali.rice.kim.api.role.Role;
50  import org.kuali.rice.kim.api.role.RoleMember;
51  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
52  import org.kuali.rice.kim.api.type.KimType;
53  import org.kuali.rice.kim.api.type.KimTypeAttribute;
54  import org.kuali.rice.kim.api.type.KimTypeInfoService;
55  import org.kuali.rice.coreservice.api.namespace.Namespace;
56  import org.kuali.rice.coreservice.api.namespace.NamespaceService;
57  import org.kuali.rice.kim.framework.role.RoleEbo;
58  import org.kuali.rice.kim.framework.role.RoleTypeService;
59  import org.kuali.rice.kim.framework.type.KimTypeService;
60  import org.kuali.rice.kim.impl.KIMPropertyConstants;
61  import org.kuali.rice.kim.impl.common.delegate.DelegateMemberBo;
62  import org.kuali.rice.kim.impl.common.attribute.KimAttributeBo;
63  import org.kuali.rice.kim.impl.common.delegate.DelegateTypeBo;
64  import org.kuali.rice.kim.impl.responsibility.ResponsibilityInternalService;
65  import org.kuali.rice.kim.impl.services.KimImplServiceLocator;
66  import org.kuali.rice.kim.impl.type.KimTypeBo;
67  import org.kuali.rice.krad.data.DataObjectService;
68  import org.kuali.rice.krad.data.KradDataServiceLocator;
69  import org.kuali.rice.krad.util.KRADPropertyConstants;
70  
71  abstract class RoleServiceBase {
72      private static final Logger LOG = Logger.getLogger( RoleServiceBase.class );
73  
74      protected DataObjectService dataObjectService;
75      protected IdentityService identityService;
76      protected NamespaceService namespaceService;
77      protected KimTypeInfoService kimTypeInfoService;
78      protected GroupService groupService;
79      protected ResponsibilityInternalService responsibilityInternalService;
80      protected RoleDao roleDao;
81      protected DateTimeService dateTimeService;
82  
83      /**
84       * A helper enumeration for indicating which KimRoleDao method to use when attempting to get role/delegation-related lists that are not in the cache.
85       *
86       * @author Kuali Rice Team (rice.collab@kuali.org)
87       */
88      protected static enum RoleDaoAction {
89          ROLE_PRINCIPALS_FOR_PRINCIPAL_ID_AND_ROLE_IDS,
90          ROLE_GROUPS_FOR_GROUP_IDS_AND_ROLE_IDS,
91          ROLE_MEMBERS_FOR_ROLE_IDS,
92          ROLE_MEMBERSHIPS_FOR_ROLE_IDS_AS_MEMBERS,
93          ROLE_MEMBERS_FOR_ROLE_IDS_WITH_FILTERS
94      }
95  
96      /**
97       * Converts the Qualifier Name/Value Role qualification set into Qualifier AttributeID/Value set
98       *
99       * @param qualification The original role qualification attribute set
100      * @param validAttributeIds The mapping of attribute names to their matching attribute ids
101      * @return Converted Map<String, String> containing ID/value pairs
102      */
103     protected Map<String, String> convertQualifierKeys(Map<String, String> qualification, Map<String, String> validAttributeIds) {
104         Map<String, String> convertedQualification = new HashMap<String, String>();
105         if (qualification != null && CollectionUtils.isNotEmpty(qualification.entrySet())) {
106             for (Map.Entry<String, String> entry : qualification.entrySet()) {
107                 String attributeId = validAttributeIds.get(entry.getKey());
108                 if (StringUtils.isNotEmpty(attributeId)) {
109                     convertedQualification.put(attributeId, entry.getValue());
110                 }
111             }
112         }
113         return convertedQualification;
114     }
115 
116     protected void getNestedRoleTypeMemberIds(String roleId, Set<String> members) {
117         ArrayList<String> roleList = new ArrayList<String>(1);
118         roleList.add(roleId);
119         List<RoleMemberBo> firstLevelMembers = getStoredRoleMembersForRoleIds(roleList, MemberType.ROLE.getCode(), Collections.<String, String>emptyMap());
120         for (RoleMemberBo member : firstLevelMembers) {
121             if (MemberType.ROLE.equals(member.getType())) {
122                 if (!members.contains(member.getMemberId())) {
123                     members.add(member.getMemberId());
124                     getNestedRoleTypeMemberIds(member.getMemberId(), members);
125                 }
126             }
127         }
128     }
129 
130     protected List<RoleMemberBo> getRoleMembersForPrincipalId(Collection<String> roleIds, String principalId) {
131         return getRoleMembersForPrincipalId(roleIds, principalId, new HashMap<String, String>(0) );
132     }
133 
134     protected List<RoleMemberBo> getRoleMembersForPrincipalId(Collection<String> roleIds, String principalId, Map<String,String> qualification ) {
135         List<Predicate> criteria = new ArrayList<Predicate>();
136 
137         if (CollectionUtils.isNotEmpty(roleIds)) {
138             if (roleIds.size() == 1) {
139                 criteria.add( PredicateFactory.equal(KIMPropertyConstants.RoleMember.ROLE_ID, roleIds.iterator().next()) );
140             } else {
141                 criteria.add( PredicateFactory.in(KIMPropertyConstants.RoleMember.ROLE_ID, roleIds) );
142             }
143         }
144         if ( StringUtils.isNotBlank(principalId) ) {
145             criteria.add( PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_ID, principalId) );
146         }
147         criteria.add( PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_TYPE_CODE, MemberType.PRINCIPAL.getCode()));
148 
149         Predicate roleQualificationPredicate = getRoleQualificationPredicate(qualification);
150         if ( roleQualificationPredicate != null ) {
151             criteria.add( roleQualificationPredicate );
152         }
153 
154         return getRoleMembershipsForPredicates(criteria);
155     }
156 
157     protected List<RoleMemberBo> getRoleMembersForGroupIds(String roleId, List<String> groupIds) {
158         if (CollectionUtils.isEmpty(groupIds)) {
159             return new ArrayList<RoleMemberBo>();
160         }
161         List<RoleMemberBo> coll = getDataObjectService().findMatching( RoleMemberBo.class,
162                 QueryByCriteria.Builder.fromPredicates(
163                         PredicateFactory.equal(KIMPropertyConstants.RoleMember.ROLE_ID, roleId),
164                         PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_TYPE_CODE, MemberType.GROUP.getCode()),
165                         PredicateFactory.in(KIMPropertyConstants.RoleMember.MEMBER_ID, groupIds) ) ).getResults();
166         List<RoleMemberBo> results = new ArrayList<RoleMemberBo>(coll.size());
167         DateTime now = new DateTime( getDateTimeService().getCurrentTimestamp().getTime() );
168         for (RoleMemberBo rm : coll) {
169             if (rm.isActive(now)) {
170                 results.add(rm);
171             }
172         }
173         return results;
174     }
175 
176     /**
177      * Retrieves a list of RoleMemberBo instances from the KimRoleDao.
178      *
179      * @param daoActionToTake An indicator for which KimRoleDao method should be used to get the results if the desired RoleMemberBos are not cached.
180      * @param roleIds         The role IDs to filter by; may get used as the IDs for members that are also roles, depending on the daoActionToTake value.
181      * @param principalId     The principal ID to filter by; may get ignored depending on the daoActionToTake value.
182      * @param groupIds        The group IDs to filter by; may get ignored depending on the daoActionToTake value.
183      * @param memberTypeCode  The member type code to filter by; may get overridden depending on the daoActionToTake value.
184      * @param qualification   The original role qualification attribute set
185      * @return A list of RoleMemberBo instances based on the provided parameters.
186      * @throws IllegalArgumentException if daoActionToTake refers to an enumeration constant that is not role-member-related.
187      */
188     protected List<RoleMemberBo> getRoleMemberBoList(RoleDaoAction daoActionToTake, Collection<String> roleIds, String principalId,
189             Collection<String> groupIds, String memberTypeCode, Map<String, String> qualification) {
190         if (roleIds == null || roleIds.isEmpty()) {
191             roleIds = Collections.emptyList();
192         }
193         if (groupIds == null || groupIds.isEmpty()) {
194             groupIds = Collections.emptyList();
195         }
196 
197         Map<String, String> validAttributeIds = new HashMap<String, String>();
198 
199         HashSet <String> kimTypeIds = new HashSet<String>();
200 
201         //Getting unique kim types
202         for (String roleId : roleIds) {
203             RoleBoLite role = getRoleBoLite(roleId);
204             kimTypeIds.add(role.getKimTypeId());
205         }
206 
207         if (qualification != null && CollectionUtils.isNotEmpty(qualification.entrySet())) {
208             for (String kimTypeId : kimTypeIds) {
209                 for (Map.Entry<String, String> entry : qualification.entrySet()) {
210                     validAttributeIds.put(entry.getKey(), getKimAttributeId(kimTypeId, entry.getKey()));
211                 }
212             }
213         }
214 
215 
216         Map<String, String> convertedQualification = convertQualifierKeys(qualification, validAttributeIds);
217 
218         switch (daoActionToTake) {
219             case ROLE_PRINCIPALS_FOR_PRINCIPAL_ID_AND_ROLE_IDS: // Search for principal role members only.
220                 return getRoleMembersForPrincipalId(roleIds, principalId, convertedQualification);
221             case ROLE_GROUPS_FOR_GROUP_IDS_AND_ROLE_IDS: // Search for group role members only.
222                 return getRoleGroupsForGroupIdsAndRoleIds(roleIds, groupIds, convertedQualification);
223             case ROLE_MEMBERS_FOR_ROLE_IDS: // Search for role members with the given member type code.
224                 return roleDao.getRoleMembersForRoleIds(roleIds, memberTypeCode, convertedQualification);
225             case ROLE_MEMBERSHIPS_FOR_ROLE_IDS_AS_MEMBERS: // Search for role members who are also roles.
226                 return getRoleMembershipsForRoleIdsAsMembers(roleIds, convertedQualification);
227             case ROLE_MEMBERS_FOR_ROLE_IDS_WITH_FILTERS: // Search for role members that might be roles, principals, or groups.
228                 return getRoleMembersForRoleIdsWithFilters(roleIds, principalId, groupIds, convertedQualification);
229             default: // This should never happen, since the previous switch block should handle this case appropriately.
230                 throw new IllegalArgumentException("The 'daoActionToTake' parameter cannot refer to a non-role-member-related value!");
231         }
232     }
233 
234     public List<RoleMemberBo> getRoleGroupsForGroupIdsAndRoleIds(Collection<String> roleIds, Collection<String> groupIds, Map<String, String> qualification) {
235         List<Predicate> criteria = new ArrayList<Predicate>();
236 
237         if (CollectionUtils.isNotEmpty(roleIds)) {
238             criteria.add( PredicateFactory.in(KIMPropertyConstants.RoleMember.ROLE_ID, roleIds) );
239         }
240         if (CollectionUtils.isNotEmpty(groupIds)) {
241             criteria.add( PredicateFactory.in(KIMPropertyConstants.RoleMember.MEMBER_ID, groupIds) );
242         }
243         criteria.add( PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_TYPE_CODE, MemberType.GROUP.getCode()));
244 
245         Predicate roleQualificationPredicate = getRoleQualificationPredicate(qualification);
246         if ( roleQualificationPredicate != null ) {
247             criteria.add( roleQualificationPredicate );
248         }
249 
250         return getRoleMembershipsForPredicates(criteria);
251     }
252 
253     protected List<RoleMemberBo> getRoleMembershipsForRoleIdsAsMembers(Collection<String> roleIds,
254             Map<String, String> qualification) {
255         List<Predicate> criteria = new ArrayList<Predicate>();
256 
257         if (CollectionUtils.isNotEmpty(roleIds)) {
258             criteria.add( PredicateFactory.in(KIMPropertyConstants.RoleMember.MEMBER_ID, roleIds) );
259         }
260         criteria.add( PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_TYPE_CODE, MemberType.ROLE.getCode()));
261 
262         Predicate roleQualificationPredicate = getRoleQualificationPredicate(qualification);
263         if ( roleQualificationPredicate != null ) {
264             criteria.add( roleQualificationPredicate );
265         }
266 
267         return getRoleMembershipsForPredicates(criteria);
268     }
269 
270     protected List<RoleMemberBo> getRoleMembersForRoleIdsWithFilters(Collection<String> roleIds,
271             String principalId, Collection<String> groupIds, Map<String, String> qualification) {
272         List<Predicate> criteria = new ArrayList<Predicate>();
273 
274         if (CollectionUtils.isNotEmpty(roleIds)) {
275             criteria.add( PredicateFactory.in(KIMPropertyConstants.RoleMember.ROLE_ID, roleIds) );
276         }
277         List<Predicate> principalPredicates = new ArrayList<Predicate>(2);
278         principalPredicates.add(PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_TYPE_CODE, MemberType.PRINCIPAL.getCode()));
279         if ( StringUtils.isNotBlank(principalId) ) {
280             principalPredicates.add(PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_ID, principalId));
281         }
282         List<Predicate> groupPredicates = new ArrayList<Predicate>(2);
283         groupPredicates.add(PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_TYPE_CODE, MemberType.GROUP.getCode()));
284         if (CollectionUtils.isNotEmpty(groupIds)) {
285             groupPredicates.add(PredicateFactory.in(KIMPropertyConstants.RoleMember.MEMBER_ID, groupIds));
286         }
287 
288         criteria.add( PredicateFactory.or(
289                 PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_TYPE_CODE, MemberType.ROLE.getCode()),
290                 PredicateFactory.and(principalPredicates.toArray(new Predicate[0])),
291                 PredicateFactory.and(groupPredicates.toArray(new Predicate[0]))
292         ) );
293 
294         Predicate roleQualificationPredicate = getRoleQualificationPredicate(qualification);
295         if ( roleQualificationPredicate != null ) {
296             criteria.add( roleQualificationPredicate );
297         }
298 
299         return getRoleMembershipsForPredicates(criteria);
300     }
301 
302     protected List<RoleMemberBo> getRoleMembershipsForPredicates( Collection<Predicate> criteria ) {
303         Collection<RoleMemberBo> coll = getDataObjectService().findMatching(RoleMemberBo.class, QueryByCriteria.Builder.fromPredicates(criteria) ).getResults();
304         ArrayList<RoleMemberBo> results = new ArrayList<RoleMemberBo>(coll.size());
305         DateTime now = new DateTime( getDateTimeService().getCurrentTimestamp().getTime() );
306 
307         for (RoleMemberBo rm : coll) {
308             if (rm.isActive(now)) {
309                 results.add(rm);
310             }
311         }
312 
313         return results;
314     }
315 
316     /**
317      * Attempts to add predicates to the query to filter based on a subquery against the attribute
318      * data table.
319      *
320      * FIXME: This has not been re-implemented in JPA.  We need subquery support in the Predicate APIs.
321      * ALERT!: This can only be re-implemented if we use it against role qualifiers which the role
322      * type service say can be matched exactly.  Otherwise we could filter out matches where the
323      * qualifier contains wildcards or is part of a hierarchy.
324      *
325      *  This should not be too difficult.  See the first answer here:
326      *  http://stackoverflow.com/questions/4483576/jpa-2-0-criteria-api-subqueries-in-expressions
327      *
328      *  PredicateFactory.subquery( String parentAttributeName, Class subQueryDataObject, Predicate... predicates )
329      *  (or something like that - could also pass in a build QueryByCriteria object)
330      *
331      *  Other consideration used in the code below which the above does not address...
332      *      What about referencing the outer query?  What's the syntax for that.  OJB had a special constant.
333      *
334      * @param qualification
335      */
336     protected Predicate getRoleQualificationPredicate(Map<String, String> qualification) {
337         return null;
338         //        if (qualification != null && CollectionUtils.isNotEmpty(qualification.keySet())) {
339         //            for (Map.Entry<String, String> qualifier : qualification.entrySet()) {
340         //                if (StringUtils.isNotBlank(qualifier.getValue())) {
341         //                    String value = (qualifier.getValue()).replace('*', '%');
342         //                    PredicateFactory.and(
343         //                            PredicateFactory.like("attributeValue", value),
344         //                            PredicateFactory.equal("kimAttributeId", qualifier.getKey()),
345         //                            PredicateFactory.equal("attributeValue", value),
346         //
347         //                    subCrit.addLike("attributeValue", value);
348         //                    subCrit.addEqualTo("kimAttributeId", qualifier.getKey());
349         //                    subCrit.addEqualToField("assignedToId", Criteria.PARENT_QUERY_PREFIX + "id");
350         //                    ReportQueryByCriteria subQuery = QueryFactory.newReportQuery(RoleMemberAttributeDataBo.class, subCrit);
351         //                    c.addExists(subQuery);
352         //                }
353         //            }
354         //        }
355     }
356 
357     protected List<RoleMemberBo> getRoleMembershipsForMemberId(String memberType, String memberId, Map<String, String> qualification) {
358         if (StringUtils.isBlank(memberId) || StringUtils.isBlank(memberType)) {
359             return new ArrayList<RoleMemberBo>(0);
360         }
361 
362         List<Predicate> criteria = new ArrayList<Predicate>();
363 
364         criteria.add( PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_ID, memberId) );
365         criteria.add( PredicateFactory.equal(KIMPropertyConstants.RoleMember.MEMBER_TYPE_CODE, memberType) );
366 
367         Predicate roleQualificationPredicate = getRoleQualificationPredicate(qualification);
368         if ( roleQualificationPredicate != null ) {
369             criteria.add( roleQualificationPredicate );
370         }
371 
372         return getRoleMembershipsForPredicates(criteria);
373     }
374 
375     /**
376      * Calls the KimRoleDao's "getRolePrincipalsForPrincipalIdAndRoleIds" method and/or retrieves any corresponding members from the cache.
377      */
378     protected List<RoleMemberBo> getStoredRolePrincipalsForPrincipalIdAndRoleIds(Collection<String> roleIds, String principalId, Map<String, String> qualification) {
379         return getRoleMemberBoList(RoleDaoAction.ROLE_PRINCIPALS_FOR_PRINCIPAL_ID_AND_ROLE_IDS, roleIds, principalId, Collections.<String>emptyList(), null, qualification);
380     }
381 
382     /**
383      * Calls the KimRoleDao's "getRoleGroupsForGroupIdsAndRoleIds" method and/or retrieves any corresponding members from the cache.
384      */
385     protected List<RoleMemberBo> getStoredRoleGroupsForGroupIdsAndRoleIds(Collection<String> roleIds, Collection<String> groupIds, Map<String, String> qualification) {
386         return getRoleMemberBoList(RoleDaoAction.ROLE_GROUPS_FOR_GROUP_IDS_AND_ROLE_IDS, roleIds, null, groupIds, null, qualification);
387     }
388 
389     /**
390      * Calls the KimRoleDao's "getRoleMembersForRoleIds" method and/or retrieves any corresponding members from the cache.
391      */
392     protected List<RoleMemberBo> getStoredRoleMembersForRoleIds(Collection<String> roleIds, String memberTypeCode, Map<String, String> qualification) {
393         return getRoleMemberBoList(RoleDaoAction.ROLE_MEMBERS_FOR_ROLE_IDS, roleIds, null, Collections.<String>emptyList(), memberTypeCode, qualification);
394     }
395 
396     /**
397      * Calls the KimRoleDao's "getRoleMembershipsForRoleIdsAsMembers" method and/or retrieves any corresponding members from the cache.
398      */
399     protected List<RoleMemberBo> getStoredRoleMembershipsForRoleIdsAsMembers(Collection<String> roleIds, Map<String, String> qualification) {
400         return getRoleMemberBoList(RoleDaoAction.ROLE_MEMBERSHIPS_FOR_ROLE_IDS_AS_MEMBERS, roleIds, null, Collections.<String>emptyList(), null, qualification);
401     }
402 
403     /**
404      * Calls the KimRoleDao's "getRoleMembersForRoleIdsWithFilters" method and/or retrieves any corresponding members from the cache.
405      */
406     protected List<RoleMemberBo> getStoredRoleMembersForRoleIdsWithFilters(Collection<String> roleIds, String principalId, List<String> groupIds, Map<String, String> qualification) {
407         return getRoleMemberBoList(RoleDaoAction.ROLE_MEMBERS_FOR_ROLE_IDS_WITH_FILTERS, roleIds, principalId, groupIds, null, qualification);
408     }
409 
410     /**
411      * Retrieves a RoleMemberBo object by its ID. If the role member already exists in the cache, this method will return the cached
412      * version; otherwise, it will retrieve the uncached version from the database and then cache it (if it belongs to a role that allows
413      * its members to be cached) before returning it.
414      */
415     protected RoleMemberBo getRoleMemberBo(String roleMemberId) {
416         if (StringUtils.isBlank(roleMemberId)) {
417             return null;
418         }
419 
420         return getDataObjectService().find(RoleMemberBo.class, roleMemberId);
421     }
422 
423     /**
424      * Retrieves a RoleResponsibilityActionBo object by its ID.
425      */
426     protected RoleResponsibilityActionBo getRoleResponsibilityActionBo(String roleResponsibilityActionId) {
427         if (StringUtils.isBlank(roleResponsibilityActionId)) {
428             return null;
429         }
430 
431         return getDataObjectService().find(RoleResponsibilityActionBo.class, roleResponsibilityActionId);
432     }
433 
434     /**
435      *
436      */
437     protected Map<String, DelegateTypeBo> getStoredDelegationImplMapFromRoleIds(Collection<String> roleIds) {
438         if (roleIds != null && !roleIds.isEmpty()) {
439             Map<String, DelegateTypeBo> results = new HashMap<String, DelegateTypeBo>();
440             Collection<DelegateTypeBo> coll = getDataObjectService().findMatching(DelegateTypeBo.class,
441                     QueryByCriteria.Builder.fromPredicates(
442                             PredicateFactory.in(KIMPropertyConstants.Delegation.ROLE_ID, roleIds),
443                             PredicateFactory.equal(KIMPropertyConstants.Delegation.ACTIVE, Boolean.TRUE) ) ).getResults();
444             for (DelegateTypeBo delegateBo : coll) {
445                 results.put(delegateBo.getDelegationId(), delegateBo);
446             }
447             return results;
448         }
449 
450         return Collections.emptyMap();
451     }
452 
453     /**
454      *
455      */
456     protected List<DelegateTypeBo> getStoredDelegationImplsForRoleIds(Collection<String> roleIds) {
457         if (roleIds != null && !roleIds.isEmpty()) {
458             List<DelegateTypeBo> coll = getDataObjectService().findMatching(DelegateTypeBo.class,
459                     QueryByCriteria.Builder.fromPredicates(
460                             PredicateFactory.in(KIMPropertyConstants.Delegation.ROLE_ID, roleIds),
461                             PredicateFactory.equal(KIMPropertyConstants.Delegation.ACTIVE, Boolean.TRUE) ) ).getResults();
462 
463             return new ArrayList<DelegateTypeBo>( coll );
464         }
465 
466         return Collections.emptyList();
467     }
468 
469     /**
470      * Calls the KimRoleDao's "getDelegationPrincipalsForPrincipalIdAndDelegationIds" method and/or retrieves any corresponding members from the cache.
471      */
472     protected List<DelegateMemberBo> getStoredDelegationPrincipalsForPrincipalIdAndDelegationIds(Collection<String> delegationIds, String principalId) {
473         List<Predicate> criteria = new ArrayList<Predicate>();
474 
475         if ( StringUtils.isNotBlank(principalId) ) {
476             criteria.add( PredicateFactory.equal(KIMPropertyConstants.DelegationMember.MEMBER_ID, principalId) );
477         } else {
478             return Collections.emptyList(); // no principal ID - abort
479         }
480         criteria.add( PredicateFactory.equal(KIMPropertyConstants.DelegationMember.MEMBER_TYPE_CODE, MemberType.PRINCIPAL.getCode()));
481 
482         if (delegationIds != null && !delegationIds.isEmpty()) {
483             criteria.add( PredicateFactory.in(KIMPropertyConstants.DelegationMember.DELEGATION_ID, delegationIds) );
484         }
485 
486         List<DelegateMemberBo> coll = getDataObjectService().findMatching(DelegateMemberBo.class, QueryByCriteria.Builder.fromPredicates(criteria) ).getResults();
487         ArrayList<DelegateMemberBo> results = new ArrayList<DelegateMemberBo>(coll.size());
488         DateTime now = new DateTime( getDateTimeService().getCurrentTimestamp().getTime() );
489         for (DelegateMemberBo rm : coll) {
490             if (rm.isActive(now)) {
491                 results.add(rm);
492             }
493         }
494 
495         return results;
496     }
497 
498     /**
499      * Retrieves a DelegateMemberBo object by its ID. If the delegation member already exists in the cache, this method will return the cached
500      * version; otherwise, it will retrieve the uncached version from the database and then cache it before returning it.
501      */
502     protected DelegateMemberBo getDelegateMemberBo(String delegationMemberId) {
503         if (StringUtils.isBlank(delegationMemberId)) {
504             return null;
505         }
506 
507         return getDataObjectService().find(DelegateMemberBo.class,delegationMemberId);
508     }
509 
510     /**
511      * Retrieves a DelegateMemberBo List by (principal/group/role) member ID and delegation ID. If the List already exists in the cache,
512      * this method will return the cached one; otherwise, it will retrieve the uncached version from the database and then cache it before returning it.
513      */
514     protected List<DelegateMemberBo> getDelegationMemberBoListByMemberAndDelegationId(String memberId, String delegationId) {
515 
516         Map<String, String> searchCriteria = new HashMap<String, String>(2);
517         searchCriteria.put(KimConstants.PrimaryKeyConstants.MEMBER_ID, memberId);
518         searchCriteria.put(KimConstants.PrimaryKeyConstants.DELEGATION_ID, delegationId);
519         return new ArrayList<DelegateMemberBo>(getDataObjectService().findMatching(DelegateMemberBo.class, QueryByCriteria.Builder.andAttributes(searchCriteria).build()).getResults());
520     }
521 
522     protected Object getMember(String memberTypeCode, String memberId) {
523         if (StringUtils.isBlank(memberId)) {
524             return null;
525         }
526         if (MemberType.PRINCIPAL.getCode().equals(memberTypeCode)) {
527             return getIdentityService().getPrincipal(memberId);
528         } else if (MemberType.GROUP.getCode().equals(memberTypeCode)) {
529             return getGroupService().getGroup(memberId);
530         } else if (MemberType.ROLE.getCode().equals(memberTypeCode)) {
531             return getRoleBo(memberId);
532         }
533         return null;
534     }
535 
536     protected String getMemberName(Object member) {
537         if (member == null) {
538             return "";
539         }
540         if (member instanceof Principal) {
541             return ((Principal) member).getPrincipalName();
542         }
543         if (member instanceof Group) {
544             return ((Group) member).getName();
545         }
546         if (member instanceof Role) {
547             return ((Role) member).getName();
548         }
549         return member.toString();
550     }
551 
552     protected RoleBo getRoleBo(String roleId) {
553         if (StringUtils.isBlank(roleId)) {
554             return null;
555         }
556         return getDataObjectService().find(RoleBo.class, roleId);
557     }
558 
559     protected RoleBoLite getRoleBoLite(String roleId) {
560         if (StringUtils.isBlank(roleId)) {
561             return null;
562         }
563         return getDataObjectService().find(RoleBoLite.class, roleId);
564     }
565 
566     protected DelegateTypeBo getDelegationOfType(String roleId, DelegationType delegationType) {
567         List<DelegateTypeBo> roleDelegates = getRoleDelegations(roleId);
568         if (isDelegationPrimary(delegationType)) {
569             return getPrimaryDelegation(roleId, roleDelegates);
570         } else {
571             return getSecondaryDelegation(roleId, roleDelegates);
572         }
573     }
574 
575     private DelegateTypeBo getSecondaryDelegation(String roleId, List<DelegateTypeBo> roleDelegates) {
576         DelegateTypeBo secondaryDelegate = null;
577         RoleBoLite roleBo = getRoleBoLite(roleId);
578         for (DelegateTypeBo delegate : roleDelegates) {
579             if (isDelegationSecondary(delegate.getDelegationType())) {
580                 secondaryDelegate = delegate;
581             }
582         }
583         if (secondaryDelegate == null) {
584             secondaryDelegate = new DelegateTypeBo();
585             secondaryDelegate.setRoleId(roleId);
586             secondaryDelegate.setDelegationType(DelegationType.SECONDARY);
587             secondaryDelegate.setKimTypeId(roleBo.getKimTypeId());
588         }
589         return secondaryDelegate;
590     }
591 
592     protected DelegateTypeBo getPrimaryDelegation(String roleId, List<DelegateTypeBo> roleDelegates) {
593         DelegateTypeBo primaryDelegate = null;
594         RoleBoLite roleBo = getRoleBoLite(roleId);
595         for (DelegateTypeBo delegate : roleDelegates) {
596             if (isDelegationPrimary(delegate.getDelegationType())) {
597                 primaryDelegate = delegate;
598             }
599         }
600         if (primaryDelegate == null) {
601             primaryDelegate = new DelegateTypeBo();
602             primaryDelegate.setRoleId(roleId);
603             primaryDelegate.setDelegationType(DelegationType.PRIMARY);
604             primaryDelegate.setKimTypeId(roleBo.getKimTypeId());
605         }
606         return primaryDelegate;
607     }
608 
609     protected RoleMemberBo matchingMemberRecord(List<RoleMemberBo> roleMembers, String memberId, String memberTypeCode, Map<String, String> qualifier) {
610         for (RoleMemberBo rm : roleMembers) {
611             if (doesMemberMatch(rm, memberId, memberTypeCode, qualifier)) {
612                 return rm;
613             }
614         }
615         return null;
616     }
617 
618     protected boolean isDelegationPrimary(DelegationType delegationType) {
619         return DelegationType.PRIMARY.equals(delegationType);
620     }
621 
622     protected boolean isDelegationSecondary(DelegationType delegationType) {
623         return DelegationType.SECONDARY.equals(delegationType);
624     }
625 
626 
627     private List<DelegateTypeBo> getRoleDelegations(String roleId) {
628         if (roleId == null) {
629             return new ArrayList<DelegateTypeBo>();
630         }
631         return getStoredDelegationImplsForRoleIds(Collections.singletonList(roleId));
632 
633     }
634 
635     protected RoleBo getRoleBoByName(String namespaceCode, String roleName) {
636         if (StringUtils.isBlank(namespaceCode)
637                 || StringUtils.isBlank(roleName)) {
638             return null;
639         }
640         Map<String, Object> criteria = new HashMap<String, Object>(3);
641         criteria.put(KimConstants.UniqueKeyConstants.NAMESPACE_CODE, namespaceCode);
642         criteria.put(KimConstants.UniqueKeyConstants.NAME, roleName);
643         criteria.put(KRADPropertyConstants.ACTIVE, Boolean.TRUE);
644         QueryResults<RoleBo> results =
645                 getDataObjectService().findMatching(RoleBo.class, QueryByCriteria.Builder.andAttributes(criteria).build());
646         if (results.getResults().isEmpty()) {
647             return null;
648         } else if (results.getResults().size() > 1) {
649             throw new NonUniqueResultException("Finding a role by name should return a unique role, "
650                     + "but encountered multiple. namespaceCode='" + namespaceCode + "', name='" + roleName +"'");
651         }
652         return results.getResults().get(0);
653     }
654 
655     protected RoleBoLite getRoleBoLiteByName(String namespaceCode, String roleName) {
656         if (StringUtils.isBlank(namespaceCode)
657                 || StringUtils.isBlank(roleName)) {
658             return null;
659         }
660         Map<String, Object> criteria = new HashMap<String, Object>(3);
661         criteria.put(KimConstants.UniqueKeyConstants.NAMESPACE_CODE, namespaceCode);
662         criteria.put(KimConstants.UniqueKeyConstants.NAME, roleName);
663         criteria.put(KRADPropertyConstants.ACTIVE, Boolean.TRUE);
664         QueryResults<RoleBoLite> results =
665                 getDataObjectService().findMatching(RoleBoLite.class, QueryByCriteria.Builder.andAttributes(criteria).build());
666         if (results.getResults().isEmpty()) {
667             return null;
668         } else if (results.getResults().size() > 1) {
669             throw new NonUniqueResultException("Finding a role by name should return a unique role, "
670                     + "but encountered multiple. namespaceCode='" + namespaceCode + "', name='" + roleName +"'");
671         }
672         return results.getResults().get(0);
673     }
674 
675     protected List<RoleMember> doAnyMemberRecordsMatchByExactQualifier( RoleEbo role, String memberId, RoleDaoAction daoActionToTake, Map<String, String> qualifier ) {
676         List<RoleMemberBo> roleMemberBos = getRoleMembersByExactQualifierMatch(role, memberId, daoActionToTake, qualifier);
677         List<RoleMember> roleMembers = new ArrayList<RoleMember>();
678         if(CollectionUtils.isNotEmpty(roleMemberBos)) {
679             for (RoleMemberBo bo : roleMemberBos) {
680                 roleMembers.add(RoleMemberBo.to(bo));
681             }
682             return roleMembers;
683         }
684 
685         return Collections.emptyList();
686     }
687 
688     protected List<RoleMemberBo> getRoleMembersByExactQualifierMatch(RoleEbo role, String memberId, RoleDaoAction daoActionToTake, Map<String, String> qualifier) {
689         List<RoleMemberBo> rms = new ArrayList<RoleMemberBo>();
690         RoleTypeService roleTypeService = getRoleTypeService( role.getId() );
691         if(roleTypeService != null) {
692             List<String> attributesForExactMatch = roleTypeService.getQualifiersForExactMatch();
693             if(CollectionUtils.isNotEmpty(attributesForExactMatch)) {
694                 switch (daoActionToTake) {
695                     case ROLE_GROUPS_FOR_GROUP_IDS_AND_ROLE_IDS : // Search for group role members only.
696                         rms = getStoredRoleGroupsForGroupIdsAndRoleIds(Collections.singletonList(role.getId()), Collections.singletonList(memberId), populateQualifiersForExactMatch(qualifier, attributesForExactMatch));
697                         break;
698                     case ROLE_PRINCIPALS_FOR_PRINCIPAL_ID_AND_ROLE_IDS : // Search for principal role members only.
699                         rms = getStoredRolePrincipalsForPrincipalIdAndRoleIds(Collections.singletonList(role.getId()), memberId, populateQualifiersForExactMatch(qualifier, attributesForExactMatch));
700                         break;
701                     case ROLE_MEMBERSHIPS_FOR_ROLE_IDS_AS_MEMBERS : // Search for roles as role members only.
702                         List<RoleMemberBo> allRoleMembers = getStoredRoleMembershipsForRoleIdsAsMembers(Collections.singletonList(role.getId()), populateQualifiersForExactMatch(qualifier, attributesForExactMatch));
703                         for(RoleMemberBo rm : allRoleMembers) {
704                             if ( rm.getMemberId().equals(memberId) ) {
705                                 rms.add(rm);
706                             }
707                         }
708                         break;
709                     default : // The daoActionToTake parameter is invalid; throw an exception.
710                         throw new IllegalArgumentException("The 'daoActionToTake' parameter cannot refer to a non-role-member-related value!");
711                 }
712 
713             }
714         }
715         return rms;
716     }
717 
718     //return roleMemberId of match or null if no match
719     protected RoleMember doAnyMemberRecordsMatch(List<RoleMemberBo> roleMembers, String memberId, String memberTypeCode, Map<String, String> qualifier) {
720         for (RoleMemberBo rm : roleMembers) {
721             if (rm.isActive() && doesMemberMatch(rm, memberId, memberTypeCode, qualifier)) {
722                 return RoleMemberBo.to(rm);
723             }
724         }
725         return null;
726     }
727 
728     protected boolean doesMemberMatch(RoleMemberBo roleMember, String memberId, String memberTypeCode, Map<String, String> qualifier) {
729         if (roleMember.getMemberId().equals(memberId) && roleMember.getType().getCode().equals(memberTypeCode)) {
730             // member ID/type match
731             Map<String, String> roleQualifier = roleMember.getAttributes();
732             if ((qualifier == null || qualifier.isEmpty())
733                     && (roleQualifier == null || roleQualifier.isEmpty())) {
734                 return true; // blank qualifier match
735             } else {
736                 if (qualifier != null && roleQualifier != null && qualifier.equals(roleQualifier)) {
737                     return true; // qualifier match
738                 }
739             }
740         }
741         return false;
742     }
743 
744     /**
745      * Retrieves the role type service associated with the given role ID
746      *
747      * @param roleId the role ID to get the role type service for
748      * @return the Role Type Service
749      */
750     protected RoleTypeService getRoleTypeService(String roleId) {
751         RoleBoLite roleBo = getRoleBoLite(roleId);
752         if(roleBo != null){
753             KimType roleType = KimTypeBo.to(roleBo.getKimRoleType());
754             if (roleType != null) {
755                 return getRoleTypeService(roleType);
756             }
757         }
758         return KimImplServiceLocator.getDefaultRoleTypeService();
759     }
760 
761     /**
762      * Retrieves the role type service for the given service name.
763      *
764      * @param serviceName the name of the service to retrieve
765      * @return the Role Type Service
766      */
767     protected RoleTypeService getRoleTypeServiceByName(String serviceName) {
768         try {
769             KimTypeService service = (KimTypeService) GlobalResourceLoader.getService(QName.valueOf(serviceName));
770             if (service != null && service instanceof RoleTypeService) {
771                 return (RoleTypeService) service;
772             }
773             LOG.warn("Unable to find role type service by name: " + serviceName + ". Defaulting to: kimNoMembersRoleTypeService ");
774             return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
775         } catch (Exception ex) {
776             LOG.warn("Unable to find role type service by name: " + serviceName, ex);
777             return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
778         }
779     }
780 
781     protected RoleTypeService getRoleTypeService(KimType typeInfo) {
782         String serviceName = typeInfo.getServiceName();
783         if (serviceName != null) {
784             try {
785                 KimTypeService service = (KimTypeService) GlobalResourceLoader.getService(QName.valueOf(serviceName));
786                 if (service != null && service instanceof RoleTypeService) {
787                     return (RoleTypeService) service;
788                 }
789                 LOG.warn("Unable to find role type service with name: " + serviceName + ". Defaulting to: kimNoMembersRoleTypeService ");
790                 return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
791             } catch (Exception ex) {
792                 LOG.error("Unable to find role type service with name: " + serviceName, ex);
793                 return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
794             }
795         }
796         return KimImplServiceLocator.getDefaultRoleTypeService();
797     }
798 
799     protected Map<String, String> populateQualifiersForExactMatch(Map<String, String> defaultQualification, List<String> attributes) {
800         Map<String,String> qualifiersForExactMatch = new HashMap<String,String>();
801         if (defaultQualification != null && CollectionUtils.isNotEmpty(defaultQualification.keySet())) {
802             for (String attributeName : attributes) {
803                 if (StringUtils.isNotEmpty(defaultQualification.get(attributeName))) {
804                     qualifiersForExactMatch.put(attributeName, defaultQualification.get(attributeName));
805                 }
806             }
807         }
808         return qualifiersForExactMatch;
809     }
810 
811     // TODO: pulling attribute IDs repeatedly is inefficient - consider caching the entire list as a map
812     // TODO: KULRICE-12100: Most of the time there should be only one result for the kimTypeId and attributeName, but it is not guaranteed, which it should be.
813     // TODO: pulling attribute IDs repeatedly is inefficient - consider caching the entire list as a map
814     /*
815      * search by attribute name, if none return null, if there is only one, return. If there are multiple, then
816      * search by kimType: if found return else 
817      *     search by appId of kimType : if found return else
818      *          search by rice app id : if found return else
819      *              search by kuali app id : if found return else
820      *                  return null.
821      */
822     protected String getKimAttributeId(String kimTypeId, String attributeName) {
823         Collection<KimAttributeBo> attributeData = getAttributeByName(attributeName);
824         String kimAttributeId = null;
825 
826         if (CollectionUtils.isNotEmpty(attributeData)) {
827             if (CollectionUtils.size(attributeData) == 1) {
828                 kimAttributeId = attributeData.iterator().next().getId();
829             } else {
830                 kimAttributeId = getCorrectAttributeId(kimTypeId, attributeName, attributeData);
831             }
832         }
833 
834         return kimAttributeId;
835     }
836 
837     /*
838      * Searches the KimAttributeBo for the attribute by name
839      */
840     protected Collection<KimAttributeBo> getAttributeByName(String attributeName) {
841         /*Map<String, Object> critieria = new HashMap<String, Object>(1);
842         critieria.put(KimConstants.AttributeConstants.ATTRIBUTE_NAME, attributeName);*/
843         QueryResults<KimAttributeBo> attributeData = getDataObjectService().findMatching(KimAttributeBo.class, QueryByCriteria.Builder.forAttribute("attributeName", attributeName).build());
844 
845         return attributeData.getResults();
846     }
847 
848     /*
849      * Attempts to get the right attribute for the kimType. If it fails, then tries by namespace.
850      */
851     protected String getCorrectAttributeId(String kimTypeId, String attributeName, Collection<KimAttributeBo> attributeData) {
852         KimType kimType = getKimTypeInfoService().getKimType(kimTypeId);
853         String attribute = getAttributeFromKimType(kimType, attributeName);
854 
855         if (attribute != null) {
856             return attribute;
857         } else {
858             return getAttributeFromNamespace(kimType, attributeName, attributeData);
859         }
860     }
861 
862     protected String getAttributeFromKimType(KimType kimType, String attributeName) {
863         if (kimType != null) {
864             for (KimTypeAttribute attribute : kimType.getAttributeDefinitions()) {
865                 if (attribute.getKimAttribute() != null
866                         && StringUtils.equals(attributeName, attribute.getKimAttribute().getAttributeName())) {
867                     return attribute.getKimAttribute().getId();
868                 }
869             }
870         }
871 
872         return null;
873     }
874 
875     /*
876      * Gets the attribute based on the app namespace, if it cannot find then tries Rice namespace and then Kuali.
877      */
878     protected String getAttributeFromNamespace(KimType kimType, String attributeName, Collection<KimAttributeBo> attributes) {
879         String appId = getAppIdFromNamespace(kimType.getNamespaceCode());
880         String attributeId = getAttributeFromAppId(attributes, appId);
881 
882         if (attributeId == null) {
883             attributeId = getAttributeFromAppId(attributes, KimConstants.KIM_TYPE_RICE_NAMESPACE);
884             if (attributeId == null) {
885                 attributeId = getAttributeFromAppId(attributes, KimConstants.KIM_TYPE_DEFAULT_NAMESPACE);
886             }
887         }
888 
889         return attributeId;
890     }
891 
892     protected String getAppIdFromNamespace(String namespaceCode) {
893         Namespace appNamespace = getNamespaceService().getNamespace(namespaceCode);
894         if (appNamespace == null) {
895             throw new RuntimeException("Namespace " + namespaceCode + " not mapped in namespace table.");
896         }
897 
898         return appNamespace.getApplicationId();
899     }
900 
901     /*
902      * Compares the appId of the attribute with the given appId.
903      * Here we make the assumption that there are not multiple attributes with the same name
904      * for a given application.
905      */
906     protected String getAttributeFromAppId(Collection<KimAttributeBo> attributes, String appId) {
907         for (KimAttributeBo attribute : attributes) {
908             if (StringUtils.equalsIgnoreCase(getAppIdFromNamespace(attribute.getNamespaceCode()), appId)) {
909                 return attribute.getId();
910             }
911         }
912 
913         return null;
914     }
915 
916     protected KimTypeInfoService getKimTypeInfoService() {
917         if (kimTypeInfoService == null) {
918             kimTypeInfoService = KimApiServiceLocator.getKimTypeInfoService();
919         }
920 
921         return kimTypeInfoService;
922     }
923 
924     protected NamespaceService getNamespaceService() {
925         if (namespaceService == null) {
926             namespaceService = CoreServiceApiServiceLocator.getNamespaceService();
927         }
928 
929         return namespaceService;
930     }
931 
932     protected IdentityService getIdentityService() {
933         if (identityService == null) {
934             identityService = KimApiServiceLocator.getIdentityService();
935         }
936 
937         return identityService;
938     }
939 
940     protected GroupService getGroupService() {
941         if (groupService == null) {
942             groupService = KimApiServiceLocator.getGroupService();
943         }
944 
945         return groupService;
946     }
947 
948     protected ResponsibilityInternalService getResponsibilityInternalService() {
949         if (responsibilityInternalService == null) {
950             responsibilityInternalService = KimImplServiceLocator.getResponsibilityInternalService();
951         }
952         return responsibilityInternalService;
953     }
954 
955     protected RoleDao getRoleDao() {
956         return this.roleDao;
957     }
958 
959     public void setRoleDao(RoleDao roleDao) {
960         this.roleDao = roleDao;
961     }
962 
963     public DataObjectService getDataObjectService() {
964         if ( dataObjectService == null ) {
965             dataObjectService = KradDataServiceLocator.getDataObjectService();
966         }
967         return dataObjectService;
968     }
969 
970     public void setDataObjectService(DataObjectService dataObjectService) {
971         this.dataObjectService = dataObjectService;
972     }
973 
974     public DateTimeService getDateTimeService() {
975         if ( dateTimeService == null ) {
976             dateTimeService = CoreApiServiceLocator.getDateTimeService();
977         }
978         return dateTimeService;
979     }
980 
981     public void setDateTimeService(DateTimeService dateTimeService) {
982         this.dateTimeService = dateTimeService;
983     }
984 
985 }