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