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