Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
BusinessGroupServiceImpl.java 72.14 KiB
/**
 * <a href="http://www.openolat.org">
 * OpenOLAT - Online Learning and Training</a><br>
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); <br>
 * you may not use this file except in compliance with the License.<br>
 * You may obtain a copy of the License at the
 * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
 * <p>
 * Unless required by applicable law or agreed to in writing,<br>
 * software distributed under the License is distributed on an "AS IS" BASIS, <br>
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
 * See the License for the specific language governing permissions and <br>
 * limitations under the License.
 * <p>
 * Initial code contributed and copyrighted by<br>
 * frentix GmbH, http://www.frentix.com
 * <p>
 */
package org.olat.group.manager;

import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.hibernate.ObjectNotFoundException;
import org.hibernate.StaleObjectStateException;
import org.olat.admin.user.delete.service.UserDeletionManager;
import org.olat.basesecurity.BaseSecurity;
import org.olat.basesecurity.BaseSecurityManager;
import org.olat.basesecurity.Constants;
import org.olat.basesecurity.SecurityGroup;
import org.olat.collaboration.CollaborationTools;
import org.olat.collaboration.CollaborationToolsFactory;
import org.olat.core.commons.persistence.DB;
import org.olat.core.commons.taskExecutor.TaskExecutorManager;
import org.olat.core.id.Identity;
import org.olat.core.id.Roles;
import org.olat.core.logging.DBRuntimeException;
import org.olat.core.logging.KnownIssueException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.logging.activity.ActionType;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.async.ProgressDelegate;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.mail.MailContext;
import org.olat.core.util.mail.MailContextImpl;
import org.olat.core.util.mail.MailPackage;
import org.olat.core.util.mail.MailTemplate;
import org.olat.core.util.mail.MailerResult;
import org.olat.core.util.mail.MailerWithTemplate;
import org.olat.core.util.notifications.NotificationsManager;
import org.olat.core.util.notifications.Subscriber;
import org.olat.core.util.resource.OLATResourceableJustBeforeDeletedEvent;
import org.olat.core.util.resource.OresHelper;
import org.olat.course.nodes.projectbroker.service.ProjectBrokerManagerFactory;
import org.olat.group.BusinessGroup;
import org.olat.group.BusinessGroupAddResponse;
import org.olat.group.BusinessGroupMembership;
import org.olat.group.BusinessGroupModule;
import org.olat.group.BusinessGroupOrder;
import org.olat.group.BusinessGroupService;
import org.olat.group.BusinessGroupShort;
import org.olat.group.BusinessGroupView;
import org.olat.group.DeletableGroupData;
import org.olat.group.DeletableReference;
import org.olat.group.GroupLoggingAction;
import org.olat.group.area.BGArea;
import org.olat.group.area.BGAreaManager;
import org.olat.group.manager.BusinessGroupMailing.MailType;
import org.olat.group.model.BGMembership;
import org.olat.group.model.BGRepositoryEntryRelation;
import org.olat.group.model.BGResourceRelation;
import org.olat.group.model.BusinessGroupEnvironment;
import org.olat.group.model.BusinessGroupMembershipChange;
import org.olat.group.model.BusinessGroupMembershipImpl;
import org.olat.group.model.BusinessGroupMembershipViewImpl;
import org.olat.group.model.BusinessGroupMembershipsChanges;
import org.olat.group.model.DisplayMembers;
import org.olat.group.model.EnrollState;
import org.olat.group.model.IdentityGroupKey;
import org.olat.group.model.MembershipModification;
import org.olat.group.model.SearchBusinessGroupParams;
import org.olat.group.right.BGRightManager;
import org.olat.group.right.BGRightsRole;
import org.olat.group.ui.BGMailHelper;
import org.olat.group.ui.edit.BusinessGroupModifiedEvent;
import org.olat.instantMessaging.IMConfigSync;
import org.olat.instantMessaging.InstantMessagingModule;
import org.olat.instantMessaging.syncservice.SyncUserListTask;
import org.olat.properties.Property;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryEntryShort;
import org.olat.repository.RepositoryManager;
import org.olat.repository.SearchRepositoryEntryParameters;
import org.olat.resource.OLATResource;
import org.olat.resource.accesscontrol.ACService;
import org.olat.resource.accesscontrol.manager.ACReservationDAO;
import org.olat.resource.accesscontrol.model.ResourceReservation;
import org.olat.testutils.codepoints.server.Codepoint;
import org.olat.user.UserDataDeletable;
import org.olat.util.logging.activity.LoggingResourceable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


/**
 * 
 * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
 */
