001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.kim.impl.group;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.collections.Predicate;
020import org.apache.commons.lang.StringUtils;
021import org.apache.log4j.Logger;
022import org.kuali.rice.core.api.criteria.CriteriaLookupService;
023import org.kuali.rice.core.api.criteria.GenericQueryResults;
024import org.kuali.rice.core.api.criteria.LookupCustomizer;
025import org.kuali.rice.core.api.criteria.QueryByCriteria;
026import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
027import org.kuali.rice.core.api.exception.RiceRuntimeException;
028import org.kuali.rice.core.api.membership.MemberType;
029import org.kuali.rice.kim.api.KimConstants;
030import org.kuali.rice.kim.api.group.Group;
031import org.kuali.rice.kim.api.group.GroupMember;
032import org.kuali.rice.kim.api.group.GroupMemberQueryResults;
033import org.kuali.rice.kim.api.group.GroupQueryResults;
034import org.kuali.rice.kim.api.group.GroupService;
035import org.kuali.rice.kim.api.services.KimApiServiceLocator;
036import org.kuali.rice.kim.impl.KIMPropertyConstants;
037import org.kuali.rice.kim.impl.common.attribute.AttributeTransform;
038import org.kuali.rice.kim.impl.common.attribute.KimAttributeDataBo;
039import org.kuali.rice.kim.impl.services.KimImplServiceLocator;
040import org.kuali.rice.krad.service.BusinessObjectService;
041
042import javax.jws.WebParam;
043import java.sql.Timestamp;
044import java.util.ArrayList;
045import java.util.Collection;
046import java.util.Collections;
047import java.util.HashMap;
048import java.util.HashSet;
049import java.util.List;
050import java.util.Map;
051import java.util.Set;
052
053import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
054
055public 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}