001    /**
002     * Copyright 2005-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kim.impl.group;
017    
018    import org.apache.commons.collections.CollectionUtils;
019    import org.apache.commons.collections.Predicate;
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.log4j.Logger;
022    import org.kuali.rice.core.api.criteria.CriteriaLookupService;
023    import org.kuali.rice.core.api.criteria.GenericQueryResults;
024    import org.kuali.rice.core.api.criteria.LookupCustomizer;
025    import org.kuali.rice.core.api.criteria.QueryByCriteria;
026    import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
027    import org.kuali.rice.core.api.exception.RiceRuntimeException;
028    import org.kuali.rice.core.api.membership.MemberType;
029    import org.kuali.rice.kim.api.KimConstants;
030    import org.kuali.rice.kim.api.group.Group;
031    import org.kuali.rice.kim.api.group.GroupMember;
032    import org.kuali.rice.kim.api.group.GroupMemberQueryResults;
033    import org.kuali.rice.kim.api.group.GroupQueryResults;
034    import org.kuali.rice.kim.api.group.GroupService;
035    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
036    import org.kuali.rice.kim.impl.KIMPropertyConstants;
037    import org.kuali.rice.kim.impl.common.attribute.AttributeTransform;
038    import org.kuali.rice.kim.impl.common.attribute.KimAttributeDataBo;
039    import org.kuali.rice.kim.impl.services.KimImplServiceLocator;
040    import org.kuali.rice.krad.service.BusinessObjectService;
041    
042    import javax.jws.WebParam;
043    import java.sql.Timestamp;
044    import java.util.ArrayList;
045    import java.util.Collection;
046    import java.util.Collections;
047    import java.util.HashMap;
048    import java.util.HashSet;
049    import java.util.List;
050    import java.util.Map;
051    import java.util.Set;
052    
053    import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
054    
055    public class GroupServiceImpl extends GroupServiceBase implements GroupService {
056        private static final Logger LOG = Logger.getLogger(GroupServiceImpl.class);
057    
058        protected BusinessObjectService businessObjectService;
059        private CriteriaLookupService criteriaLookupService;
060    
061        @Override
062        public Group getGroup(String groupId) throws RiceIllegalArgumentException {
063            incomingParamCheck(groupId, "groupId");
064                    return GroupBo.to(getGroupBo(groupId));
065        }
066    
067        @Override
068        public List<Group> getGroupsByPrincipalId(String principalId) throws RiceIllegalArgumentException {
069            incomingParamCheck(principalId,  "principalId");
070            return getGroupsByPrincipalIdAndNamespaceCodeInternal(principalId, null);
071        }
072    
073        @Override
074        public List<Group> getGroupsByPrincipalIdAndNamespaceCode(String principalId, String namespaceCode) throws RiceIllegalArgumentException {
075            incomingParamCheck(principalId, "principalId");
076            incomingParamCheck(namespaceCode, "namespaceCode");
077    
078                    return getGroupsByPrincipalIdAndNamespaceCodeInternal(principalId, namespaceCode);
079        }
080    
081        protected List<Group> getGroupsByPrincipalIdAndNamespaceCodeInternal(String principalId, String namespaceCode) throws RiceIllegalArgumentException {
082    
083            Collection<Group> directGroups = getDirectGroupsForPrincipal( principalId, namespaceCode );
084                    Set<Group> groups = new HashSet<Group>();
085            groups.addAll(directGroups);
086                    for ( Group group : directGroups ) {
087                            groups.add( group );
088                            groups.addAll( getParentGroups( group.getId() ) );
089                    }
090                    return Collections.unmodifiableList(new ArrayList<Group>( groups ));
091        }
092    
093        @Override
094        public List<String> findGroupIds(final QueryByCriteria queryByCriteria) throws RiceIllegalArgumentException {
095            incomingParamCheck(queryByCriteria, "queryByCriteria");
096    
097            GroupQueryResults results = this.findGroups(queryByCriteria);
098            List<String> result = new ArrayList<String>();
099    
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    }