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