@Service("businessGroupService")
public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataDeletable {
	private final OLog log = Tracing.createLoggerFor(BusinessGroupServiceImpl.class);

	@Autowired
	private BGAreaManager areaManager;
	@Autowired
	private BGRightManager rightManager;
	@Autowired
	private BusinessGroupModule groupModule;
	@Autowired
	private BusinessGroupDAO businessGroupDAO;
	@Autowired
	private RepositoryManager repositoryManager;
	@Autowired
	private BaseSecurity securityManager;
	@Autowired
	private BusinessGroupRelationDAO businessGroupRelationDAO;
	@Autowired
	private BusinessGroupImportExport businessGroupImportExport;
	@Autowired
	private BusinessGroupArchiver businessGroupArchiver;
	@Autowired
	private BusinessGroupPropertyDAO businessGroupPropertyManager;
	@Autowired
	private UserDeletionManager userDeletionManager;
	@Autowired
	private NotificationsManager notificationsManager;
	@Autowired
	private MailerWithTemplate mailer;
	@Autowired
	private ACService acService;
	@Autowired
	private ACReservationDAO reservationDao;
	@Autowired
	private DB dbInstance;
	
	private List<DeletableGroupData> deleteListeners = new ArrayList<DeletableGroupData>();

	@PostConstruct
	public void init() {
		userDeletionManager.registerDeletableUserData(this);
	}
	
	@Override
	public void registerDeletableGroupDataListener(DeletableGroupData listener) {
		this.deleteListeners.add(listener);
	}

	@Override
	public List<String> getDependingDeletablableListFor(BusinessGroup currentGroup, Locale locale) {
		List<String> deletableList = new ArrayList<String>();
		for (DeletableGroupData deleteListener : deleteListeners) {
			DeletableReference deletableReference = deleteListener.checkIfReferenced(currentGroup, locale);
			if (deletableReference.isReferenced()) {
				deletableList.add(deletableReference.getName());
			}
		}
		return deletableList;
	}
	
	@Override
	public void deleteUserData(Identity identity, String newDeletedUserName) {
		// remove as Participant 
		List<BusinessGroup> attendedGroups = findBusinessGroupsAttendedBy(identity, null);
		for (Iterator<BusinessGroup> iter = attendedGroups.iterator(); iter.hasNext();) {
			securityManager.removeIdentityFromSecurityGroup(identity, iter.next().getPartipiciantGroup());
		}
		log.debug("Remove partipiciant identity=" + identity + " from " + attendedGroups.size() + " groups");
		// remove from waitinglist 
		List<BusinessGroup> waitingGroups = findBusinessGroupsWithWaitingListAttendedBy(identity, null);
		for (Iterator<BusinessGroup> iter = waitingGroups.iterator(); iter.hasNext();) {
			securityManager.removeIdentityFromSecurityGroup(identity, iter.next().getWaitingGroup());
		}
		log.debug("Remove from waiting-list identity=" + identity + " in " + waitingGroups.size() + " groups");

		// remove as owner
		List<BusinessGroup> ownerGroups = findBusinessGroupsOwnedBy(identity, null);
		for (Iterator<BusinessGroup> iter = ownerGroups.iterator(); iter.hasNext();) {
			BusinessGroup businessGroup = iter.next();
			securityManager.removeIdentityFromSecurityGroup(identity, businessGroup.getOwnerGroup());
			if (securityManager.countIdentitiesOfSecurityGroup(businessGroup.getOwnerGroup()) == 0) {
				securityManager.addIdentityToSecurityGroup(userDeletionManager.getAdminIdentity(), businessGroup.getOwnerGroup());
				log.info("Delete user-data, add Administrator-identity as owner of businessGroup=" + businessGroup.getName());
			}
		}
		log.debug("Remove owner identity=" + identity + " from " + ownerGroups.size() + " groups");
		log.debug("All entries in groups deleted for identity=" + identity);
	}

	@Override
	public BusinessGroup createBusinessGroup(Identity creator, String name, String description,
			Integer minParticipants, Integer maxParticipants, boolean waitingListEnabled, boolean autoCloseRanksEnabled,
			RepositoryEntry re) {

		BusinessGroup group = businessGroupDAO.createAndPersist(creator, name, description,
				minParticipants, maxParticipants, waitingListEnabled, autoCloseRanksEnabled, false, false, false);
		if(re != null) {
			addResourceTo(group, re);
		}
		return group;
	}

	@Override
	@Transactional
	public BusinessGroup updateBusinessGroup(Identity ureqIdentity, BusinessGroup group, String name, String description,
			Integer minParticipants, Integer maxParticipants) {
		
		SyncUserListTask syncIM = new SyncUserListTask(group);
		BusinessGroup bg = businessGroupDAO.loadForUpdate(group.getKey());

		Integer previousMaxParticipants = bg.getMaxParticipants();
		bg.setName(name);
		bg.setDescription(description);
		bg.setMaxParticipants(maxParticipants);
		bg.setMinParticipants(minParticipants);
		bg.setLastUsage(new Date(System.currentTimeMillis()));
		//auto rank if possible
		autoRankCheck(ureqIdentity, bg, previousMaxParticipants, syncIM);
		BusinessGroup updatedGroup = businessGroupDAO.merge(bg);

		syncIM(syncIM, updatedGroup);
		return updatedGroup;
	}

	@Override
	public BusinessGroup updateBusinessGroup(Identity ureqIdentity, BusinessGroup group, String name, String description,
			Integer minParticipants, Integer maxParticipants, Boolean waitingList, Boolean autoCloseRanks) {
		
		SyncUserListTask syncIM = new SyncUserListTask(group);
		BusinessGroup bg = businessGroupDAO.loadForUpdate(group.getKey());
		
		Integer previousMaxParticipants = bg.getMaxParticipants();
		bg.setName(name);
		bg.setDescription(description);
		bg.setMaxParticipants(maxParticipants);
		bg.setMinParticipants(minParticipants);
		bg.setWaitingListEnabled(waitingList);
		if (waitingList != null && waitingList.booleanValue() && bg.getWaitingGroup() == null) {
			// Waitinglist is enabled but not created => Create waitingGroup
			SecurityGroup waitingGroup = securityManager.createAndPersistSecurityGroup();
			bg.setWaitingGroup(waitingGroup);
		}
		bg.setAutoCloseRanksEnabled(autoCloseRanks);
		bg.setLastUsage(new Date(System.currentTimeMillis()));
		//auto rank if possible
		autoRankCheck(ureqIdentity, bg, previousMaxParticipants, syncIM);
		return businessGroupDAO.merge(bg);
	}
	
	private void autoRankCheck(Identity identity, BusinessGroup updatedGroup, Integer previousMaxParticipants, SyncUserListTask syncIM) {
		if(updatedGroup.getWaitingListEnabled() == null || !updatedGroup.getWaitingListEnabled().booleanValue()
				|| updatedGroup.getAutoCloseRanksEnabled() == null || !updatedGroup.getAutoCloseRanksEnabled().booleanValue()) {
			//do not check further, no waiting list, no automatic ranks
			return;
		}
		
		int currentMaxNumber = updatedGroup.getMaxParticipants() == null || updatedGroup.getMaxParticipants().intValue() <= 0
				? -1 : updatedGroup.getMaxParticipants().intValue();
		int previousMaxNumber = previousMaxParticipants == null || previousMaxParticipants.intValue() <= 0
				? -1 : previousMaxParticipants.intValue();
		
		if(currentMaxNumber > previousMaxNumber) {
			//I can rank up some users
			transferFirstIdentityFromWaitingToParticipant(identity, updatedGroup, null, syncIM);
		}
	}

	@Override
	public DisplayMembers getDisplayMembers(BusinessGroup group) {
		Property props = businessGroupPropertyManager.findProperty(group);
		DisplayMembers displayMembers = new DisplayMembers();
		displayMembers.setShowOwners(businessGroupPropertyManager.showOwners(props));
		displayMembers.setShowParticipants(businessGroupPropertyManager.showPartips(props));
		displayMembers.setShowWaitingList(businessGroupPropertyManager.showWaitingList(props));
		displayMembers.setOwnersPublic(businessGroupPropertyManager.isOwnersPublic(props));
		displayMembers.setParticipantsPublic(businessGroupPropertyManager.isPartipsPublic(props));
		displayMembers.setWaitingListPublic(businessGroupPropertyManager.isWaitingListPublic(props));
		displayMembers.setDownloadLists(businessGroupPropertyManager.isDownloadLists(props));
		return displayMembers;
	}

	@Override
	public void updateDisplayMembers(BusinessGroup group, DisplayMembers displayMembers) {
		boolean showOwners = displayMembers.isShowOwners();
		boolean showPartips = displayMembers.isShowParticipants();
		boolean showWaitingList = displayMembers.isShowWaitingList();
		boolean ownersPublic = displayMembers.isOwnersPublic();
		boolean partipsPublic = displayMembers.isParticipantsPublic();
		boolean waitingListPublic = displayMembers.isWaitingListPublic();
		boolean downloadLists = displayMembers.isDownloadLists();
		businessGroupPropertyManager.updateDisplayMembers(group, showOwners, showPartips, showWaitingList,
				ownersPublic, partipsPublic, waitingListPublic, downloadLists);
	}

	@Override
	@Transactional
	public BusinessGroup setLastUsageFor(final Identity identity, final BusinessGroup group) {
		BusinessGroup reloadedBusinessGroup = businessGroupDAO.loadForUpdate(group.getKey());
		reloadedBusinessGroup.setLastUsage(new Date());
		if(identity != null) {
			List<SecurityGroup> secGroups = new ArrayList<SecurityGroup>();
			if(group.getOwnerGroup() != null) {
				secGroups.add(group.getOwnerGroup());
			}
			if(group.getPartipiciantGroup() != null) {
				secGroups.add(group.getPartipiciantGroup());
			}
			if(group.getWaitingGroup() != null) {
				secGroups.add(group.getWaitingGroup());
			}
			securityManager.touchMembership(identity, secGroups);
		}
		return businessGroupDAO.merge(reloadedBusinessGroup);
	}

	@Override
	@Transactional
	public BusinessGroup loadBusinessGroup(BusinessGroup group) {
		return businessGroupDAO.load(group.getKey());
	}

	@Override
	@Transactional
	public BusinessGroup loadBusinessGroup(Long key) {
		return businessGroupDAO.load(key);
	}

	@Override
	@Transactional
	public BusinessGroup loadBusinessGroup(OLATResource resource) {
		return businessGroupDAO.load(resource.getResourceableId());
	}

	@Override
	@Transactional
	public List<BusinessGroup> loadBusinessGroups(Collection<Long> keys) {
		return businessGroupDAO.load(keys);
	}

	@Override
	public List<BusinessGroupShort> loadShortBusinessGroups(Collection<Long> keys) {
		return businessGroupDAO.loadShort(keys);
	}

	@Override
	@Transactional
	public List<BusinessGroup> loadAllBusinessGroups() {
		return businessGroupDAO.loadAll();
	}

	@Override
	@Transactional
	public BusinessGroup copyBusinessGroup(Identity identity, BusinessGroup sourceBusinessGroup, String targetName, String targetDescription,
			Integer targetMin, Integer targetMax,  boolean copyAreas, boolean copyCollabToolConfig, boolean copyRights,
			boolean copyOwners, boolean copyParticipants, boolean copyMemberVisibility, boolean copyWaitingList, boolean copyRelations) {

		// 1. create group, set waitingListEnabled, enableAutoCloseRanks like source business-group
		BusinessGroup newGroup = createBusinessGroup(null, targetName, targetDescription, targetMin, targetMax, 
				sourceBusinessGroup.getWaitingListEnabled(), sourceBusinessGroup.getAutoCloseRanksEnabled(), null);
		// return immediately with null value to indicate an already take groupname
		if (newGroup == null) { 
			return null;
		}
		// 2. copy tools
		if (copyCollabToolConfig) {
			CollaborationToolsFactory toolsF = CollaborationToolsFactory.getInstance();
			// get collab tools from original group and the new group
			CollaborationTools oldTools = toolsF.getOrCreateCollaborationTools(sourceBusinessGroup);
			CollaborationTools newTools = toolsF.getOrCreateCollaborationTools(newGroup);
			// copy the collab tools settings
			for (int i = 0; i < CollaborationTools.TOOLS.length; i++) {
				String tool = CollaborationTools.TOOLS[i];
				newTools.setToolEnabled(tool, oldTools.isToolEnabled(tool));
			}			
			String oldNews = oldTools.lookupNews();
			newTools.saveNews(oldNews);
		}
		// 3. copy member visibility
		if (copyMemberVisibility) {
			businessGroupPropertyManager.copyConfigurationFromGroup(sourceBusinessGroup, newGroup);
		}
		// 4. copy areas
		if (copyAreas) {
			List<BGArea> areas = areaManager.findBGAreasOfBusinessGroup(sourceBusinessGroup);
			for(BGArea area : areas) {
				// reference target group to source groups areas
				areaManager.addBGToBGArea(newGroup, area);
			}
		}
		// 5. copy owners
		if (copyOwners) {
			List<Identity> owners = securityManager.getIdentitiesOfSecurityGroup(sourceBusinessGroup.getOwnerGroup());
			if(owners.isEmpty()) {
				securityManager.addIdentityToSecurityGroup(identity, newGroup.getOwnerGroup());
			} else {
				for (Identity owner:owners) {
					securityManager.addIdentityToSecurityGroup(owner, newGroup.getOwnerGroup());
				}
			}
		} else {
			securityManager.addIdentityToSecurityGroup(identity, newGroup.getOwnerGroup());
		}
		// 6. copy participants
		if (copyParticipants) {
			List<Identity> participants = securityManager.getIdentitiesOfSecurityGroup(sourceBusinessGroup.getPartipiciantGroup());
			for(Identity participant:participants) {
				securityManager.addIdentityToSecurityGroup(participant, newGroup.getPartipiciantGroup());
			}
		}
		// 7. copy rights
		if (copyRights) {
			List<String> participantRights = rightManager.findBGRights(sourceBusinessGroup, BGRightsRole.participant);
			for (String sourceRight:participantRights) {
				rightManager.addBGRight(sourceRight, newGroup, BGRightsRole.participant);
			}
			List<String> tutorRights = rightManager.findBGRights(sourceBusinessGroup, BGRightsRole.tutor);
			for (String sourceRight:tutorRights) {
				rightManager.addBGRight(sourceRight, newGroup, BGRightsRole.tutor);
			}
			
		}
		// 8. copy waiting-lisz
		if (copyWaitingList) {
			List<Identity> waitingList = securityManager.getIdentitiesOfSecurityGroup(sourceBusinessGroup.getWaitingGroup());
			for (Identity waiting:waitingList) {
				securityManager.addIdentityToSecurityGroup(waiting, newGroup.getWaitingGroup());
			}
		}
		//9. copy relations
		if(copyRelations) {
			List<OLATResource> resources = businessGroupRelationDAO.findResources(Collections.singletonList(sourceBusinessGroup), 0, -1);
			for(OLATResource resource:resources) {
				businessGroupRelationDAO.addRelationToResource(newGroup, resource);
			}	
		}
		return newGroup;
	}

	@Override
	public BusinessGroup mergeBusinessGroups(final Identity ureqIdentity, BusinessGroup targetGroup,
			final List<BusinessGroup> groupsToMerge, MailPackage mailing) {
		groupsToMerge.remove(targetGroup);//to be sure
		SyncUserListTask syncIM = new SyncUserListTask(targetGroup);
		Roles ureqRoles = securityManager.getRoles(ureqIdentity);

		targetGroup = businessGroupDAO.loadForUpdate(targetGroup.getKey());
		Set<Identity> currentOwners
			= new HashSet<Identity>(securityManager.getIdentitiesOfSecurityGroup(targetGroup.getOwnerGroup()));
		Set<Identity> currentParticipants 
			= new HashSet<Identity>(securityManager.getIdentitiesOfSecurityGroup(targetGroup.getPartipiciantGroup()));
		Set<Identity> currentWaiters
			= new HashSet<Identity>(securityManager.getIdentitiesOfSecurityGroup(targetGroup.getWaitingGroup()));

		Set<Identity> newOwners = new HashSet<Identity>();
		Set<Identity> newParticipants = new HashSet<Identity>();
		Set<Identity> newWaiters = new HashSet<Identity>();
		
		//collect the owners
		for(BusinessGroup group:groupsToMerge) {
			List<Identity> owners = securityManager.getIdentitiesOfSecurityGroup(group.getOwnerGroup());
			owners.removeAll(currentOwners);
			newOwners.addAll(owners);
		}
		
		//collect the participants but test if they are not already owners
		for(BusinessGroup group:groupsToMerge) {
			List<Identity> participants = securityManager.getIdentitiesOfSecurityGroup(group.getPartipiciantGroup());
			participants.removeAll(currentParticipants);
			for(Identity participant:participants) {
				if(!newOwners.contains(participant)) {
					newParticipants.add(participant);
				}
			}
		}
		
		//collect the waiting list but test if they are not already owners or participants
		for(BusinessGroup group:groupsToMerge) {
			List<Identity> waitingList = securityManager.getIdentitiesOfSecurityGroup(group.getWaitingGroup());
			waitingList.removeAll(currentWaiters);
			for(Identity waiter:waitingList) {
				if(!newOwners.contains(waiter) && !newParticipants.contains(waiter)) {
					newWaiters.add(waiter);
				}
			}
		}
		
		for(Identity newOwner:newOwners) {
			addOwner(ureqIdentity, ureqRoles, newOwner, targetGroup, mailing, syncIM);
		}
		for(Identity newParticipant:newParticipants) {
			addParticipant(ureqIdentity, ureqRoles, newParticipant, targetGroup, mailing, syncIM);
		}
		for(Identity newWaiter:newWaiters) {
			addToWaitingList(ureqIdentity, newWaiter, targetGroup, mailing);
		}
			
		syncIM(syncIM, targetGroup);
		for(BusinessGroup group:groupsToMerge) {
			deleteBusinessGroup(group);
		}
		return targetGroup;
	}

	@Override
	public void updateMembership(Identity ureqIdentity, MembershipModification membersMod,
			List<BusinessGroup> groups, MailPackage mailing) {
		Roles ureqRoles = securityManager.getRoles(ureqIdentity);
		for(BusinessGroup group:groups) {
			updateMembers(ureqIdentity, ureqRoles, membersMod, group, mailing);
		}
	}
	
	private void updateMembers(Identity ureqIdentity, Roles ureqRoles, MembershipModification membersMod,
			BusinessGroup group, MailPackage mailing) {
		final SyncUserListTask syncIM = new SyncUserListTask(group);
		
		group = businessGroupDAO.loadForUpdate(group.getKey());
		
		List<Identity> currentOwners = securityManager.getIdentitiesOfSecurityGroup(group.getOwnerGroup());
		List<Identity> currentParticipants = securityManager.getIdentitiesOfSecurityGroup(group.getPartipiciantGroup());
		List<Identity> currentWaitingList = securityManager.getIdentitiesOfSecurityGroup(group.getWaitingGroup());

		for(Identity owner:membersMod.getAddOwners()) {
			if(!currentOwners.contains(owner)) {
				addOwner(ureqIdentity, ureqRoles, owner, group, mailing, syncIM);
			}
		}
		for(Identity participant:membersMod.getAddParticipants()) {
			if(!currentParticipants.contains(participant)) {
				addParticipant(ureqIdentity, ureqRoles, participant, group, mailing, syncIM);
			}
		}
		for(Identity waitingIdentity:membersMod.getAddToWaitingList()) {
			if(!currentWaitingList.contains(waitingIdentity)) {
				addToWaitingList(ureqIdentity, waitingIdentity, group, mailing);
			}
		}
		
		//remove owners
		List<Identity> ownerToRemove = new ArrayList<Identity>();
		for(Identity removed:membersMod.getRemovedIdentities()) {
			if(currentOwners.contains(removed)) {
				ownerToRemove.add(removed);
			}
			if(currentParticipants.contains(removed)) {
				removeParticipant(ureqIdentity, removed, group, mailing, syncIM);
			}
			if(currentWaitingList.contains(removed)) {
				removeFromWaitingList(ureqIdentity, removed, group, mailing);
			}
		}
		removeOwners(ureqIdentity, ownerToRemove, group);
		
		//release lock
		dbInstance.commit();
		
		syncIM(syncIM, group);
	}

	@Override
	@Transactional
	public void updateMemberships(final Identity ureqIdentity, final List<BusinessGroupMembershipChange> changes,
			MailPackage mailing) {
		Roles ureqRoles = securityManager.getRoles(ureqIdentity);
		Map<Long,BusinessGroupMembershipsChanges> changesMap = new HashMap<Long,BusinessGroupMembershipsChanges>();
		for(BusinessGroupMembershipChange change:changes) {
			BusinessGroupMembershipsChanges changesWrapper;
			if(changesMap.containsKey(change.getGroupKey())) {
				changesWrapper = changesMap.get(change.getGroupKey());
			} else {
				changesWrapper = new BusinessGroupMembershipsChanges();
				changesMap.put(change.getGroupKey(), changesWrapper);
			}
			
			Identity id = change.getMember();
			if(change.getTutor() != null) {
				if(change.getTutor().booleanValue()) {
					changesWrapper.addTutors.add(id);
				} else {
					changesWrapper.removeTutors.add(id);
				}
			}
			
			if(change.getParticipant() != null) {
				if(change.getParticipant().booleanValue()) {
					changesWrapper.addParticipants.add(id);
				} else {
					changesWrapper.removeParticipants.add(id);
				}	
			}
			
			if(change.getWaitingList() != null) {
				if(change.getWaitingList().booleanValue()) {
					changesWrapper.addToWaitingList.add(id);
				} else {
					changesWrapper.removeFromWaitingList.add(id);
				}
			}
		}
		
		List<BusinessGroup> groups = loadBusinessGroups(changesMap.keySet());
		for(BusinessGroup group:groups) {
			BusinessGroupMembershipsChanges changesWrapper = changesMap.get(group.getKey());
			SyncUserListTask syncIM = new SyncUserListTask(group);
			group = businessGroupDAO.loadForUpdate(group.getKey());
					
			for(Identity id:changesWrapper.addToWaitingList) {
				addToWaitingList(ureqIdentity, id, group, mailing);
			}
			for(Identity id:changesWrapper.removeFromWaitingList) {
				removeFromWaitingList(ureqIdentity, id, group, mailing);
			}
			for(Identity id:changesWrapper.addTutors) {
				addOwner(ureqIdentity, ureqRoles, id, group, mailing, syncIM);
			}
			for(Identity id:changesWrapper.removeTutors) {
				removeOwner(ureqIdentity, id, group, syncIM);
			}
			for(Identity id:changesWrapper.addParticipants) {
				addParticipant(ureqIdentity, ureqRoles, id, group, mailing, syncIM);
			}
			for(Identity id:changesWrapper.removeParticipants) {
				removeParticipant(ureqIdentity, id, group, mailing, syncIM);
			}
			//release lock
			dbInstance.commit();
			
			syncIM(syncIM, group);
		}
	}

	@Override
	@Transactional
	public BusinessGroup findBusinessGroup(SecurityGroup secGroup) {
		return businessGroupDAO.findBusinessGroup(secGroup);
	}

	@Override
	@Transactional(readOnly=true)
	public List<BusinessGroup> findBusinessGroupsOwnedBy(Identity identity, OLATResource resource) {
		SearchBusinessGroupParams params = new SearchBusinessGroupParams(identity, true, false);
		return businessGroupDAO.findBusinessGroups(params, resource, 0, -1);
	}
	
	@Override
	@Transactional(readOnly=true)
	public List<BusinessGroup> findBusinessGroupsAttendedBy(Identity identity, OLATResource resource) {
		SearchBusinessGroupParams params = new SearchBusinessGroupParams(identity, false, true);
		return businessGroupDAO.findBusinessGroups(params, resource, 0, -1);
	}
	
	@Override
	public List<BusinessGroup> findBusinessGroupsWithWaitingListAttendedBy(Identity identity,  OLATResource resource) {
		return businessGroupDAO.findBusinessGroupsWithWaitingListAttendedBy(identity, resource);
	}
	
	@Override
	@Transactional(readOnly=true)
	public int countBusinessGroups(SearchBusinessGroupParams params, OLATResource resource) {
		if(params == null) {
			params = new SearchBusinessGroupParams();
		}
		return businessGroupDAO.countBusinessGroups(params, resource);
	}

	@Override
	@Transactional(readOnly=true)
	public List<BusinessGroup> findBusinessGroups(SearchBusinessGroupParams params, OLATResource resource,
			int firstResult, int maxResults, BusinessGroupOrder... ordering) {
		if(params == null) {
			params = new SearchBusinessGroupParams();
		}
		return businessGroupDAO.findBusinessGroups(params, resource, firstResult, maxResults);
	}
	@Override
	@Transactional(readOnly=true)
	public int countBusinessGroupViews(SearchBusinessGroupParams params, OLATResource resource) {
		if(params == null) {
			params = new SearchBusinessGroupParams();
		}
		return businessGroupDAO.countBusinessGroupViews(params, resource);
	}

	@Override
	@Transactional(readOnly=true)
	public List<BusinessGroupView> findBusinessGroupViews(SearchBusinessGroupParams params, OLATResource resource, int firstResult,
			int maxResults, BusinessGroupOrder... ordering) {
		if(params == null) {
			params = new SearchBusinessGroupParams();
		}
		return businessGroupDAO.findBusinessGroupViews(params, resource, firstResult, maxResults);
	}

	@Override
	@Transactional(readOnly=true)
	public List<BusinessGroupView> findBusinessGroupViewsWithAuthorConnection(Identity author) {
		return businessGroupDAO.findBusinessGroupWithAuthorConnection(author);
	}

	@Override
	@Transactional(readOnly=true)
	public List<Long> toGroupKeys(String groupNames, OLATResource resource) {
		return businessGroupRelationDAO.toGroupKeys(groupNames, resource);
	}

	@Override
	@Transactional(readOnly=true)
	public int countContacts(Identity identity) {
		return businessGroupDAO.countContacts(identity);
	}

	@Override
	@Transactional(readOnly=true)
	public List<Identity> findContacts(Identity identity, int firstResult, int maxResults) {
		return businessGroupDAO.findContacts(identity, firstResult, maxResults);
	}

	@Override
	public void deleteBusinessGroup(BusinessGroup group) {
		try{
			OLATResourceableJustBeforeDeletedEvent delEv = new OLATResourceableJustBeforeDeletedEvent(group);
			// notify all (currently running) BusinessGroupXXXcontrollers
			// about the deletion which will occur.
			CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(delEv, group);
	
			// refresh object to avoid stale object exceptions
			group = loadBusinessGroup(group);
			// 0) Loop over all deletableGroupData
			for (DeletableGroupData deleteListener : deleteListeners) {
				if(log.isDebug()) {
					log.debug("deleteBusinessGroup: call deleteListener=" + deleteListener);
				}
				deleteListener.deleteGroupDataFor(group);
			} 
			
			// 0) Delete from project broker 
			ProjectBrokerManagerFactory.getProjectBrokerManager().deleteGroupDataFor(group);
			// 1) Delete all group properties
			
			CollaborationTools ct = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(group);
			ct.deleteTools(group);// deletes everything concerning properties&collabTools
			
			// 1.b)delete display member property
			businessGroupPropertyManager.deleteDisplayMembers(group);
			// 1.c)delete user in security groups
			//removeFromRepositoryEntrySecurityGroup(group);
			// 2) Delete the group areas
			areaManager.deleteBGtoAreaRelations(group);
			// 3) Delete the group object itself on the database
			businessGroupRelationDAO.deleteRelations(group);
			businessGroupDAO.delete(group);
			// 4) Delete the associated security groups
			if(group.getOwnerGroup() != null) {
				securityManager.deleteSecurityGroup(group.getOwnerGroup());
			}
			// in all cases the participant groups
			if(group.getPartipiciantGroup() != null) {
				securityManager.deleteSecurityGroup(group.getPartipiciantGroup());
			}
			// Delete waiting-group when one exists
			if (group.getWaitingGroup() != null) {
				securityManager.deleteSecurityGroup(group.getWaitingGroup());
			}
	
			// delete the publisher attached to this group (e.g. the forum and folder
			// publisher)
			notificationsManager.deletePublishersOf(group);
	
			// delete potential jabber group roster
			if (InstantMessagingModule.isEnabled()) {
				String groupID = InstantMessagingModule.getAdapter().createChatRoomString(group);
				InstantMessagingModule.getAdapter().deleteRosterGroup(groupID);
			}
			log.audit("Deleted Business Group", group.toString());
		} catch(DBRuntimeException dbre) {
			Throwable th = dbre.getCause();
			if ((th instanceof ObjectNotFoundException) && th.getMessage().contains("org.olat.group.BusinessGroupImpl")) {
				//group already deleted
				return;
			}
			if ((th instanceof StaleObjectStateException) &&
					(th.getMessage().startsWith("Row was updated or deleted by another transaction"))) {
				// known issue OLAT-3654
				log.info("Group was deleted by another user in the meantime. Known issue OLAT-3654");
				throw new KnownIssueException("Group was deleted by another user in the meantime", 3654);
			} else {
				throw dbre;
			}
		}
	}

	@Override
	public MailerResult deleteBusinessGroupWithMail(BusinessGroup businessGroupTodelete, String businessPath, Identity deletedBy, Locale locale) {
		Codepoint.codepoint(this.getClass(), "deleteBusinessGroupWithMail");
			
		// collect data for mail
		BaseSecurity secMgr = BaseSecurityManager.getInstance();
		List<Identity> users = new ArrayList<Identity>();
		SecurityGroup ownerGroup = businessGroupTodelete.getOwnerGroup();
		if (ownerGroup != null) {
			List<Identity> owner = secMgr.getIdentitiesOfSecurityGroup(ownerGroup);
			users.addAll(owner);
		}
		SecurityGroup partGroup = businessGroupTodelete.getPartipiciantGroup();
		if (partGroup != null) {
			List<Identity> participants = secMgr.getIdentitiesOfSecurityGroup(partGroup);
			users.addAll(participants);
		}
		SecurityGroup watiGroup = businessGroupTodelete.getWaitingGroup();
		if (watiGroup != null) {
			List<Identity> waiting = secMgr.getIdentitiesOfSecurityGroup(watiGroup);
			users.addAll(waiting);
		}
		// now delete the group first
		deleteBusinessGroup(businessGroupTodelete);
		// finally send email
		MailerWithTemplate mailer = MailerWithTemplate.getInstance();
		MailTemplate mailTemplate = BGMailHelper.createDeleteGroupMailTemplate(businessGroupTodelete, deletedBy);
		if (mailTemplate != null) {
			//fxdiff VCRP-16: intern mail system
			MailContext context = new MailContextImpl(businessPath);
			MailerResult mailerResult = mailer.sendMailAsSeparateMails(context, users, null, mailTemplate, null);
			//MailHelper.printErrorsAndWarnings(mailerResult, wControl, locale);
			return mailerResult;
		}
		return null;
	}

	@Override
	public int countMembersOf(OLATResource resource, boolean owner, boolean attendee) {
		return businessGroupRelationDAO.countMembersOf(resource, owner, attendee);
	}

	@Override
	public List<Identity> getMembersOf(OLATResource resource, boolean owner, boolean attendee) {
		return businessGroupRelationDAO.getMembersOf(resource, owner, attendee);
	}

	@Override
	public BusinessGroupAddResponse addOwners(Identity ureqIdentity, Roles ureqRoles, List<Identity> addIdentities,
			BusinessGroup group, MailPackage mailing) {
		SyncUserListTask syncIM = new SyncUserListTask(group);
		BusinessGroupAddResponse response = new BusinessGroupAddResponse();
		for (Identity identity : addIdentities) {
			group = loadBusinessGroup(group); // reload business group
			if (securityManager.isIdentityPermittedOnResourceable(identity, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_GUESTONLY)) {
				response.getIdentitiesWithoutPermission().add(identity);
			} else if(addOwner(ureqIdentity, ureqRoles, identity, group, mailing, syncIM)) {
				response.getAddedIdentities().add(identity);
				log.audit("added identity '" + identity.getName() + "' to securitygroup with key " + group.getOwnerGroup().getKey());
			} else {
				response.getIdentitiesAlreadyInGroup().add(identity);
			}
		}
		syncIM(syncIM, group);
		return response;
	}
	
	private boolean addOwner(Identity ureqIdentity, Roles ureqRoles, Identity identityToAdd, BusinessGroup group, MailPackage mailing, SyncUserListTask syncIM) {
		if (!securityManager.isIdentityInSecurityGroup(identityToAdd, group.getOwnerGroup())) {
			boolean mustAccept = true;
			if(ureqIdentity != null && ureqIdentity.equals(identityToAdd)) {
				mustAccept = false;//adding itself, we hope that he knows what he makes
			} else if(ureqRoles == null || ureqIdentity == null) {
				mustAccept = false;//administrative task
			} else {
				mustAccept = groupModule.isAcceptMembership(ureqRoles);
			}

			if(mustAccept) {
				ResourceReservation olderReservation = reservationDao.loadReservation(identityToAdd, group.getResource());
				if(olderReservation == null) {
					Calendar cal = Calendar.getInstance();
					cal.add(Calendar.MONTH, 6);
					Date expiration = cal.getTime();
					ResourceReservation reservation =
							reservationDao.createReservation(identityToAdd, "group_coach", expiration, group.getResource());
					if(reservation != null) {
						BusinessGroupMailing.sendEmail(ureqIdentity, identityToAdd, group, MailType.addCoach, mailing, mailer);
					}
				}
			} else {
				internalAddCoach(identityToAdd, group, syncIM);
				BusinessGroupMailing.sendEmail(ureqIdentity, identityToAdd, group, MailType.addCoach, mailing, mailer);
			}
			return true;
		}
		return false;
	}
	
	private void internalAddCoach(Identity identityToAdd, BusinessGroup group, SyncUserListTask syncIM) {
		securityManager.addIdentityToSecurityGroup(identityToAdd, group.getOwnerGroup());
		// add user to buddies rosters
		if(syncIM != null) {
			syncIM.addUserToAdd(identityToAdd.getName());
		}
		// notify currently active users of this business group
		BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT, group, identityToAdd);
		// do logging
		ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_OWNER_ADDED, getClass(), LoggingResourceable.wrap(group), LoggingResourceable.wrap(identityToAdd));
	}
	
	private boolean addParticipant(Identity ureqIdentity, Roles ureqRoles, Identity identityToAdd, BusinessGroup group,
			MailPackage mailing, SyncUserListTask syncIM) {
		
		if(!securityManager.isIdentityInSecurityGroup(identityToAdd, group.getPartipiciantGroup())) {
			boolean mustAccept = true;
			if(ureqIdentity != null && ureqIdentity.equals(identityToAdd)) {
				mustAccept = false;//adding itself, we hope that he knows what he makes
			} else if(ureqRoles == null || ureqIdentity == null) {
				mustAccept = false;//administrative task
			} else {
				mustAccept = groupModule.isAcceptMembership(ureqRoles);
			}
			
			if(mustAccept) {
				ResourceReservation olderReservation = reservationDao.loadReservation(identityToAdd, group.getResource());
				if(olderReservation == null) {
					Calendar cal = Calendar.getInstance();
					cal.add(Calendar.MONTH, 6);
					Date expiration = cal.getTime();
					ResourceReservation reservation =
							reservationDao.createReservation(identityToAdd, "group_participant", expiration, group.getResource());
					if(reservation != null) {
						BusinessGroupMailing.sendEmail(ureqIdentity, identityToAdd, group, MailType.addParticipant, mailing, mailer);
					}
				}
			} else {
				internalAddParticipant(identityToAdd, group, syncIM);
				BusinessGroupMailing.sendEmail(ureqIdentity, identityToAdd, group, MailType.addParticipant, mailing, mailer);
			}
			return true;
		}
		return false;
	}
	
	/**
	 * this method is for internal usage only. It add the identity to to group without synchronization or checks!
	 * @param ureqIdentity
	 * @param ureqRoles
	 * @param identityToAdd
	 * @param group
	 * @param syncIM
	 */
	private void internalAddParticipant(Identity identityToAdd, BusinessGroup group, SyncUserListTask syncIM) {
		securityManager.addIdentityToSecurityGroup(identityToAdd, group.getPartipiciantGroup());
		
		// add user to buddies rosters
		if(syncIM != null) {
			syncIM.addUserToAdd(identityToAdd.getName());
		}
		
		// notify currently active users of this business group
		BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT, group, identityToAdd);
		// do logging
		ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_PARTICIPANT_ADDED, getClass(), LoggingResourceable.wrap(group), LoggingResourceable.wrap(identityToAdd));
		// send notification mail in your controller!
	}

	@Override
	@Transactional
	public BusinessGroupAddResponse addParticipants(Identity ureqIdentity, Roles ureqRoles, List<Identity> addIdentities,
			BusinessGroup group, MailPackage mailing) {	
		BusinessGroupAddResponse response = new BusinessGroupAddResponse();
		SyncUserListTask syncIM = new SyncUserListTask(group);

		BusinessGroup currBusinessGroup = businessGroupDAO.loadForUpdate(group.getKey());	
		for (final Identity identity : addIdentities) {
			if (securityManager.isIdentityPermittedOnResourceable(identity, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_GUESTONLY)) {
				response.getIdentitiesWithoutPermission().add(identity);
			} else if(addParticipant(ureqIdentity, ureqRoles, identity, currBusinessGroup, mailing, syncIM)) {
				response.getAddedIdentities().add(identity);
				log.audit("added identity '" + identity.getName() + "' to securitygroup with key " + currBusinessGroup.getPartipiciantGroup().getKey());
			} else {
				response.getIdentitiesAlreadyInGroup().add(identity);
			}
		}

		syncIM(syncIM, group);
		return response;
	}

	@Override
	@Transactional
	public void acceptPendingParticipation(Identity ureqIdentity, Identity identityToAdd, OLATResource resource) {
		ResourceReservation reservation = acService.getReservation(identityToAdd, resource);
		if(reservation != null && "BusinessGroup".equals(resource.getResourceableTypeName())) {
			BusinessGroup group = businessGroupDAO.loadForUpdate(resource.getResourceableId());
			if(!securityManager.isIdentityInSecurityGroup(identityToAdd, group.getPartipiciantGroup())) {
				SyncUserListTask syncIM = new SyncUserListTask(group);
				internalAddParticipant(identityToAdd, group, syncIM);
				syncIM(syncIM, group);
			}
			reservationDao.deleteReservation(reservation);
		}
	}

	private void removeParticipant(Identity ureqIdentity, Identity identity, BusinessGroup group, MailPackage mailing, SyncUserListTask syncIM) {

		boolean removed = securityManager.removeIdentityFromSecurityGroup(identity, group.getPartipiciantGroup());
		if(removed) {
			// remove user from buddies rosters
			syncIM.addUserToRemove(identity.getName());
			//remove subscriptions if user gets removed
			removeSubscriptions(identity, group);
			
			// notify currently active users of this business group
			BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, group, identity);
			// do logging
			ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_PARTICIPANT_REMOVED, getClass(), LoggingResourceable.wrap(identity), LoggingResourceable.wrap(group));
			// Check if a waiting-list with auto-close-ranks is configurated
			if ( group.getWaitingListEnabled().booleanValue() && group.getAutoCloseRanksEnabled().booleanValue() ) {
				// even when doOnlyPostRemovingStuff is set to true we really transfer the first Identity here
				transferFirstIdentityFromWaitingToParticipant(ureqIdentity, group, null, syncIM);
			}	
			// send mail
			BusinessGroupMailing.sendEmail(ureqIdentity, identity, group, MailType.removeParticipant, mailing, mailer);
		}
	}
	
	@Override
	@Transactional
	public void removeParticipants(Identity ureqIdentity, List<Identity> identities, BusinessGroup group, MailPackage mailing) {
		final SyncUserListTask syncIM = new SyncUserListTask(group);
		group = businessGroupDAO.loadForUpdate(group.getKey());
		for (Identity identity : identities) {
		  removeParticipant(ureqIdentity, identity, group, mailing, syncIM);
		  log.audit("removed identiy '" + identity.getName() + "' from securitygroup with key " + group.getPartipiciantGroup().getKey());
		}
		syncIM(syncIM, group);
	}

	@Override
	public void removeMembers(Identity ureqIdentity, List<Identity> identities, OLATResource resource, MailPackage mailing) {
		if(identities == null || identities.isEmpty() || resource == null) return;//nothing to do
		
		List<BusinessGroup> groups = null;
		if("BusinessGroup".equals(resource.getResourceableTypeName())) {
			//it's a group resource
			BusinessGroup group = loadBusinessGroup(resource);
			if(group != null) {
				groups = Collections.singletonList(group);
			}
		} else {	
			groups = findBusinessGroups(null, resource, 0, -1);
		}
		if(groups == null || groups.isEmpty()) {
			return;//nothing to do
		}

		List<OLATResource> groupResources = new ArrayList<OLATResource>();
		Map<Long,BusinessGroup> keyToGroupMap = new HashMap<Long,BusinessGroup>();
		for(BusinessGroup group:groups) {
			groupResources.add(group.getResource());
			keyToGroupMap.put(group.getKey(), group);
		}
		final Map<Long,Identity> keyToIdentityMap = new HashMap<Long,Identity>();
		for(Identity identity:identities) {
			keyToIdentityMap.put(identity.getKey(), identity);
		}
		
		List<BusinessGroupMembershipViewImpl> memberships = businessGroupDAO.getMembershipInfoInBusinessGroups(groups, identities);
		Collections.sort(memberships, new BusinessGroupMembershipViewComparator());

		BusinessGroupMembershipViewImpl nextGroupMembership = null;
		for(final Iterator<BusinessGroupMembershipViewImpl> itMembership=memberships.iterator(); nextGroupMembership != null || itMembership.hasNext(); ) {
			final BusinessGroupMembershipViewImpl currentMembership;
			if(nextGroupMembership == null) {
				currentMembership = itMembership.next();
			} else {
				currentMembership = nextGroupMembership;
				nextGroupMembership = null;
			}
			
			Long groupKey = currentMembership.getGroupKey();
			BusinessGroup nextGroup = businessGroupDAO.loadForUpdate(groupKey);
			SyncUserListTask syncIM = new SyncUserListTask(nextGroup);
			nextGroupMembership = removeGroupMembers(ureqIdentity, currentMembership, nextGroup, keyToIdentityMap, itMembership, mailing, syncIM);
			//release the lock
			dbInstance.commit();
			syncIM(syncIM, nextGroup);
		}

		List<ResourceReservation> reservations = acService.getReservations(groupResources);
		for(ResourceReservation reservation:reservations) {
			if(identities.contains(reservation.getIdentity())) {
				reservationDao.deleteReservation(reservation);
			}
		}
	}
	
	private final BusinessGroupMembershipViewImpl removeGroupMembers(Identity ureqIdentity, BusinessGroupMembershipViewImpl currentMembership,
			BusinessGroup currentGroup, Map<Long,Identity> keyToIdentityMap, Iterator<BusinessGroupMembershipViewImpl> itMembership,
			MailPackage mailing, SyncUserListTask syncIM) {

		BusinessGroupMembershipViewImpl previsousComputedMembership = currentMembership;
		BusinessGroupMembershipViewImpl membership;

		do {
			if(previsousComputedMembership != null) {
				membership = previsousComputedMembership;
				previsousComputedMembership = null;
			} else if(itMembership.hasNext()) {
				membership = itMembership.next();
			} else {
				//security, nothing to do
				return null;
			}
			
			if(currentGroup.getKey().equals(membership.getGroupKey())) {
				Identity id = keyToIdentityMap.get(membership.getIdentityKey());
				if(membership.getOwnerGroupKey() != null) {
					removeOwner(ureqIdentity, id, currentGroup, syncIM);
				}
				if(membership.getParticipantGroupKey() != null) {
					removeParticipant(ureqIdentity, id, currentGroup, mailing, syncIM);
				}
				if(membership.getWaitingGroupKey() != null) {
					removeFromWaitingList(ureqIdentity, id, currentGroup, mailing);
				}
			} else {
				return membership;
			}
		} while (itMembership.hasNext());

		return null;
	}

	private void addToWaitingList(Identity ureqIdentity, Identity identity, BusinessGroup group, MailPackage mailing) {
		securityManager.addIdentityToSecurityGroup(identity, group.getWaitingGroup());

		// notify currently active users of this business group
		BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT, group, identity);
		// do logging
		ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_TO_WAITING_LIST_ADDED, getClass(), LoggingResourceable.wrap(identity));
		// send mail
		BusinessGroupMailing.sendEmail(ureqIdentity, identity, group, MailType.addToWaitingList, mailing, mailer);
	}
	
	@Override
	public BusinessGroupAddResponse addToWaitingList(Identity ureqIdentity, List<Identity> addIdentities, BusinessGroup group, MailPackage mailing) {
		BusinessGroupAddResponse response = new BusinessGroupAddResponse();
		BusinessGroup currBusinessGroup = businessGroupDAO.loadForUpdate(group.getKey()); // reload business group

		for (final Identity identity : addIdentities) {	
			if (securityManager.isIdentityPermittedOnResourceable(identity, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_GUESTONLY)) {
				response.getIdentitiesWithoutPermission().add(identity);
			}
			// Check if identity is already in group. make a db query in case
			// someone in another workflow already added this user to this group. if
			// found, add user to model
			else if (securityManager.isIdentityInSecurityGroup(ureqIdentity, currBusinessGroup.getWaitingGroup()) 
					|| securityManager.isIdentityInSecurityGroup(ureqIdentity, currBusinessGroup.getPartipiciantGroup()) ) {
				response.getIdentitiesAlreadyInGroup().add(identity);
			} else {
				// identity has permission and is not already in group => add it
				addToWaitingList(ureqIdentity, identity, currBusinessGroup, mailing);
				response.getAddedIdentities().add(identity);
				log.audit("added identity '" + identity.getName() + "' to securitygroup with key " + currBusinessGroup.getPartipiciantGroup().getKey());
			}
		}
		return response;
	}
	private final void removeFromWaitingList(Identity ureqIdentity, Identity identity, BusinessGroup group, MailPackage mailing) {
		securityManager.removeIdentityFromSecurityGroup(identity, group.getWaitingGroup());
		// notify currently active users of this business group
		BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, group, identity);
		// do logging
		ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_FROM_WAITING_LIST_REMOVED, getClass(), LoggingResourceable.wrap(identity));
		// send mail
		BusinessGroupMailing.sendEmail(ureqIdentity, identity, group, MailType.removeToWaitingList, mailing, mailer);
	}
	
	@Override
	public void removeFromWaitingList(Identity ureqIdentity, List<Identity> identities, BusinessGroup businessGroup, MailPackage mailing) {
		businessGroup = businessGroupDAO.loadForUpdate(businessGroup.getKey());
		
		for (Identity identity : identities) {
		  removeFromWaitingList(ureqIdentity, identity, businessGroup, mailing);
		  log.audit("removed identiy '" + identity.getName() + "' from securitygroup with key " + businessGroup.getOwnerGroup().getKey());
		}
	}
	
	@Override
	public int getPositionInWaitingListFor(Identity identity, BusinessGroup businessGroup) {
		// get position in waiting-list
		List<Object[]> identities = securityManager.getIdentitiesAndDateOfSecurityGroup(businessGroup.getWaitingGroup(), true);
		for (int i = 0; i<identities.size(); i++) {
		  Object[] co = identities.get(i);
		  Identity waitingListIdentity = (Identity) co[0];
		  if (waitingListIdentity.equals(identity) ) {
		  	return i+1;// '+1' because list begins with 0 
		  }
		}
		return -1;
	}

	@Override
	public BusinessGroupAddResponse moveIdentityFromWaitingListToParticipant(Identity ureqIdentity, List<Identity> identities, 
			BusinessGroup currBusinessGroup, MailPackage mailing) {
		
		Roles ureqRoles = securityManager.getRoles(ureqIdentity);
		
		BusinessGroupAddResponse response = new BusinessGroupAddResponse();
		SyncUserListTask syncIM = new SyncUserListTask(currBusinessGroup);
		currBusinessGroup = businessGroupDAO.loadForUpdate(currBusinessGroup.getKey());
		
		for (Identity identity : identities) {
			// check if identity is already in participant
			if (!securityManager.isIdentityInSecurityGroup(identity,currBusinessGroup.getPartipiciantGroup()) ) {
				// Identity is not in participant-list => move idenity from waiting-list to participant-list
				addParticipant(ureqIdentity, ureqRoles, identity, currBusinessGroup, mailing, syncIM);
				removeFromWaitingList(ureqIdentity, identity, currBusinessGroup, mailing);
				response.getAddedIdentities().add(identity);
				// notification mail is handled in controller
			} else {
				response.getIdentitiesAlreadyInGroup().add(identity);
			}
		}
		
		syncIM(syncIM, currBusinessGroup);
		return response;
	}

	@Override
	public BusinessGroupAddResponse addToSecurityGroupAndFireEvent(Identity ureqIdentity, List<Identity> addIdentities, SecurityGroup secGroup) {
		BusinessGroupAddResponse response = new BusinessGroupAddResponse();
		for (Identity identity : addIdentities) {
			if (securityManager.isIdentityPermittedOnResourceable(identity, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_GUESTONLY)) {
				response.getIdentitiesWithoutPermission().add(identity);
			}
			// Check if identity is already in group. make a db query in case
			// someone in another workflow already added this user to this group. if
			// found, add user to model
			else if (securityManager.isIdentityInSecurityGroup(identity, secGroup)) {
				response.getIdentitiesAlreadyInGroup().add(identity);
			} else {
	      // identity has permission and is not already in group => add it
				securityManager.addIdentityToSecurityGroup(identity, secGroup);
				ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_OWNER_ADDED, getClass(), LoggingResourceable.wrap(identity));
				
				response.getAddedIdentities().add(identity);
				log.audit("added identity '" + identity.getName() + "' to securitygroup with key " + secGroup.getKey());
			}
		}
		return response;
	}
	
	@Override
	public void removeAndFireEvent(Identity ureqIdentity, List<Identity> identities, SecurityGroup secGroup) {
		for (Identity identity : identities) {
			securityManager.removeIdentityFromSecurityGroup(identity, secGroup);
		  log.audit("removed identiy '" + identity.getName() + "' from securitygroup with key " + secGroup.getKey());
		}
	}
	
	@Override
	public EnrollState enroll(Identity ureqIdentity, Roles ureqRoles, Identity identity, BusinessGroup group,
			MailPackage mailing) {
		final BusinessGroup reloadedGroup = businessGroupDAO.loadForUpdate(group.getKey());
		
		log.info("doEnroll start: group=" + OresHelper.createStringRepresenting(group), identity.getName());
		EnrollState enrollStatus = new EnrollState();

		ResourceReservation reservation = acService.getReservation(identity, reloadedGroup.getResource());
		SyncUserListTask syncIM = new SyncUserListTask(reloadedGroup);
		
		//reservation has the highest priority over max participant or other settings
		if(reservation != null) {
			addParticipant(ureqIdentity, ureqRoles, identity, reloadedGroup, mailing, syncIM);
			enrollStatus.setEnrolled(BGMembership.participant);
			log.info("doEnroll (reservation) - setIsEnrolled ", identity.getName());
			if(reservation != null) {
				acService.removeReservation(reservation);
			}
		} else if (reloadedGroup.getMaxParticipants() != null) {
			int participantsCounter = securityManager.countIdentitiesOfSecurityGroup(reloadedGroup.getPartipiciantGroup());
			int reservations = acService.countReservations(reloadedGroup.getResource());
			
			log.info("doEnroll - participantsCounter: " + participantsCounter + ", reservations: " + reservations + " maxParticipants: " + reloadedGroup.getMaxParticipants().intValue(), identity.getName());
			if (reservation == null && (participantsCounter + reservations) >= reloadedGroup.getMaxParticipants().intValue()) {
				// already full, show error and updated choose page again
				if (reloadedGroup.getWaitingListEnabled().booleanValue()) {
					addToWaitingList(ureqIdentity, identity, reloadedGroup, mailing);
					enrollStatus.setEnrolled(BGMembership.waiting);
				} else {
					// No Waiting List => List is full
					enrollStatus.setI18nErrorMessage("error.group.full");
					enrollStatus.setFailed(true);
				}
			} else {
				//enough place
				addParticipant(ureqIdentity, ureqRoles, identity, reloadedGroup, mailing, syncIM);
				enrollStatus.setEnrolled(BGMembership.participant);
				log.info("doEnroll - setIsEnrolled ", identity.getName());
			}
		} else {
			if (log.isDebug()) log.debug("doEnroll as participant beginTransaction");
			addParticipant(ureqIdentity, ureqRoles, identity, reloadedGroup, mailing, syncIM);
			enrollStatus.setEnrolled(BGMembership.participant);						
			if (log.isDebug()) log.debug("doEnroll as participant committed");
		}
		
		syncIM(syncIM, reloadedGroup);
		log.info("doEnroll end", identity.getName());
		return enrollStatus;
	}

	private void transferFirstIdentityFromWaitingToParticipant(Identity ureqIdentity, BusinessGroup group, 
			MailPackage mailing, SyncUserListTask syncIM) {

		// Check if waiting-list is enabled and auto-rank-up
		if (group.getWaitingListEnabled() != null && group.getWaitingListEnabled().booleanValue()
				&& group.getAutoCloseRanksEnabled() != null && group.getAutoCloseRanksEnabled().booleanValue()) {
			// Check if participant is not full
			Integer maxSize = group.getMaxParticipants();
			int reservations = acService.countReservations(group.getResource());
			int partipiciantSize = securityManager.countIdentitiesOfSecurityGroup(group.getPartipiciantGroup());
			if (maxSize != null && (partipiciantSize + reservations) < maxSize.intValue()) {
				// ok it has free places => get first identities from waiting list
				List<Object[]> identities = securityManager.getIdentitiesAndDateOfSecurityGroup(group.getWaitingGroup(), true/*sortedByAddedDate*/);
				
				int counter = 0;
				int freeSlot = maxSize - (partipiciantSize + reservations);
			  for(Object[] co: identities) {
			  	if(counter >= freeSlot) {
			  		break;
			  	}
			  	
			  	// It has an identity and transfer from waiting-list to participant-group is not done
					Identity firstWaitingListIdentity = (Identity) co[0];
					// Check if firstWaitingListIdentity is not allready in participant-group
					if (!securityManager.isIdentityInSecurityGroup(firstWaitingListIdentity,group.getPartipiciantGroup())) {
						// move the identity from the waitinglist to the participant group
						
						ActionType formerStickyActionType = ThreadLocalUserActivityLogger.getStickyActionType();
						try{
							// OLAT-4955: force add-participant and remove-from-waitinglist logging actions 
							//            that get triggered in the next two methods to be of ActionType admin
							//            This is needed to make sure the targetIdentity ends up in the o_loggingtable
							ThreadLocalUserActivityLogger.setStickyActionType(ActionType.admin);
							MailPackage subMailing = new MailPackage(false);//doesn0t send these emails but a specific one
							addParticipant(ureqIdentity, null, firstWaitingListIdentity, group, subMailing, syncIM);
							removeFromWaitingList(ureqIdentity, firstWaitingListIdentity, group, subMailing);
						} finally {
							ThreadLocalUserActivityLogger.setStickyActionType(formerStickyActionType);
						}

						BusinessGroupMailing.sendEmail(ureqIdentity, firstWaitingListIdentity, group, MailType.graduateFromWaitingListToParticpant, mailing, mailer);				
						counter++;
				  }
				}
			}
		} else {
			log.warn("Called method transferFirstIdentityFromWaitingToParticipant but waiting-list or autoCloseRanks is disabled.");
		}
	}
	
	private void syncIM(SyncUserListTask task, BusinessGroup group) {
		if (!task.isEmpty() && InstantMessagingModule.isEnabled()) {
			//evaluate whether to sync or not
			IMConfigSync syncGroup = InstantMessagingModule.getAdapter().getConfig().getSyncGroupsConfig();
			//only sync when a group is a certain type and this type is configured that you want to sync it
			if(syncGroup.equals(IMConfigSync.allGroups) || 
					(syncGroup.equals(IMConfigSync.perConfig) && isChatEnableFor(group))) { 

				//course group enrolment is time critial so we move this in an separate thread and catch all failures 
				try {
					TaskExecutorManager.getInstance().runTask(task);
				} catch (Exception e) {
					log.error("Error trying to sync the roster of the business group: " + group, e);
				}
			}
		}
	}
	
	private boolean isChatEnableFor(BusinessGroup group) {
		CollaborationTools tools = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(group);
		if(tools == null) {
			return false;
		}
		return tools.isToolEnabled(CollaborationTools.TOOL_CHAT);
	}
	
	private void removeOwner(Identity ureqIdentity, Identity identityToRemove, BusinessGroup group, SyncUserListTask syncIM) {
		securityManager.removeIdentityFromSecurityGroup(identityToRemove, group.getOwnerGroup());
		// remove user from buddies rosters
		syncIM.addUserToRemove(identityToRemove.getName());
		
		//remove subsciptions if user gets removed
		removeSubscriptions(identityToRemove, group);
		
		// notify currently active users of this business group
		if (identityToRemove.getKey().equals(ureqIdentity.getKey()) ) {
			BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.MYSELF_ASOWNER_REMOVED_EVENT, group, identityToRemove);
		} else {
  		BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, group, identityToRemove);
		}
		// do logging
		ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_OWNER_REMOVED, getClass(), LoggingResourceable.wrap(group), LoggingResourceable.wrap(identityToRemove));
	}
	
	@Override
	public void removeOwners(Identity ureqIdentity, Collection<Identity> identitiesToRemove, BusinessGroup group) {
		SyncUserListTask syncIM = new SyncUserListTask(group);
		for(Identity identityToRemove:identitiesToRemove) {
			removeOwner(ureqIdentity, identityToRemove, group, syncIM);
		}
		syncIM(syncIM, group);
	}
	
	private void removeSubscriptions(Identity identity, BusinessGroup group) {
		NotificationsManager notiMgr = NotificationsManager.getInstance();
		List<Subscriber> l = notiMgr.getSubscribers(identity);
		for (Iterator<Subscriber> iterator = l.iterator(); iterator.hasNext();) {
			Subscriber subscriber = iterator.next();
			Long resId = subscriber.getPublisher().getResId();
			Long groupKey = group.getKey();
			if (resId != null && groupKey != null && resId.equals(groupKey)) {
				notiMgr.unsubscribe(subscriber);
			}
		}
	}
	
	@Override
	@Transactional(readOnly=true)
	public boolean hasResources(BusinessGroup group) {
		return businessGroupRelationDAO.countResources(group) > 0;
	}
	
	@Override
	@Transactional(readOnly=true)
	public boolean hasResources(List<BusinessGroup> groups) {
		return businessGroupRelationDAO.countResources(groups) > 0;
	}

	@Override
	@Transactional
	public void addResourceTo(BusinessGroup group, RepositoryEntry re) {
		businessGroupRelationDAO.addRelationToResource(group, re.getOlatResource());
		//add author permission
		securityManager.createAndPersistPolicyWithResource(re.getOwnerGroup(), Constants.PERMISSION_ACCESS, group.getResource());
		//add coach and participant permission
		securityManager.createAndPersistPolicyWithResource(group.getOwnerGroup(), Constants.PERMISSION_COACH, re.getOlatResource());
		securityManager.createAndPersistPolicyWithResource(group.getPartipiciantGroup(), Constants.PERMISSION_PARTI, re.getOlatResource());
	}

	@Override
	@Transactional
	public void addResourcesTo(List<BusinessGroup> groups, List<RepositoryEntry> resources) {
		if(groups == null || groups.isEmpty()) return;
		if(resources == null || resources.isEmpty()) return;
		
		List<Long> groupKeys = new ArrayList<Long>();
		for(BusinessGroup group:groups) {
			groupKeys.add(group.getKey());
		}

		//check for duplicate entries
		List<BGResourceRelation> relations = businessGroupRelationDAO.findRelations(groupKeys, 0, -1);
		for(BusinessGroup group:groups) {
			for(RepositoryEntry re:resources) {
				boolean relationExists = false;
				for(BGResourceRelation relation:relations) {
					if(relation.getGroup().equals(group) && relation.getResource().equals(re.getOlatResource())) {
						relationExists = true;
					}
				}
				if(!relationExists) {
					addResourceTo(group, re);
				}
			}
		}
	}

	@Override
	public void dedupMembers(Identity ureqIdentity, boolean coaches, boolean participants, ProgressDelegate delegate) {
		SearchRepositoryEntryParameters params = new SearchRepositoryEntryParameters();
		params.setRoles(new Roles(true, false, false, false, false, false, false));
		params.setResourceTypes(Collections.singletonList("CourseModule"));
		
		float ratio = -1.0f;
		if(delegate != null) {
			int numOfEntries = repositoryManager.countGenericANDQueryWithRolesRestriction(params, true);
			ratio = 100.0f / (float)numOfEntries;
		}

		int counter = 0;
		int countForCommit = 0;
		float actual = 100.0f;
		int batch = 25;
		List<RepositoryEntry> entries;
		do {
			entries = repositoryManager.genericANDQueryWithRolesRestriction(params, counter, batch, true);
			for(RepositoryEntry re:entries) {
				countForCommit += 2 + dedupSingleRepositoryentry(ureqIdentity, re, coaches, participants, false);
				if(countForCommit > 25) {
					dbInstance.intermediateCommit();
					countForCommit = 0;
				}
			}
			counter += entries.size();
			if(delegate != null) {
				actual -= (entries.size() * ratio);
				delegate.setActual(actual);
			}
		} while(entries.size() == batch);
		
		if(delegate != null) {
			delegate.finished();
		}
	}
	@Override
	@Transactional
	public void dedupMembers(Identity ureqIdentity, RepositoryEntry entry, boolean coaches, boolean participants) {
		dedupSingleRepositoryentry(ureqIdentity, entry, coaches, participants, false);
	}
	
	@Override
	@Transactional(readOnly=true)
	public int countDuplicateMembers(RepositoryEntry entry, boolean coaches, boolean participants) {
		return dedupSingleRepositoryentry(null, entry, coaches, participants, true);
	}

	private int dedupSingleRepositoryentry(Identity ureqIdentity, RepositoryEntry entry, boolean coaches, boolean participants, boolean dryRun) {
		int count = 0;
		
		List<BusinessGroup> groups = null;//load only if needed
		if(coaches && entry.getTutorGroup() != null) {
			List<Identity> repoTutorList = securityManager.getIdentitiesOfSecurityGroup(entry.getTutorGroup());
			if(!repoTutorList.isEmpty()) {
				SearchBusinessGroupParams params = new SearchBusinessGroupParams();
				groups = businessGroupDAO.findBusinessGroups(params, entry.getOlatResource(), 0, -1);
				List<SecurityGroup> ownerSecGroups = new ArrayList<SecurityGroup>();
				for(BusinessGroup group:groups) {
					ownerSecGroups.add(group.getOwnerGroup());
				}
				
				List<Identity> ownerList = securityManager.getIdentitiesOfSecurityGroups(ownerSecGroups);
				repoTutorList.retainAll(ownerList);
				if(!dryRun) {
					repositoryManager.removeTutors(ureqIdentity, repoTutorList, entry);
				}
				count += repoTutorList.size();
			}
		}
		
		if(participants && entry.getParticipantGroup() != null) {
			List<Identity> repoParticipantList = securityManager.getIdentitiesOfSecurityGroup(entry.getParticipantGroup());
			if(!repoParticipantList.isEmpty()) {
			
				if(groups == null) {
					SearchBusinessGroupParams params = new SearchBusinessGroupParams();
					groups = businessGroupDAO.findBusinessGroups(params, entry.getOlatResource(), 0, -1);
				}
				List<SecurityGroup> participantSecGroups = new ArrayList<SecurityGroup>();
				for(BusinessGroup group:groups) {
					participantSecGroups.add(group.getPartipiciantGroup());
				}
				List<Identity> participantList = securityManager.getIdentitiesOfSecurityGroups(participantSecGroups);
				repoParticipantList.retainAll(participantList);
				if(!dryRun) {
					repositoryManager.removeParticipants(ureqIdentity, repoParticipantList, entry, null);
				}
				count += repoParticipantList.size();
			}
		}
		return count;
	}

	@Override
	@Transactional
	public void removeResourceFrom(BusinessGroup group, RepositoryEntry re) {
		businessGroupRelationDAO.deleteRelation(group, re.getOlatResource());
		//remove author permission
		securityManager.deletePolicy(re.getOwnerGroup(), Constants.PERMISSION_ACCESS, group.getResource());
		//remove permission
		securityManager.deletePolicy(group.getOwnerGroup(), Constants.PERMISSION_COACH, re.getOlatResource());
		securityManager.deletePolicy(group.getPartipiciantGroup(), Constants.PERMISSION_PARTI, re.getOlatResource());
	}
	
	@Override
	@Transactional
	public void removeResource(OLATResource resource) {
		SearchBusinessGroupParams params = new SearchBusinessGroupParams();
		List<BusinessGroup> groups = findBusinessGroups(params, resource, 0, -1);
		for(BusinessGroup group:groups) {
			businessGroupRelationDAO.deleteRelation(group, resource);
			//remove author permission
			//securityManager.deletePolicy(re.getOwnerGroup(), Constants.PERMISSION_ACCESS, group.getResource());
			//remove permission
			securityManager.deletePolicy(group.getOwnerGroup(), Constants.PERMISSION_COACH, resource);
			securityManager.deletePolicy(group.getPartipiciantGroup(), Constants.PERMISSION_PARTI, resource);
		}
	}

	@Override
	@Transactional(readOnly=true)
	public List<OLATResource> findResources(Collection<BusinessGroup> groups, int firstResult, int maxResults) {
		return businessGroupRelationDAO.findResources(groups, firstResult, maxResults);
	}

	@Override
	@Transactional(readOnly=true)
	public List<RepositoryEntry> findRepositoryEntries(Collection<BusinessGroup> groups, int firstResult, int maxResults) {
		return businessGroupRelationDAO.findRepositoryEntries(groups, firstResult, maxResults);
	}

	@Override
	@Transactional(readOnly=true)
	public List<RepositoryEntryShort> findShortRepositoryEntries(Collection<BusinessGroupShort> groups, int firstResult, int maxResults) {
		return businessGroupRelationDAO.findShortRepositoryEntries(groups, firstResult, maxResults);
	}
	
	@Override
	@Transactional(readOnly=true)
	public List<BGRepositoryEntryRelation> findRelationToRepositoryEntries(Collection<Long> groupKeys, int firstResult, int maxResults) {
		return businessGroupRelationDAO.findRelationToRepositoryEntries(groupKeys, firstResult, maxResults);
	}

	@Override
	@Transactional(readOnly=true)
	public boolean isIdentityInBusinessGroup(Identity identity, BusinessGroup businessGroup) {
		SecurityGroup participants = businessGroup.getPartipiciantGroup();
		if (participants != null && securityManager.isIdentityInSecurityGroup(identity, participants)) {
			return true;
		}
		SecurityGroup owners = businessGroup.getOwnerGroup();
		if (owners != null && securityManager.isIdentityInSecurityGroup(identity, owners)) {
			return true;
		}
		return false;
	}

	@Override
	@Transactional(readOnly=true)
	public List<BusinessGroupMembership> getBusinessGroupMembership(Collection<Long> businessGroups, Identity... identity) {
		List<BusinessGroupMembershipViewImpl> views =
				businessGroupDAO.getMembershipInfoInBusinessGroups(businessGroups, identity);

		Map<IdentityGroupKey, BusinessGroupMembershipImpl> memberships = new HashMap<IdentityGroupKey, BusinessGroupMembershipImpl>();
		for(BusinessGroupMembershipViewImpl membership: views) {
			if(membership.getOwnerGroupKey() != null) {
				Long groupKey = membership.getOwnerGroupKey();
				IdentityGroupKey key = new IdentityGroupKey(membership.getIdentityKey(), groupKey);
				if(!memberships.containsKey(key)) {
					memberships.put(key, new BusinessGroupMembershipImpl(membership.getIdentityKey(), groupKey));
				}
				BusinessGroupMembershipImpl mb = memberships.get(key);
				mb.setOwner(true);
				mb.setCreationDate(membership.getCreationDate());
				mb.setLastModified(membership.getLastModified());
			}
			if(membership.getParticipantGroupKey() != null) {
				Long groupKey = membership.getParticipantGroupKey();
				IdentityGroupKey key = new IdentityGroupKey(membership.getIdentityKey(), groupKey);
				if(!memberships.containsKey(key)) {
					memberships.put(key, new BusinessGroupMembershipImpl(membership.getIdentityKey(), groupKey));
				}
				BusinessGroupMembershipImpl mb = memberships.get(key);
				mb.setParticipant(true);
				mb.setCreationDate(membership.getCreationDate());
				mb.setLastModified(membership.getLastModified());
			}
			if(membership.getWaitingGroupKey() != null) {
				Long groupKey = membership.getWaitingGroupKey();
				IdentityGroupKey key = new IdentityGroupKey(membership.getIdentityKey(), groupKey);
				if(!memberships.containsKey(key)) {
					memberships.put(key, new BusinessGroupMembershipImpl(membership.getIdentityKey(), groupKey));
				}
				BusinessGroupMembershipImpl mb = memberships.get(key);
				mb.setWaiting(true);
				mb.setCreationDate(membership.getCreationDate());
				mb.setLastModified(membership.getLastModified());
			}
		}
		
		return new ArrayList<BusinessGroupMembership>(memberships.values());
	}
	
	@Override
	@Transactional(readOnly=true)
	public boolean isIdentityInBusinessGroup(Identity identity, Long groupKey,
			boolean ownedById, boolean attendedById, OLATResource resource) {
		return businessGroupRelationDAO.isIdentityInBusinessGroup(identity, groupKey, ownedById, attendedById, resource);
	}
	
	@Override
	public void exportGroups(List<BusinessGroup> groups, List<BGArea> areas, File fExportFile,
			BusinessGroupEnvironment env, boolean backwardsCompatible) {
		businessGroupImportExport.exportGroups(groups, areas, fExportFile, env, backwardsCompatible);
	}

	@Override
	public BusinessGroupEnvironment importGroups(RepositoryEntry re, File fGroupExportXML) {
		return businessGroupImportExport.importGroups(re, fGroupExportXML);
	}

	@Override
	public void archiveGroups(List<BusinessGroup> groups, File exportFile) {
		businessGroupArchiver.archiveGroups(groups, exportFile);
	}

	@Override
	public File archiveGroupMembers(OLATResource resource, List<String> columnList, List<BusinessGroup> groupList, String archiveType, Locale locale, String charset) {
		return businessGroupArchiver.archiveGroupMembers(resource, columnList, groupList, archiveType, locale, charset);
	}
}