View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kim.impl.group;
17  
18  import static org.kuali.rice.core.api.criteria.PredicateFactory.and;
19  import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
20  import static org.kuali.rice.core.api.criteria.PredicateFactory.in;
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import javax.jws.WebParam;
32  
33  import org.apache.commons.collections.CollectionUtils;
34  import org.apache.commons.lang.StringUtils;
35  import org.apache.log4j.Logger;
36  import org.joda.time.DateTime;
37  import org.kuali.rice.core.api.criteria.Predicate;
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.exception.RiceIllegalArgumentException;
41  import org.kuali.rice.core.api.exception.RiceRuntimeException;
42  import org.kuali.rice.core.api.membership.MemberType;
43  import org.kuali.rice.kim.api.KimConstants;
44  import org.kuali.rice.kim.api.common.history.HistoryQueryUtils;
45  import org.kuali.rice.kim.api.group.Group;
46  import org.kuali.rice.kim.api.group.GroupMember;
47  import org.kuali.rice.kim.api.group.GroupMemberQueryResults;
48  import org.kuali.rice.kim.api.group.GroupQueryResults;
49  import org.kuali.rice.kim.api.group.GroupService;
50  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
51  import org.kuali.rice.kim.impl.KIMPropertyConstants;
52  import org.kuali.rice.kim.impl.common.attribute.AttributeTransform;
53  import org.kuali.rice.kim.impl.common.attribute.KimAttributeDataBo;
54  import org.kuali.rice.kim.impl.services.KimImplServiceLocator;
55  import org.kuali.rice.krad.data.DataObjectService;
56  
57  public class GroupServiceImpl extends GroupServiceBase implements GroupService {
58      private static final Logger LOG = Logger.getLogger(GroupServiceImpl.class);
59  
60      private DataObjectService dataObjectService;
61  
62      @Override
63      public Group getGroup(String groupId) throws RiceIllegalArgumentException {
64          incomingParamCheck(groupId, "groupId");
65  		return GroupBo.to(getGroupBo(groupId));
66      }
67  
68      @Override
69      public List<Group> getGroupsByPrincipalId(String principalId) throws RiceIllegalArgumentException {
70          incomingParamCheck(principalId,  "principalId");
71          return getGroupsByPrincipalIdAndNamespaceCodeInternal(principalId, null);
72      }
73  
74      @Override
75      public List<Group> getGroupsByPrincipalIdAndNamespaceCode(String principalId, String namespaceCode) throws RiceIllegalArgumentException {
76          incomingParamCheck(principalId, "principalId");
77          incomingParamCheck(namespaceCode, "namespaceCode");
78             Collections.singleton("name");
79  		return getGroupsByPrincipalIdAndNamespaceCodeInternal(principalId, namespaceCode);
80      }
81  
82      protected List<Group> getGroupsByPrincipalIdAndNamespaceCodeInternal(String principalId, String namespaceCode) throws RiceIllegalArgumentException {
83  
84          Collection<Group> directGroups = getDirectGroupsForPrincipal( principalId, namespaceCode, new DateTime(System.currentTimeMillis()) );
85  		Set<Group> groups = new HashSet<Group>();
86          groups.addAll(directGroups);
87  		for ( Group group : directGroups ) {
88  			groups.add( group );
89  			groups.addAll( getParentGroups( group.getId() ) );
90  		}
91  		return Collections.unmodifiableList(new ArrayList<Group>( groups ));
92      }
93  
94      @Override
95      public List<String> findGroupIds(final QueryByCriteria queryByCriteria) throws RiceIllegalArgumentException {
96          incomingParamCheck(queryByCriteria, "queryByCriteria");
97  
98          GroupQueryResults results = this.findGroups(queryByCriteria);
99          List<String> result = new ArrayList<String>();
100 
101         for (Group group : results.getResults()) {
102             result.add(group.getId());
103         }
104 
105         return Collections.unmodifiableList(result);
106     }
107 
108     @Override
109     public boolean isDirectMemberOfGroup(String principalId, String groupId) throws RiceIllegalArgumentException {
110         incomingParamCheck(principalId, "principalId");
111         incomingParamCheck(groupId, "groupId");
112 
113         final QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
114         builder.setPredicates(
115                 and(
116                         equal(KIMPropertyConstants.GroupMember.MEMBER_ID, principalId),
117                         equal(KIMPropertyConstants.GroupMember.MEMBER_TYPE_CODE, KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode()),
118                         equal(KIMPropertyConstants.GroupMember.GROUP_ID, groupId),
119                         HistoryQueryUtils.between(KIMPropertyConstants.KimMember.ACTIVE_FROM_DATE_VALUE, KIMPropertyConstants.KimMember.ACTIVE_TO_DATE_VALUE, DateTime.now()))
120         );
121 		QueryResults<GroupMemberBo> groupMembers = dataObjectService.findMatching(GroupMemberBo.class, builder.build());
122 		return (groupMembers.getResults().size() > 0);
123     }
124 
125     @Override
126     public List<String> getGroupIdsByPrincipalId(String principalId) throws RiceIllegalArgumentException {
127         incomingParamCheck(principalId, "principalId");
128         return getGroupIdsByPrincipalIdAndNamespaceCodeInternal(principalId, null);
129     }
130 
131     @Override
132     public List<String> getGroupIdsByPrincipalIdAndNamespaceCode(String principalId, String namespaceCode) throws RiceIllegalArgumentException {
133         incomingParamCheck(principalId, "principalId");
134         incomingParamCheck(namespaceCode, "namespaceCode");
135 
136         List<String> result = new ArrayList<String>();
137 
138         if (principalId != null) {
139             List<Group> groupList = getGroupsByPrincipalIdAndNamespaceCode(principalId, namespaceCode);
140 
141             for (Group group : groupList) {
142                 result.add(group.getId());
143             }
144         }
145 
146         return Collections.unmodifiableList(result);
147     }
148 
149     protected List<String> getGroupIdsByPrincipalIdAndNamespaceCodeInternal(String principalId, String namespaceCode) throws RiceIllegalArgumentException {
150 
151         List<String> result = new ArrayList<String>();
152 
153         if (principalId != null) {
154             List<Group> groupList = getGroupsByPrincipalIdAndNamespaceCodeInternal(principalId, namespaceCode);
155 
156             for (Group group : groupList) {
157                 result.add(group.getId());
158             }
159         }
160 
161         return Collections.unmodifiableList(result);
162     }
163 
164     @Override
165     public List<String> getDirectGroupIdsByPrincipalId(String principalId) throws RiceIllegalArgumentException {
166         incomingParamCheck(principalId, "principalId");
167 
168         List<String> result = new ArrayList<String>();
169 
170         if (principalId != null) {
171         	Collection<Group> groupList = getDirectGroupsForPrincipal(principalId);
172 
173             for (Group g : groupList) {
174                 result.add(g.getId());
175             }
176         }
177 
178         return Collections.unmodifiableList(result);
179     }
180 
181     @Override
182     public List<String> getMemberPrincipalIds(String groupId) throws RiceIllegalArgumentException {
183         incomingParamCheck(groupId, "groupId");
184 
185 		return getMemberPrincipalIdsInternal(groupId, new HashSet<String>());
186     }
187 
188     @Override
189     public List<String> getDirectMemberPrincipalIds(String groupId) throws RiceIllegalArgumentException {
190         incomingParamCheck(groupId, "groupId");
191 
192         return this.getMemberIdsByType(getMembersOfGroup(groupId), KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE);
193     }
194 
195     @Override
196     public List<String> getMemberGroupIds(String groupId) throws RiceIllegalArgumentException {
197         incomingParamCheck(groupId, "groupId");
198 
199 		List<GroupBo> groups = getMemberGroupBos( groupId );
200 		ArrayList<String> groupIds = new ArrayList<String>( groups.size() );
201 		for ( GroupBo group : groups ) {
202 			if ( group.isActive() ) {
203 				groupIds.add( group.getId() );
204 			}
205 		}
206 		return Collections.unmodifiableList(groupIds);
207     }
208 
209 
210 	protected List<GroupBo> getMemberGroupBos(String groupId) {
211 		if ( groupId == null ) {
212 			return Collections.emptyList();
213 		}
214 		Set<GroupBo> groups = new HashSet<GroupBo>();
215 
216 		GroupBo group = getGroupBo(groupId);
217 		getMemberGroupsInternal(group, groups);
218 
219 		return new ArrayList<GroupBo>(groups);
220 	}
221 
222     protected void getMemberGroupsInternal( GroupBo group, Set<GroupBo> groups ) {
223 		if ( group == null ) {
224 			return;
225 		}
226 		List<String> groupIds = group.getMemberGroupIds();
227 
228 		for (String id : groupIds) {
229 			GroupBo memberGroup = getGroupBo(id);
230 			// if we've already seen that group, don't recurse into it
231 			if ( memberGroup.isActive() && !groups.contains( memberGroup ) ) {
232 				groups.add(memberGroup);
233 				getMemberGroupsInternal(memberGroup,groups);
234 			}
235 		}
236 
237 	}
238 
239     @Override
240 	public boolean isGroupMemberOfGroup(String groupMemberId, String groupId) throws RiceIllegalArgumentException {
241 		return isGroupMemberOfGroupWithDate(groupMemberId, groupId, new DateTime(System.currentTimeMillis()));
242 	}
243 
244     @Override
245     public boolean isGroupMemberOfGroupWithDate(String groupMemberId, String groupId, DateTime asOfDate) throws RiceIllegalArgumentException {
246         incomingParamCheck(groupMemberId, "groupMemberId");
247         incomingParamCheck(groupId, "groupId");
248         incomingParamCheck(asOfDate, "asOfDate");
249 
250         return isMemberOfGroupInternal(groupMemberId, groupId, new HashSet<String>(), KimConstants.KimGroupMemberTypes.GROUP_MEMBER_TYPE, asOfDate);
251     }
252 
253     @Override
254     public boolean isMemberOfGroup(String principalId, String groupId) throws RiceIllegalArgumentException{
255         return isMemberOfGroupWithDate(principalId, groupId, new DateTime(System.currentTimeMillis()));
256     }
257 
258     @Override
259     public boolean isMemberOfGroupWithDate(String principalId, String groupId, DateTime asOfDate) throws RiceIllegalArgumentException{
260         incomingParamCheck(principalId, "principalId");
261         incomingParamCheck(groupId, "groupId");
262         incomingParamCheck(asOfDate, "asOfDate");
263 
264         Set<String> visitedGroupIds = new HashSet<String>();
265         return isMemberOfGroupInternal(principalId, groupId, visitedGroupIds, KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE, asOfDate);
266     }
267 
268     @Override
269     public List<String> getDirectMemberGroupIds(String groupId) throws RiceIllegalArgumentException{
270         incomingParamCheck(groupId, "groupId");
271 
272         return this.getMemberIdsByType(getMembersOfGroup(groupId), KimConstants.KimGroupMemberTypes.GROUP_MEMBER_TYPE);
273     }
274 
275     @Override
276     public List<String> getDirectMemberGroupIdsWithDate(String groupId) throws RiceIllegalArgumentException{
277         incomingParamCheck(groupId, "groupId");
278 
279         return this.getMemberIdsByType(getMembersOfGroup(groupId), KimConstants.KimGroupMemberTypes.GROUP_MEMBER_TYPE);
280     }
281 
282     @Override
283     public List<String> getParentGroupIds(String groupId) throws RiceIllegalArgumentException {
284         incomingParamCheck(groupId, "groupId");
285 
286         List<String> result = new ArrayList<String>();
287         if (groupId != null) {
288             List<Group> groupList = getParentGroups(groupId);
289 
290             for (Group group : groupList) {
291                 result.add(group.getId());
292             }
293         }
294 
295         return Collections.unmodifiableList(result);
296     }
297 
298     @Override
299     public List<String> getDirectParentGroupIds(String groupId) throws RiceIllegalArgumentException {
300         return getDirectParentGroupIdsWithDate(groupId, new DateTime(System.currentTimeMillis()));
301     }
302 
303     @Override
304     public List<String> getDirectParentGroupIdsWithDate(String groupId, DateTime asOfDate) throws RiceIllegalArgumentException {
305         incomingParamCheck(groupId, "groupId");
306         incomingParamCheck(asOfDate, "asOfDate");
307 
308         List<String> result = new ArrayList<String>();
309         if (groupId != null) {
310             List<Group> groupList = getDirectParentGroups(groupId, asOfDate);
311             for (Group group : groupList) {
312                 result.add(group.getId());
313             }
314         }
315 
316         return Collections.unmodifiableList(result);
317     }
318 
319     @Override
320     public Map<String, String> getAttributes(String groupId) throws RiceIllegalArgumentException {
321         incomingParamCheck(groupId, "groupId");
322 
323         Group group = getGroup(groupId);
324         if (group != null) {
325             return group.getAttributes();
326         }
327         return Collections.emptyMap();
328     }
329 
330     @Override
331     public List<GroupMember> getMembers(List<String> groupIds) throws RiceIllegalArgumentException{
332         return getMembersWithDate(groupIds, new DateTime(System.currentTimeMillis()));
333     }
334 
335     @Override
336     public List<GroupMember> getMembersWithDate(List<String> groupIds, DateTime asOfDate) throws RiceIllegalArgumentException{
337         incomingParamCheck(groupIds, "groupIds");
338         incomingParamCheck(asOfDate, "asOfDate");
339 
340         final QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
341         builder.setPredicates(
342                 and(
343                     in(KIMPropertyConstants.GroupMember.GROUP_ID, groupIds.toArray(new String[groupIds.size()])),
344                     HistoryQueryUtils.between(KIMPropertyConstants.KimMember.ACTIVE_FROM_DATE_VALUE, KIMPropertyConstants.KimMember.ACTIVE_TO_DATE_VALUE, asOfDate))
345                 );
346         return findGroupMembers(builder.build()).getResults();
347     }
348 
349     @Override
350     public List<Group> getGroups(Collection<String> groupIds) throws RiceIllegalArgumentException {
351         incomingParamCheck(groupIds, "groupIds");
352         if (groupIds.isEmpty()) {
353             return Collections.emptyList();
354         }
355         final QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
356         builder.setPredicates(and(in("id", groupIds.toArray()), equal("active", Boolean.TRUE)));
357         GroupQueryResults qr = findGroups(builder.build());
358 
359         return qr.getResults();
360     }
361 
362     @Override
363     public Group getGroupByNamespaceCodeAndName(String namespaceCode, String groupName) throws RiceIllegalArgumentException{
364         incomingParamCheck(namespaceCode, "namespaceCode");
365         incomingParamCheck(groupName, "groupName");
366 
367         final QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
368         builder.setPredicates(
369                     and(
370                         equal(KimConstants.UniqueKeyConstants.NAMESPACE_CODE, namespaceCode),
371                         equal(KimConstants.UniqueKeyConstants.GROUP_NAME, groupName)));
372 		QueryResults<GroupBo> groups = dataObjectService.findMatching(GroupBo.class, builder.build());
373 		if ( !groups.getResults().isEmpty() ) {
374 			return GroupBo.to(groups.getResults().iterator().next());
375 		}
376 		return null;
377     }
378 
379     @Override
380     public GroupQueryResults findGroups(final QueryByCriteria queryByCriteria) throws RiceIllegalArgumentException {
381         incomingParamCheck(queryByCriteria, "queryByCriteria");
382 
383         QueryResults<GroupBo> results = dataObjectService.findMatching(GroupBo.class,
384                 AttributeTransform.getInstance().apply(queryByCriteria));
385 
386         GroupQueryResults.Builder builder = GroupQueryResults.Builder.create();
387         builder.setMoreResultsAvailable(results.isMoreResultsAvailable());
388         builder.setTotalRowCount(results.getTotalRowCount());
389 
390         final List<Group.Builder> ims = new ArrayList<Group.Builder>();
391         for (GroupBo bo : results.getResults()) {
392             ims.add(Group.Builder.create(bo));
393         }
394 
395         builder.setResults(ims);
396         return builder.build();
397     }
398 
399     @Override
400     public GroupMemberQueryResults findGroupMembers(final QueryByCriteria queryByCriteria) throws RiceIllegalArgumentException {
401         incomingParamCheck(queryByCriteria, "queryByCriteria");
402 
403         QueryResults<GroupMemberBo> results = dataObjectService.findMatching(GroupMemberBo.class, queryByCriteria);
404 
405         GroupMemberQueryResults.Builder builder = GroupMemberQueryResults.Builder.create();
406         builder.setMoreResultsAvailable(results.isMoreResultsAvailable());
407         builder.setTotalRowCount(results.getTotalRowCount());
408 
409         final List<GroupMember.Builder> ims = new ArrayList<GroupMember.Builder>();
410         for (GroupMemberBo bo : results.getResults()) {
411             ims.add(GroupMember.Builder.create(bo));
412         }
413 
414         builder.setResults(ims);
415         return builder.build();
416     }
417 
418 
419     protected boolean isMemberOfGroupInternal(String memberId,
420             String groupId,
421             Set<String> visitedGroupIds,
422             MemberType memberType,
423             DateTime asOfDate) {
424 
425         if ( memberId == null || groupId == null ) {
426 			return false;
427 		}
428 
429 		// when group traversal is not needed
430 		Group group = getGroup(groupId);
431 		if ( group == null || !group.isActive() ) {
432 			return false;
433 		}
434 
435         List<GroupMember> members = getMembersOfGroupWithDate(group.getId(), asOfDate);
436 		// check the immediate group
437 		for (String groupMemberId : getMemberIdsByType(members, memberType)) {
438 			if (groupMemberId.equals(memberId)) {
439 				return true;
440 			}
441 		}
442 
443 		// check each contained group, returning as soon as a match is found
444 		for ( String memberGroupId : getMemberIdsByType(members, KimConstants.KimGroupMemberTypes.GROUP_MEMBER_TYPE) ) {
445 			if (!visitedGroupIds.contains(memberGroupId)){
446 				visitedGroupIds.add(memberGroupId);
447 				if ( isMemberOfGroupInternal( memberId, memberGroupId, visitedGroupIds, memberType, asOfDate ) ) {
448 					return true;
449 				}
450 			}
451 		}
452 
453 		// no match found, return false
454 		return false;
455 	}
456 
457     protected void getParentGroupsInternal( String groupId, Set<Group> groups ) {
458 		List<Group> parentGroups = getDirectParentGroups( groupId, new DateTime(System.currentTimeMillis()) );
459 		for ( Group group : parentGroups ) {
460 			if ( !groups.contains( group ) ) {
461 				groups.add( group );
462 				getParentGroupsInternal( group.getId(), groups );
463 			}
464 		}
465 	}
466 
467     protected List<Group> getDirectParentGroups(String groupId, DateTime asOfDate) {
468         incomingParamCheck(groupId, "groupId");
469 
470         final QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
471         builder.setPredicates(
472                 and(
473                     equal(KIMPropertyConstants.GroupMember.MEMBER_ID, groupId),
474                     equal(KIMPropertyConstants.GroupMember.MEMBER_TYPE_CODE, KimConstants.KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode()),
475                     HistoryQueryUtils.between(KIMPropertyConstants.KimMember.ACTIVE_FROM_DATE_VALUE,
476                             KIMPropertyConstants.KimMember.ACTIVE_TO_DATE_VALUE, asOfDate)));
477 
478         List<GroupMember> groupMembers = findGroupMembers(builder.build()).getResults();
479 		Set<String> matchingGroupIds = new HashSet<String>();
480 		// filter to active groups
481 		for ( GroupMember gm : groupMembers ) {
482 		    matchingGroupIds.add(gm.getGroupId());
483 		}
484 		if (CollectionUtils.isNotEmpty(matchingGroupIds)) {
485             return getGroups(matchingGroupIds);
486         }
487         return Collections.emptyList();
488 	}
489 
490     @Override
491     public List<GroupMember> getMembersOfGroup(String groupId) throws RiceIllegalArgumentException {
492         incomingParamCheck(groupId, "groupId");
493         return getMembersOfGroupWithDate(groupId, DateTime.now());
494     }
495 
496     @Override
497     public List<GroupMember> getMembersOfGroupWithDate(String groupId, DateTime asOfDate) throws RiceIllegalArgumentException {
498         incomingParamCheck(groupId, "groupId");
499 
500         final QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
501         builder.setPredicates(
502                 and(
503                     equal(KIMPropertyConstants.GroupMember.GROUP_ID, groupId),
504                     HistoryQueryUtils.between(KIMPropertyConstants.KimMember.ACTIVE_FROM_DATE_VALUE, KIMPropertyConstants.KimMember.ACTIVE_TO_DATE_VALUE, asOfDate)));
505 
506         return findGroupMembers(builder.build()).getResults();
507     }
508 
509     protected List<String> getMemberIdsByType(Collection<GroupMember> members, MemberType memberType) {
510         List<String> membersIds = new ArrayList<String>();
511         if (members != null) {
512             for (GroupMember member : members) {
513                 if (member.getType().equals(memberType)) {
514                     membersIds.add(member.getMemberId());
515                 }
516             }
517         }
518         return Collections.unmodifiableList(membersIds);
519     }
520 
521     protected GroupBo getGroupBo(String groupId) {
522         incomingParamCheck(groupId, "groupId");
523         return dataObjectService.find(GroupBo.class, groupId);
524     }
525 
526     protected GroupMemberBo getGroupMemberBo(String id) {
527         incomingParamCheck(id, "id");
528         return dataObjectService.find(GroupMemberBo.class, id);
529     }
530 
531 	protected List<Group> getParentGroups(String groupId) throws RiceIllegalArgumentException {
532 		if ( StringUtils.isEmpty(groupId) ) {
533 			throw new RiceIllegalArgumentException("groupId is blank");
534 		}
535 		Set<Group> groups = new HashSet<Group>();
536 		getParentGroupsInternal( groupId, groups );
537 		return new ArrayList<Group>( groups );
538 	}
539 
540     protected List<String> getMemberPrincipalIdsInternal(String groupId, Set<String> visitedGroupIds) {
541 		if ( groupId == null ) {
542 			return Collections.emptyList();
543 		}
544 		Set<String> ids = new HashSet<String>();
545 		GroupBo group = getGroupBo(groupId);
546 		if ( group == null || !group.isActive()) {
547 			return Collections.emptyList();
548 		}
549 
550         //List<String> memberIds = getMemberIdsByType(group, memberType);
551         //List<GroupMember> members = new ArrayList<GroupMember>(getMembersOfGroup(group.getId()));
552 		ids.addAll( group.getMemberPrincipalIds());
553 		visitedGroupIds.add(group.getId());
554 
555 		for (String memberGroupId : group.getMemberGroupIds()) {
556 			if (!visitedGroupIds.contains(memberGroupId)){
557 				ids.addAll(getMemberPrincipalIdsInternal(memberGroupId, visitedGroupIds));
558 			}
559 		}
560 
561 		return Collections.unmodifiableList(new ArrayList<String>(ids));
562 	}
563 
564     protected Collection<Group> getDirectGroupsForPrincipal( String principalId ) {
565 		return getDirectGroupsForPrincipal( principalId, null, new DateTime(System.currentTimeMillis()) );
566 	}
567 
568 	protected Collection<Group> getDirectGroupsForPrincipal( String principalId, String namespaceCode, DateTime asOfDate ) {
569 		if ( principalId == null ) {
570 			return Collections.emptyList();
571 		}
572 
573 		// only return the active members
574         final QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
575         builder.setPredicates(
576                 and(
577                     equal(KIMPropertyConstants.GroupMember.MEMBER_ID, principalId),
578                     equal(KIMPropertyConstants.GroupMember.MEMBER_TYPE_CODE, MemberType.PRINCIPAL.getCode()),
579                     HistoryQueryUtils.between(KIMPropertyConstants.KimMember.ACTIVE_FROM_DATE_VALUE, KIMPropertyConstants.KimMember.ACTIVE_TO_DATE_VALUE, asOfDate)));
580 		List<GroupMember> groupMembers = findGroupMembers(builder.build()).getResults();
581         Set<String> groupIds = new HashSet<String>( groupMembers.size());
582         for (GroupMember gm : groupMembers) {
583             groupIds.add(gm.getGroupId());
584         }
585 
586 		// pull all the group information for the matching members
587 		List<Group> groups = CollectionUtils.isEmpty(groupIds) ? Collections.<Group>emptyList() : getGroups(groupIds);
588 		List<Group> result = new ArrayList<Group>( groups.size() );
589 		// filter by namespace if necessary
590 		for ( Group group : groups ) {
591 			if ( group.isActive() ) {
592 				if ( StringUtils.isBlank(namespaceCode) || StringUtils.equals(namespaceCode, group.getNamespaceCode() ) ) {
593 					result.add(group);
594 				}
595 			}
596 		}
597 		return result;
598 	}
599 
600     @Override
601     public boolean addGroupToGroup(String childId, String parentId)  throws RiceIllegalArgumentException {
602         incomingParamCheck(childId, "childId");
603         incomingParamCheck(parentId, "parentId");
604 
605         if(childId.equals(parentId)) {
606             throw new RiceIllegalArgumentException("Can't add group to itself.");
607         }
608         if(isGroupMemberOfGroup(parentId, childId)) {
609             throw new RiceIllegalArgumentException("Circular group reference.");
610         }
611 
612         GroupMemberBo groupMember = new GroupMemberBo();
613         groupMember.setGroupId(parentId);
614         groupMember.setType(KimConstants.KimGroupMemberTypes.GROUP_MEMBER_TYPE);
615         groupMember.setMemberId(childId);
616 
617         this.dataObjectService.save(groupMember);
618         return true;
619     }
620 
621     @Override
622     public boolean addPrincipalToGroup(String principalId, String groupId) throws RiceIllegalArgumentException {
623         incomingParamCheck(principalId, "principalId");
624         incomingParamCheck(groupId, "groupId");
625 
626         GroupMemberBo groupMember = new GroupMemberBo();
627         groupMember.setGroupId(groupId);
628         groupMember.setType(KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE);
629         groupMember.setMemberId(principalId);
630 
631         groupMember = this.dataObjectService.save(groupMember);
632         KimImplServiceLocator.getGroupInternalService().updateForUserAddedToGroup(groupMember.getMemberId(),
633                 groupMember.getGroupId());
634         return true;
635     }
636 
637     @Override
638     public Group createGroup(Group group) throws RiceIllegalArgumentException {
639         incomingParamCheck(group, "group");
640         if (StringUtils.isNotBlank(group.getId()) && getGroup(group.getId()) != null) {
641             throw new RiceIllegalArgumentException("the group to create already exists: " + group);
642         }
643         List<GroupAttributeBo> attrBos = KimAttributeDataBo
644                 .createFrom(GroupAttributeBo.class, group.getAttributes(), group.getKimTypeId());
645         if (StringUtils.isNotEmpty(group.getId())) {
646             for (GroupAttributeBo attr : attrBos) {
647                 attr.setAssignedToId(group.getId());
648             }
649         }
650         GroupBo bo = GroupBo.from(group);
651         bo.setAttributeDetails(attrBos);
652 
653         bo = saveGroup(bo);
654 
655         return GroupBo.to(bo);
656     }
657 
658     @Override
659     public Group updateGroup(Group group) throws RiceIllegalArgumentException{
660         incomingParamCheck(group, "group");
661         GroupBo origGroup = getGroupBo(group.getId());
662         if (StringUtils.isBlank(group.getId()) || origGroup == null) {
663             throw new RiceIllegalArgumentException("the group does not exist: " + group);
664         }
665         List<GroupAttributeBo> attrBos = KimAttributeDataBo.createFrom(GroupAttributeBo.class, group.getAttributes(), group.getKimTypeId());
666         GroupBo bo = GroupBo.from(group);
667         bo.setMembers(origGroup.getMembers());
668         bo.setAttributeDetails(attrBos);
669 
670         bo = saveGroup(bo);
671         if (origGroup.isActive()
672                 && !bo.isActive()) {
673             KimImplServiceLocator.getRoleInternalService().groupInactivated(bo.getId());
674         }
675 
676         return GroupBo.to(bo);
677     }
678 
679     @Override
680 	public Group updateGroup(String groupId, Group group) throws RiceIllegalArgumentException{
681         incomingParamCheck(group, "group");
682         incomingParamCheck(groupId, "groupId");
683 
684         if (StringUtils.equals(groupId, group.getId())) {
685             return updateGroup(group);
686         }
687 
688         //if group Ids are different, inactivate old group, and create new with new id based off old
689         GroupBo groupBo = getGroupBo(groupId);
690 
691         if (StringUtils.isBlank(group.getId()) || groupBo == null) {
692             throw new RiceIllegalArgumentException("the group does not exist: " + group);
693         }
694 
695         //create and save new group
696         GroupBo newGroup = GroupBo.from(group);
697         newGroup.setMembers(groupBo.getMembers());
698         List<GroupAttributeBo> attrBos = KimAttributeDataBo.createFrom(GroupAttributeBo.class, group.getAttributes(), group.getKimTypeId());
699         newGroup.setAttributeDetails(attrBos);
700         newGroup = saveGroup(newGroup);
701 
702         //inactivate and save old group
703         groupBo.setActive(false);
704         saveGroup(groupBo);
705 
706         return GroupBo.to(newGroup);
707     }
708 
709     @Override
710     public GroupMember createGroupMember(GroupMember groupMember) throws RiceIllegalArgumentException {
711         incomingParamCheck(groupMember, "groupMember");
712         if (StringUtils.isNotBlank(groupMember.getId()) && getGroupMemberBo(groupMember.getId()) != null) {
713             throw new RiceIllegalArgumentException("the groupMember to create already exists: " + groupMember);
714         }
715 
716         GroupMemberBo bo = GroupMemberBo.from(groupMember);
717         GroupBo groupBo = getGroupBo(groupMember.getGroupId());
718         groupBo.getMembers().add(bo);
719         groupBo = saveGroup(groupBo);
720 
721         //get new groupMember from saved group
722         for (GroupMemberBo member : groupBo.getMembers()) {
723             if (member.getMemberId().equals(groupMember.getMemberId())
724                     && member.getType().equals(groupMember.getType())
725                     && member.getActiveFromDate().equals(groupMember.getActiveFromDate())
726                     && member.getActiveToDate().equals(groupMember.getActiveToDate())) {
727                 return GroupMemberBo.to(member);
728             }
729         }
730         return GroupMemberBo.to(bo);
731     }
732 
733     @Override
734     public GroupMember updateGroupMember(
735             @WebParam(name = "groupMember") GroupMember groupMember) throws RiceIllegalArgumentException {
736         incomingParamCheck(groupMember, "groupMember");
737         if (StringUtils.isBlank(groupMember.getId()) || getGroupMemberBo(groupMember.getId()) == null) {
738             throw new RiceIllegalArgumentException("the groupMember to update does not exist: " + groupMember);
739         }
740 
741         GroupMemberBo bo = GroupMemberBo.from(groupMember);
742         GroupBo groupBo = getGroupBo(groupMember.getGroupId());
743         //find and replace the existing member
744 
745         List<GroupMemberBo> memberList = new ArrayList<GroupMemberBo>();
746         for (GroupMemberBo member : groupBo.getMembers()) {
747             if (member.getId().equals(bo.getId())) {
748                 memberList.add(bo);
749             } else {
750                 memberList.add(member);
751             }
752 
753         }
754         groupBo.setMembers(memberList);
755         groupBo = saveGroup(groupBo);
756 
757         //get new groupMember from saved group
758         for (GroupMemberBo member : groupBo.getMembers()) {
759             if (member.getId().equals(groupMember.getId())) {
760                 return GroupMemberBo.to(member);
761             }
762         }
763         return GroupMemberBo.to(bo);
764     }
765 
766     @Override
767     public void removeAllMembers(String groupId) throws RiceIllegalArgumentException{
768         incomingParamCheck(groupId, "groupId");
769 
770 
771         GroupService groupService = KimApiServiceLocator.getGroupService();
772         List<String> memberPrincipalsBefore = groupService.getMemberPrincipalIds(groupId);
773 
774         Collection<GroupMemberBo> toDeactivate = getActiveGroupMembers(groupId, null, null);
775         java.sql.Timestamp today = new java.sql.Timestamp(System.currentTimeMillis());
776 
777         // Set principals as inactive
778         for (GroupMemberBo aToDeactivate : toDeactivate) {
779             aToDeactivate.setActiveToDateValue(today);
780         }
781 
782         // Save
783         for (GroupMemberBo bo : toDeactivate) {
784             this.dataObjectService.save(bo);
785         }
786         List<String> memberPrincipalsAfter = groupService.getMemberPrincipalIds(groupId);
787 
788         if (!CollectionUtils.isEmpty(memberPrincipalsAfter)) {
789     	    // should never happen!
790     	    LOG.warn("after attempting removal of all members, group with id '" + groupId + "' still has principal members");
791         }
792 
793         // do updates
794         KimImplServiceLocator.getGroupInternalService().updateForWorkgroupChange(groupId, memberPrincipalsBefore,
795                memberPrincipalsAfter);
796     }
797 
798     @Override
799     public boolean removeGroupFromGroup(String childId, String parentId) throws RiceIllegalArgumentException {
800     	incomingParamCheck(childId, "childId");
801         incomingParamCheck(parentId, "parentId");
802 
803         java.sql.Timestamp today = new java.sql.Timestamp(System.currentTimeMillis());
804 
805     	List<GroupMemberBo> groupMembers =
806     		getActiveGroupMembers(parentId, childId, KimConstants.KimGroupMemberTypes.GROUP_MEMBER_TYPE);
807 
808         if(groupMembers.size() == 1) {
809         	GroupMemberBo groupMember = groupMembers.get(0);
810         	groupMember.setActiveToDateValue(today);
811             this.dataObjectService.save(groupMember);
812             return true;
813         }
814 
815         return false;
816     }
817 
818     @Override
819     public boolean removePrincipalFromGroup(String principalId, String groupId) throws RiceIllegalArgumentException {
820     	incomingParamCheck(principalId, "principalId");
821         incomingParamCheck(groupId, "groupId");
822 
823         List<GroupMemberBo> groupMembers =
824     		getActiveGroupMembers(groupId, principalId, KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE);
825 
826         if(groupMembers.size() == 1) {
827         	GroupMemberBo member = groupMembers.iterator().next();
828         	member.setActiveToDateValue(new java.sql.Timestamp(DateTime.now().getMillis()));
829         	this.dataObjectService.save(member);
830             KimImplServiceLocator.getGroupInternalService().updateForUserRemovedFromGroup(member.getMemberId(),
831                     member.getGroupId());
832             return true;
833         }
834 
835         return false;
836     }
837 
838 	protected GroupBo saveGroup(GroupBo group) {
839 		if ( group == null ) {
840 			return null;
841 		} else if (group.getId() != null) {
842 			// Get the version of the group that is in the DB
843 			GroupBo oldGroup = getGroupBo(group.getId());
844 
845 			if (oldGroup != null) {
846 				// Inactivate and re-add members no longer in the group (in order to preserve history).
847 				java.sql.Timestamp activeTo = new java.sql.Timestamp(System.currentTimeMillis());
848 				List<GroupMemberBo> toReAdd = null;
849 
850 				if (oldGroup.getMembers() != null) {
851                     for (GroupMemberBo member : oldGroup.getMembers()) {
852                         // if the old member isn't in the new group
853                         if (group.getMembers() == null || !group.getMembers().contains(member)) {
854                             // inactivate the member
855                             member.setActiveToDateValue(activeTo);
856                             if (toReAdd == null) {
857                                 toReAdd = new ArrayList<GroupMemberBo>();
858                             }
859                             // queue it up for re-adding
860                             toReAdd.add(member);
861                         }
862                     }
863 				}
864 
865 				// do the re-adding
866 				if (toReAdd != null) {
867 					List<GroupMemberBo> groupMembers = group.getMembers();
868 					if (groupMembers == null) {
869                         groupMembers = new ArrayList<GroupMemberBo>(toReAdd.size());
870                     }
871 					group.setMembers(groupMembers);
872 				}
873             }
874 		}
875 
876 		return KimImplServiceLocator.getGroupInternalService().saveWorkgroup(group);
877 	}
878 
879 
880 	/**
881 	 * This helper method gets the active group members of the specified type (see {@link org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes}).
882 	 * If the optional params are null, it will return all active members for the specified group regardless
883 	 * of type.
884 	 *
885 	 * @param parentId
886 	 * @param childId optional, but if provided then memberType must be too
887 	 * @param memberType optional, but must be provided if childId is
888      * @return a list of group members
889 	 */
890 	private List<GroupMemberBo> getActiveGroupMembers(String parentId, String childId, MemberType memberType) {
891     	final java.sql.Date today = new java.sql.Date(System.currentTimeMillis());
892 
893     	if (childId != null && memberType == null) {
894             throw new RiceRuntimeException("memberType must be non-null if childId is non-null");
895         }
896 
897         final QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
898 
899         builder.setPredicates(
900                 and(
901                         equal(KIMPropertyConstants.GroupMember.GROUP_ID, parentId),
902                         HistoryQueryUtils.between(KIMPropertyConstants.KimMember.ACTIVE_FROM_DATE_VALUE,
903                                 KIMPropertyConstants.KimMember.ACTIVE_TO_DATE_VALUE, DateTime.now())));
904 
905         List<Predicate> optionalPredicates = new ArrayList<Predicate>();
906         if (StringUtils.isNotEmpty(childId)) {
907             optionalPredicates.add(equal(KIMPropertyConstants.GroupMember.MEMBER_ID, childId));
908         }
909         if (memberType != null) {
910             optionalPredicates.add(equal(KIMPropertyConstants.GroupMember.MEMBER_TYPE_CODE, memberType.getCode()));
911         }
912         if (CollectionUtils.isNotEmpty(optionalPredicates)) {
913             optionalPredicates.addAll(Arrays.asList(builder.getPredicates()));
914             builder.setPredicates(
915                     and(optionalPredicates.toArray(new Predicate[optionalPredicates.size()])));
916         }
917 
918         QueryResults<GroupMemberBo> groupMembers = this.dataObjectService.findMatching(GroupMemberBo.class, builder.build());
919 
920         /*CollectionUtils.filter(groupMembers, new Predicate() {
921 			@Override public boolean evaluate(Object object) {
922 				GroupMemberBo member = (GroupMemberBo) object;
923 				// keep in the collection (return true) if the activeToDate is null, or if it is set to a future date
924 				return member.getActiveToDate() == null || today.before(member.getActiveToDate().toDate());
925 			}
926 		});*/
927 
928         return new ArrayList<GroupMemberBo>(groupMembers.getResults());
929 	}
930 
931 
932     public void setDataObjectService(final DataObjectService dataObjectService) {
933         this.dataObjectService = dataObjectService;
934     }
935 
936     private void incomingParamCheck(Object object, String name) {
937         if (object == null) {
938             throw new RiceIllegalArgumentException(name + " was null");
939         } else if (object instanceof String
940                 && StringUtils.isBlank((String) object)) {
941             throw new RiceIllegalArgumentException(name + " was blank");
942         }
943     }
944 
945     /**
946      * Returns the list of group members who are currently active and futureActive .
947      * @param groupId
948      * @return
949      */
950     public List<GroupMember> getCurrentAndFutureMembers(String groupId){
951         List<GroupMemberBo> groupMembersBos = getActiveGroupMembers(groupId, null, null);
952         List<GroupMember> groupMembers = new ArrayList<GroupMember>();
953         for (GroupMemberBo groupBo : groupMembersBos) {
954             groupMembers.add(GroupMemberBo.to(groupBo));
955         }
956         return Collections.unmodifiableList(groupMembers);
957     }
958 }