diff --git a/src/main/java/org/olat/admin/user/groups/GroupOverviewController.java b/src/main/java/org/olat/admin/user/groups/GroupOverviewController.java
index 9be4b9be575c16bf1559f9458870e196898ef851..969a1920d50205257c083062dde9f2dc38844ea7 100644
--- a/src/main/java/org/olat/admin/user/groups/GroupOverviewController.java
+++ b/src/main/java/org/olat/admin/user/groups/GroupOverviewController.java
@@ -51,8 +51,6 @@ import org.olat.core.gui.control.generic.closablewrapper.CloseableModalControlle
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalWindowWrapperController;
 import org.olat.core.id.Identity;
 import org.olat.core.util.Util;
-import org.olat.core.util.coordinate.CoordinatorManager;
-import org.olat.core.util.coordinate.SyncerExecutor;
 import org.olat.core.util.mail.MailHelper;
 import org.olat.core.util.mail.MailTemplate;
 import org.olat.core.util.mail.MailerResult;
@@ -297,13 +295,7 @@ public class GroupOverviewController extends BasicController {
 					businessGroupService.removeOwners(ureq.getIdentity(), Collections.singletonList(identity), group);
 				}
 				// 2) remove as participant
-				final BusinessGroup toRemFromGroup = group;
-				//TODO gsync
-				CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerExecutor() {
-					public void execute() {
-						businessGroupService.removeParticipant(getIdentity(), identity, toRemFromGroup);
-					}
-				});
+				businessGroupService.removeParticipants(getIdentity(), Collections.singletonList(identity), group);
 	
 				// 3) notify user about this action:
 				if(doSendMail){
diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java
index 1c4077e2c963aaee6b2eb0dc5d5bea8bc92335fd..c3475abc9491738c8ec6ff729e976e5bee6c747e 100644
--- a/src/main/java/org/olat/collaboration/CollaborationTools.java
+++ b/src/main/java/org/olat/collaboration/CollaborationTools.java
@@ -210,10 +210,10 @@ public class CollaborationTools implements Serializable {
 	public final static String KEY_FOLDER_ACCESS = "folder";
 
 	//o_clusterOK by guido
-	Hashtable<String, Boolean> cacheToolStates;
-	final OLATResourceable ores;
+	private Hashtable<String, Boolean> cacheToolStates;
+	private final BusinessGroup ores;
 	
-	OLog log = Tracing.createLoggerFor(this.getClass());
+	private static OLog log = Tracing.createLoggerFor(CollaborationTools.class);
 	private transient CoordinatorManager coordinatorManager;
 
 	/**
@@ -221,7 +221,7 @@ public class CollaborationTools implements Serializable {
 	 * 
 	 * @param ores
 	 */
-	CollaborationTools(CoordinatorManager coordinatorManager, OLATResourceable ores) {
+	CollaborationTools(CoordinatorManager coordinatorManager, BusinessGroup ores) {
 		this.coordinatorManager = coordinatorManager;
 		this.ores = ores;
 		cacheToolStates = new Hashtable<String, Boolean>();
diff --git a/src/main/java/org/olat/collaboration/CollaborationToolsFactory.java b/src/main/java/org/olat/collaboration/CollaborationToolsFactory.java
index 65c3e07d679cfc9f937bd35fb9b8a25d4bde4f26..aa359141134f74269b1febd777a13fd329c52537 100644
--- a/src/main/java/org/olat/collaboration/CollaborationToolsFactory.java
+++ b/src/main/java/org/olat/collaboration/CollaborationToolsFactory.java
@@ -32,6 +32,7 @@ import org.olat.core.logging.Tracing;
 import org.olat.core.util.cache.n.CacheWrapper;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.coordinate.SyncerCallback;
+import org.olat.group.BusinessGroup;
 
 /**
  * Description:<BR>
@@ -72,7 +73,7 @@ public class CollaborationToolsFactory {
 	 * @param ores
 	 * @return CollaborationTools
 	 */
-	public CollaborationTools getOrCreateCollaborationTools(final OLATResourceable ores) {
+	public CollaborationTools getOrCreateCollaborationTools(final BusinessGroup ores) {
 		if (ores == null) throw new AssertException("Null is not allowed here, you have to provide an existing ores here!");
 		final String cacheKey = Long.valueOf(ores.getResourceableId()).toString();
 		//sync operation cluster wide
diff --git a/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java b/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java
index f3bd0626a71f78c72f87fdfb8daeca5eeddddfec..ed4e99175da3f1acbb8976c3c64e187c3bbf3b6c 100644
--- a/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java
+++ b/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java
@@ -42,10 +42,10 @@ import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
-import org.olat.core.id.OLATResourceable;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.core.util.vfs.QuotaManager;
+import org.olat.group.BusinessGroup;
 import org.olat.instantMessaging.InstantMessagingModule;
 
 /**
@@ -66,13 +66,13 @@ public class CollaborationToolsSettingsController extends BasicController {
 
 	boolean lastCalendarEnabledState;
 	private Controller quotaCtr;
-	private OLATResourceable businessGroup;
+	private BusinessGroup businessGroup;
 
 	/**
 	 * @param ureq
 	 * @param tools
 	 */
-	public CollaborationToolsSettingsController(UserRequest ureq, WindowControl wControl, OLATResourceable businessGroup) {
+	public CollaborationToolsSettingsController(UserRequest ureq, WindowControl wControl, BusinessGroup businessGroup) {
 		super(ureq, wControl);
 		this.businessGroup = businessGroup;
 		CollaborationTools collabTools = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(businessGroup);
diff --git a/src/main/java/org/olat/course/member/_content/edit_member.html b/src/main/java/org/olat/course/member/_content/edit_member.html
index af17764a8cd410fdb290134bb67fca8de40d9855..a9d9b08270e429c9104af63752be9e4661a9abab 100644
--- a/src/main/java/org/olat/course/member/_content/edit_member.html
+++ b/src/main/java/org/olat/course/member/_content/edit_member.html
@@ -1,13 +1,17 @@
 #if($r.available("infos"))
-	$r.render("infos")
+	$r.render("infos")<br/>
 #end
 #if($r.available("repoRights"))
-	<h4>$editTitle</h4>
-	$r.render("repoRights")
+	<fieldset>
+		<legend>$editTitle</legend>
+		$r.render("repoRights")
+	</fieldset>
 #end
 #if($r.available("groupList"))
-	<h5>$r.translate("edit.member.groups")</h5>
-	$r.render("groupList")
+	<fieldset>
+		<legend>$r.translate("edit.member.groups")</legend>
+		$r.render("groupList")
+	</fieldset>
 #end
 #if($r.available("buttonLayout"))
 	$r.render("buttonLayout")
diff --git a/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java b/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java
index 42ebb442d57a1e545bbb69db13af89b4bc5e609b..1d89925b7ad86acc9cba44571f90512e5bf3d15e 100644
--- a/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java
+++ b/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java
@@ -25,6 +25,7 @@
 
 package org.olat.course.nodes.en;
 
+import java.util.Collections;
 import java.util.List;
 
 import org.olat.basesecurity.BaseSecurity;
@@ -32,28 +33,25 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.id.Identity;
 import org.olat.core.manager.BasicManager;
-import org.olat.core.util.coordinate.CoordinatorManager;
-import org.olat.core.util.coordinate.SyncerExecutor;
 import org.olat.core.util.mail.MailContext;
 import org.olat.core.util.mail.MailContextImpl;
 import org.olat.core.util.mail.MailHelper;
 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.resource.OresHelper;
 import org.olat.course.groupsandrights.CourseGroupManager;
 import org.olat.course.nodes.ENCourseNode;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.group.BusinessGroup;
 import org.olat.group.BusinessGroupService;
 import org.olat.group.area.BGAreaManager;
+import org.olat.group.model.BGMembership;
+import org.olat.group.model.EnrollState;
 import org.olat.group.model.SearchBusinessGroupParams;
 import org.olat.group.ui.BGMailHelper;
 import org.olat.properties.Property;
 import org.olat.resource.OLATResource;
 import org.olat.resource.accesscontrol.ACService;
-import org.olat.resource.accesscontrol.model.ResourceReservation;
-import org.olat.testutils.codepoints.server.Codepoint;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -94,48 +92,16 @@ public class EnrollmentManager extends BasicManager {
 			// and: why can't we just have a group here and a max participants count and an identity to enrol?
 			// the group was chosen, so why do we need the groupNames and areaNames here???
 
-			Codepoint.codepoint(EnrollmentManager.class, "beforeDoInSync");
-		//TODO gsync
-			CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerExecutor(){
-				public void execute() {
-					logInfo("doEnroll start: group="+OresHelper.createStringRepresenting(group), identity.getName());
-					Codepoint.codepoint(EnrollmentManager.class, "doInSync1");
-					// 6_1_0-RC15: reload group object here another node might have changed this in the meantime
-					BusinessGroup reloadedGroup = businessGroupService.loadBusinessGroup(group);					
-					if (reloadedGroup.getMaxParticipants() != null) {
-						int participantsCounter = securityManager.countIdentitiesOfSecurityGroup(reloadedGroup.getPartipiciantGroup());
-						int reservations = acService.countReservations(reloadedGroup.getResource());
-						//reservation has the highest priority over max participant
-						ResourceReservation reservation = acService.getReservation(identity, reloadedGroup.getResource());
-						logInfo("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()) {
-								// No Waiting List => List is full
-								enrollStatus.setErrorMessage(trans.translate("error.group.full"));
-							} else {
-								boolean done = addUserToWaitingList(identity, reloadedGroup, enNode, coursePropertyManager, wControl, trans);
-								enrollStatus.setIsInWaitingList(done);
-							}
-						} else {
-							boolean done = addUserToParticipantList(identity, reloadedGroup, enNode, coursePropertyManager, wControl, trans);
-							Codepoint.codepoint(EnrollmentManager.class, "doInSync2");
-							enrollStatus.setIsEnrolled(done);
-							logInfo("doEnroll - setIsEnrolled ", identity.getName());
-							if(reservation != null) {
-								acService.removeReservation(reservation);
-							}
-						}
-					} else {
-						if (isLogDebugEnabled()) logDebug("doEnroll beginTransaction");
-						boolean done = addUserToParticipantList(identity, reloadedGroup, enNode, coursePropertyManager, wControl, trans);
-						enrollStatus.setIsEnrolled(done);						
-						if (isLogDebugEnabled()) logDebug("doEnroll committed");
-					}
-					logInfo("doEnroll end", identity.getName());
-				}				
-			});// end of doInSync
-			Codepoint.codepoint(EnrollmentManager.class, "afterDoInSync");
+			EnrollState state =businessGroupService.enroll(group, identity);
+			if(state.isFailed()) {
+				enrollStatus.setErrorMessage(trans.translate(state.getI18nErrorMessage()));
+			} else {
+				if(state.getEnrolled() == BGMembership.participant) {
+					addUserToParticipantList(identity, group, enNode, coursePropertyManager, wControl, trans);
+				} else if(state.getEnrolled() == BGMembership.waiting) {
+					addUserToWaitingList(identity, group, enNode, coursePropertyManager, wControl, trans);
+				}
+			}
 		} else {
 			enrollStatus.setErrorMessage(trans.translate("error.group.already.enrolled"));
 		}
@@ -147,22 +113,18 @@ public class EnrollmentManager extends BasicManager {
 			final CoursePropertyManager coursePropertyManager, WindowControl wControl, Translator trans) {
 		if (isLogDebugEnabled()) logDebug("doCancelEnrollment");
 		// 1. Remove group membership, fire events, do loggin etc.
-	//TODO gsync
-		CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(enrolledGroup, new SyncerExecutor(){
-			public void execute() {
-				// Remove participant. This will also check if a waiting-list with auto-close-ranks is configurated
-				// and move the users accordingly
-				businessGroupService.removeParticipant(identity, identity, enrolledGroup);
-				logInfo("doCancelEnrollment in group " + enrolledGroup, identity.getName());
-				// 2. Remove enrollmentdate property
-				// only remove last time date, not firsttime
-				Property lastTime = coursePropertyManager
-				.findCourseNodeProperty(enNode, identity, null, ENCourseNode.PROPERTY_RECENT_ENROLLMENT_DATE);
-				if (lastTime != null) {
-					coursePropertyManager.deleteProperty(lastTime);
-				}
-			}});
-		
+		// Remove participant. This will also check if a waiting-list with auto-close-ranks is configurated
+		// and move the users accordingly
+		businessGroupService.removeParticipants(identity, Collections.singletonList(identity), enrolledGroup);
+		logInfo("doCancelEnrollment in group " + enrolledGroup, identity.getName());
+
+		logInfo("doCancelEnrollment in group " + enrolledGroup, identity.getName());
+		// 2. Remove enrollmentdate property
+		// only remove last time date, not firsttime
+		Property lastTime = coursePropertyManager.findCourseNodeProperty(enNode, identity, null, ENCourseNode.PROPERTY_RECENT_ENROLLMENT_DATE);
+		if (lastTime != null) {
+			coursePropertyManager.deleteProperty(lastTime);
+		}
 
 		// 3. Send notification mail
 		MailTemplate mailTemplate = BGMailHelper.createRemoveMyselfMailTemplate(enrolledGroup, identity);
@@ -176,19 +138,14 @@ public class EnrollmentManager extends BasicManager {
 	public void doCancelEnrollmentInWaitingList(final Identity identity, final BusinessGroup enrolledWaitingListGroup, final ENCourseNode enNode,
 			final CoursePropertyManager coursePropertyManager, WindowControl wControl, Translator trans) {
 		// 1. Remove group membership, fire events, do loggin etc.
-		//TODO gsync
-		CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(enrolledWaitingListGroup, new SyncerExecutor(){
-			public void execute() {
-				businessGroupService.removeFromWaitingList(identity, identity, enrolledWaitingListGroup);
-				// 2. Remove enrollmentdate property
-				// only remove last time date, not firsttime
-				Property lastTime = coursePropertyManager.findCourseNodeProperty(enNode, identity, null,
-						ENCourseNode.PROPERTY_RECENT_WAITINGLIST_DATE);
-				if (lastTime != null) {
-					coursePropertyManager.deleteProperty(lastTime);
-				}
-			}});
+		businessGroupService.removeFromWaitingList(identity, Collections.singletonList(identity), enrolledWaitingListGroup);
 		
+		// 2. Remove enrollmentdate property
+		// only remove last time date, not firsttime
+		Property lastTime = coursePropertyManager.findCourseNodeProperty(enNode, identity, null, ENCourseNode.PROPERTY_RECENT_WAITINGLIST_DATE);
+		if (lastTime != null) {
+			coursePropertyManager.deleteProperty(lastTime);
+		}
 
 		// 3. Send notification mail
 		MailTemplate mailTemplate = BGMailHelper.createRemoveWaitinglistMailTemplate(enrolledWaitingListGroup, identity);
@@ -298,9 +255,6 @@ public class EnrollmentManager extends BasicManager {
 	// /////////////////
 	private boolean addUserToParticipantList(Identity identity, BusinessGroup group, ENCourseNode enNode,
 			CoursePropertyManager coursePropertyManager, WindowControl wControl, Translator trans) {
-		CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(group);
-		// 1. Add user to group, fire events, do loggin etc.
-		businessGroupService.addParticipant(identity, identity, group);
 		// 2. Set first enrollment date
 		String nowString = Long.toString(System.currentTimeMillis());
 		Property firstTime = coursePropertyManager
@@ -336,9 +290,7 @@ public class EnrollmentManager extends BasicManager {
 
 	private boolean addUserToWaitingList(Identity identity, BusinessGroup group, ENCourseNode enNode,
 			CoursePropertyManager coursePropertyManager, WindowControl wControl, Translator trans) {
-		CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(group);
-		// 1. Add user to group, fire events, do loggin etc.
-		businessGroupService.addToWaitingList(identity, identity, group);
+		// <- moved to bgs 1. Add user to group, fire events, do loggin etc.
 		// 2. Set first waiting-list date
 		String nowString = Long.toString(System.currentTimeMillis());
 		Property firstTime = coursePropertyManager.findCourseNodeProperty(enNode, identity, null,
diff --git a/src/main/java/org/olat/course/nodes/projectbroker/service/ProjectGroupManagerImpl.java b/src/main/java/org/olat/course/nodes/projectbroker/service/ProjectGroupManagerImpl.java
index 432fbc54684a3e5fc63ea541b5bf702d3954b63a..c48a7cd1af769c866c38e1ba60771cf3968ea931 100644
--- a/src/main/java/org/olat/course/nodes/projectbroker/service/ProjectGroupManagerImpl.java
+++ b/src/main/java/org/olat/course/nodes/projectbroker/service/ProjectGroupManagerImpl.java
@@ -247,22 +247,23 @@ public class ProjectGroupManagerImpl extends BasicManager implements ProjectGrou
 		final Project reloadedProject = (Project) DBFactory.getInstance().loadObject(project, true);
 		final BusinessGroupAddResponse response = new BusinessGroupAddResponse();
 		final BusinessGroupService bgs = CoreSpringFactory.getImpl(BusinessGroupService.class);
-	//TODO gsync
+
+		BusinessGroupAddResponse state = bgs.addParticipants(actionIdentity, identities, reloadedProject.getProjectGroup());
+		response.getAddedIdentities().addAll(state.getAddedIdentities());
+		response.getIdentitiesAlreadyInGroup().addAll(state.getAddedIdentities());
+		
 		Boolean result = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(project.getProjectGroup(), new SyncerCallback<Boolean>(){
 			public Boolean execute() {
 				for (final Identity identity : identities) {
 					if (!BaseSecurityManager.getInstance().isIdentityInSecurityGroup(identity, reloadedProject.getProjectGroup().getPartipiciantGroup())) {
 						BaseSecurityManager.getInstance().removeIdentityFromSecurityGroup(identity, reloadedProject.getCandidateGroup());
-						bgs.addParticipant(actionIdentity, identity, reloadedProject.getProjectGroup());
 						logAudit("ProjectBroker: Accept candidate, identity=" + identity + " project=" + reloadedProject);
-						response.getAddedIdentities().add(identity);
-					} else {
-						response.getIdentitiesAlreadyInGroup().add(identity);
-					}				
+					}		
 				}
 				return Boolean.TRUE;
 			}
 		});// end of doInSync
+		
 		if (autoSignOut && result.booleanValue()) {
 			ProjectBrokerManagerFactory.getProjectBrokerManager().signOutFormAllCandidateList(response.getAddedIdentities(), reloadedProject.getProjectBroker().getKey());
 		}
diff --git a/src/main/java/org/olat/course/run/RunMainController.java b/src/main/java/org/olat/course/run/RunMainController.java
index b1b91211ff176979032b83f1b65596a770e0c4c1..0d6efd18656106946c9f9516cc08a73c0fa1aefc 100644
--- a/src/main/java/org/olat/course/run/RunMainController.java
+++ b/src/main/java/org/olat/course/run/RunMainController.java
@@ -471,16 +471,18 @@ public class RunMainController extends MainLayoutBasicController implements Gene
 	}
 
 	private void initUserRolesAndRights(final Identity identity) {
-		CourseGroupManager cgm = this.course.getCourseEnvironment().getCourseGroupManager();
+		CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager();
 		// 1) course admins: users who are in repository entry owner group
 		// if user has the role InstitutionalResourceManager and has the same institution like author
 		// then set isCourseAdmin true
-		isCourseAdmin = cgm.isIdentityCourseAdministrator(identity) | RepositoryManager.getInstance().isInstitutionalRessourceManagerFor(courseRepositoryEntry, identity);
+		isCourseAdmin = cgm.isIdentityCourseAdministrator(identity)
+				|| RepositoryManager.getInstance().isInstitutionalRessourceManagerFor(courseRepositoryEntry, identity);
 		// 2) course coaches: users who are in the owner group of any group of this
 		// course
 		isCourseCoach = cgm.isIdentityCourseCoach(identity);
 		// 3) all other rights are defined in the groupmanagement using the learning
 		// group rights
+		
 		courseRightsCache.put(CourseRights.RIGHT_GROUPMANAGEMENT, new Boolean(cgm.hasRight(identity, CourseRights.RIGHT_GROUPMANAGEMENT)));
 		courseRightsCache.put(CourseRights.RIGHT_COURSEEDITOR, new Boolean(cgm.hasRight(identity, CourseRights.RIGHT_COURSEEDITOR)));
 		courseRightsCache.put(CourseRights.RIGHT_ARCHIVING, new Boolean(cgm.hasRight(identity, CourseRights.RIGHT_ARCHIVING)));
@@ -1092,6 +1094,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene
 		// 1) administrative tools
 		if (isCourseAdmin || isCourseCoach || hasCourseRight(CourseRights.RIGHT_COURSEEDITOR)
 				|| hasCourseRight(CourseRights.RIGHT_GROUPMANAGEMENT) || hasCourseRight(CourseRights.RIGHT_ARCHIVING)
+				|| hasCourseRight(CourseRights.RIGHT_ASSESSMENT) || hasCourseRight(CourseRights.RIGHT_DB)
 				|| hasCourseRight(CourseRights.RIGHT_ASSESSMENT)) {
 			myTool.addHeader(translate("header.tools"));
 			if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) {
diff --git a/src/main/java/org/olat/group/BusinessGroupService.java b/src/main/java/org/olat/group/BusinessGroupService.java
index 9a1d3e464cf8e66a945239015d87c748965b506c..7998037f4f6031ba3d0135c192977e3aabfe6937 100644
--- a/src/main/java/org/olat/group/BusinessGroupService.java
+++ b/src/main/java/org/olat/group/BusinessGroupService.java
@@ -339,21 +339,16 @@ public interface BusinessGroupService {
 	 * @return
 	 */
 	public EnrollState enroll(final BusinessGroup group,  final Identity identity);
-
-	/**
-	 * Adds a user to a group as participant and does all the magic that needs to
-	 * be done: - add to security group (optional) - add to jabber roster - fire
-	 * multi-user event
-	 * @param ureqIdentity
-	 * @param identityToAdd The user who should be added
-	 * @param group
-	 * @param flags
-	 */
-	public void addParticipant(Identity ureqIdentity, Identity identityToAdd, BusinessGroup group);
 	
 	/**
 	 * Adds a list of users to a group as participant and does all the magic that needs to
-	 * be done: - add to security group (optional) - add to jabber roster - fire multi-user
+	 * be done:
+	 * <ul>
+	 * 	<li>add to security group (optional)
+	 *  <li>add to jabber roster
+	 *  <li>fire multi-user
+	 * </ul>
+	 * Method execute in doInSync
 	 * event
 	 * @param ureqIdentity
 	 * @param addIdentities The users who should be added
@@ -363,21 +358,17 @@ public interface BusinessGroupService {
 	 */
 	public BusinessGroupAddResponse addParticipants(Identity ureqIdentity, List<Identity> addIdentities, BusinessGroup currBusinessGroup);
 
-	/**
-	 * Remove a user from a group as participant and does all the magic that needs
-	 * to be done: - remove from security group (optional) - remove from jabber roster -
-	 * fire multi-user event
-	 * @param ureqIdentity
-	 * @param identity The user who should be removed
-	 * @param group
-	 * @param flags
-	 */
-	public void removeParticipant(Identity ureqIdentity, Identity identity, BusinessGroup group);
-	
+
 	/**
 	 * Remove a list of users from a group as participant and does all the magic that needs
-	 * to be done: - remove from secgroup (optional) - remove from jabber roster -
-	 * fire multi-user event
+	 * to be done:
+	 * <ul>
+	 * 	<li>remove from secgroup (optional)
+	 *  <li>remove from jabber roster -
+	 *  <li>fire multi-user event
+	 * </ul>
+	 * The method is made under doInSync.
+	 * 
 	 * @param ureqIdentity
 	 * @param identities The user who should be removed
 	 * @param group
@@ -395,20 +386,17 @@ public interface BusinessGroupService {
 	 */
 	public void removeMembers(Identity ureqIdentity, List<Identity> identities, OLATResource resource);
 
-	/**
-	 * Adds a user to a waiting-list of a group and does all the magic that needs to
-	 * be done: - add to security group (optional) - add to jabber roster - send
-	 * notification email - fire multi-user event
-	 * @param ureqIdentity
-	 * @param identity
-	 * @param group
-	 */
-	public void addToWaitingList(Identity ureqIdentity, Identity identity, BusinessGroup group);
 	
 	/**
 	 * Adds a  list of users to a waiting-list of a group and does all the magic that needs to
-	 * be done: - add to security group (optional) - add to jabber roster - send
-	 * notification email - fire multi user event
+	 * be done:
+	 * <ul>
+	 * 	<li>add to security group (optional)
+	 *  <li>add to jabber roster
+	 *  <li>send notification email
+	 *  <li>fire multi user event
+	 * </ul>
+	 * Method executed under doInSync
 	 * @param ureqIdentity
 	 * @param addIdentities
 	 * @param currBusinessGroup
@@ -417,20 +405,6 @@ public interface BusinessGroupService {
 	 */
 	public BusinessGroupAddResponse addToWaitingList(Identity ureqIdentity, List<Identity> addIdentities, BusinessGroup currBusinessGroup);
 
-	
-	
-	/**
-	 * Remove a user from a waiting-list as participant and does all the magic that needs
-	 * to be done:<br/>
-	 * - remove from security group (optional) <br/>
-	 * - send notification email<br/>
-	 * - fire multi user event
-	 * @param ureqIdentity
-	 * @param identity
-	 * @param waitingListGroup
-	 */
-	public void removeFromWaitingList(Identity ureqIdentity, Identity identity, BusinessGroup waitingListGroup);
-	
 	/**
 	 * Remove a list of users from a waiting-list as participant and does all the magic that needs
 	 * to be done:
diff --git a/src/main/java/org/olat/group/manager/BusinessGroupDAO.java b/src/main/java/org/olat/group/manager/BusinessGroupDAO.java
index 06dacd6f350c2b8e4db58f89ee0f347626f505de..8f0a832ea8ac2a7a67d0179b7511888762dda0ff 100644
--- a/src/main/java/org/olat/group/manager/BusinessGroupDAO.java
+++ b/src/main/java/org/olat/group/manager/BusinessGroupDAO.java
@@ -134,7 +134,7 @@ public class BusinessGroupDAO {
 	
 	public BusinessGroup load(Long id) {
 		EntityManager em = dbInstance.getCurrentEntityManager();
-		BusinessGroup group = em.find(BusinessGroupImpl.class, id, LockModeType.NONE);
+		BusinessGroup group = em.find(BusinessGroupImpl.class, id);
 		return group;
 	}
 	
diff --git a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java
index dc5b9047192efed1adbfc4b7ed583928daf80a99..e9445859e746842f77172f08d38cfeb036eb70b6 100644
--- a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java
+++ b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java
@@ -96,7 +96,7 @@ 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.SyncSingleUserTask;
+import org.olat.instantMessaging.syncservice.SyncUserListTask;
 import org.olat.notifications.NotificationsManagerImpl;
 import org.olat.properties.Property;
 import org.olat.repository.RepositoryEntry;
@@ -147,10 +147,12 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		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) {
@@ -209,7 +211,8 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 	public BusinessGroup updateBusinessGroup(final Identity ureqIdentity, final BusinessGroup group, final String name, final String description,
 			final Integer minParticipants, final Integer maxParticipants) {
 		
-		return CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerCallback<BusinessGroup>() {
+		final SyncUserListTask syncIM = new SyncUserListTask(group);
+		BusinessGroup updatedGroup = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerCallback<BusinessGroup>() {
 			public BusinessGroup execute() {
 				// refresh group to prevent stale object exception and context proxy issues
 				BusinessGroup bg = loadBusinessGroup(group);
@@ -220,16 +223,20 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 				bg.setMinParticipants(minParticipants);
 				bg.setLastUsage(new Date(System.currentTimeMillis()));
 				//auto rank if possible
-				autoRankCheck(ureqIdentity, bg, previousMaxParticipants);
+				autoRankCheck(ureqIdentity, bg, previousMaxParticipants, syncIM);
 				return businessGroupDAO.merge(bg);
 			}
 		});
+		syncIM(syncIM, updatedGroup);
+		return updatedGroup;
 	}
-	
+
+	@Override
 	public BusinessGroup updateBusinessGroup(final Identity ureqIdentity, final BusinessGroup group, final String name, final String description,
 			final Integer minParticipants, final Integer maxParticipants, final Boolean waitingList, final Boolean autoCloseRanks) {
 		
-		return CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerCallback<BusinessGroup>() {
+		final SyncUserListTask syncIM = new SyncUserListTask(group);
+		BusinessGroup updatedGroup =  CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerCallback<BusinessGroup>() {
 			public BusinessGroup execute() {
 				// refresh group to prevent stale object exception and context proxy issues
 				BusinessGroup bg = loadBusinessGroup(group);
@@ -247,13 +254,15 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 				bg.setAutoCloseRanksEnabled(autoCloseRanks);
 				bg.setLastUsage(new Date(System.currentTimeMillis()));
 				//auto rank if possible
-				autoRankCheck(ureqIdentity, bg, previousMaxParticipants);
+				autoRankCheck(ureqIdentity, bg, previousMaxParticipants, syncIM);
 				return businessGroupDAO.merge(bg);
 			}
 		});
+		
+		return updatedGroup;
 	}
 	
-	private void autoRankCheck(Identity identity, BusinessGroup updatedGroup, Integer previousMaxParticipants) {
+	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
@@ -267,7 +276,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		
 		if(currentMaxNumber > previousMaxNumber) {
 			//I can rank up some users
-			transferFirstIdentityFromWaitingToParticipant(identity, updatedGroup);
+			transferFirstIdentityFromWaitingToParticipant(identity, updatedGroup, syncIM);
 		}
 	}
 
@@ -455,6 +464,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 	@Override
 	public BusinessGroup mergeBusinessGroups(final Identity merger, final BusinessGroup targetGroup, final List<BusinessGroup> groupsToMerge) {
 		groupsToMerge.remove(targetGroup);//to be sure
+		final SyncUserListTask syncIM = new SyncUserListTask(targetGroup);
 
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(targetGroup, new SyncerExecutor(){
 			public void execute() {
@@ -499,10 +509,10 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 				}
 				
 				for(Identity newOwner:newOwners) {
-					addOwner(merger, newOwner, targetGroup);
+					addOwner(merger, newOwner, targetGroup, syncIM);
 				}
 				for(Identity newParticipant:newParticipants) {
-					addParticipant(merger, newParticipant, targetGroup);
+					addParticipant(merger, newParticipant, targetGroup, syncIM);
 				}
 				for(Identity newWaiter:newWaiters) {
 					addToWaitingList(merger, newWaiter, targetGroup);
@@ -510,6 +520,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 			}
 		});
 		
+		syncIM(syncIM, targetGroup);
 		for(BusinessGroup group:groupsToMerge) {
 			deleteBusinessGroup(group);
 		}
@@ -524,6 +535,8 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 	}
 	
 	private void updateMembers(final Identity identity, final MembershipModification membersMod, final BusinessGroup group) {
+		final SyncUserListTask syncIM = new SyncUserListTask(group);
+		
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerExecutor(){
 			public void execute() {
 				List<Identity> currentOwners = securityManager.getIdentitiesOfSecurityGroup(group.getOwnerGroup());
@@ -532,12 +545,12 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 
 				for(Identity owner:membersMod.getAddOwners()) {
 					if(!currentOwners.contains(owner)) {
-						addOwner(identity, owner, group);
+						addOwner(identity, owner, group, syncIM);
 					}
 				}
 				for(Identity participant:membersMod.getAddParticipants()) {
 					if(!currentParticipants.contains(participant)) {
-						addParticipant(identity, participant, group);
+						addParticipant(identity, participant, group, syncIM);
 					}
 				}
 				for(Identity waitingIdentity:membersMod.getAddToWaitingList()) {
@@ -553,7 +566,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 						ownerToRemove.add(removed);
 					}
 					if(currentParticipants.contains(removed)) {
-						removeParticipant(identity, removed, group);
+						removeParticipant(identity, removed, group, syncIM);
 					}
 					if(currentWaitingList.contains(removed)) {
 						removeFromWaitingList(identity, removed, group);
@@ -561,6 +574,8 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 				}
 				removeOwners(identity, ownerToRemove, group);
 		}});
+		
+		syncIM(syncIM, group);
 	}
 
 	@Override
@@ -604,8 +619,11 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		List<BusinessGroup> groups = loadBusinessGroups(changesMap.keySet());
 		for(final BusinessGroup group:groups) {
 			final BusinessGroupMembershipsChanges changesWrapper = changesMap.get(group.getKey());
+			final SyncUserListTask syncIM = new SyncUserListTask(group);
+			
 			CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerExecutor() {
 				public void execute() {
+					
 					for(Identity id:changesWrapper.addToWaitingList) {
 						addToWaitingList(ureqIdentity, id, group);
 					}
@@ -613,19 +631,21 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 						removeFromWaitingList(ureqIdentity, id, group);
 					}
 					for(Identity id:changesWrapper.addTutors) {
-						addOwner(ureqIdentity, id, group);
+						addOwner(ureqIdentity, id, group, syncIM);
 					}
 					for(Identity id:changesWrapper.removeTutors) {
-						removeOwner(ureqIdentity, id, group);
+						removeOwner(ureqIdentity, id, group, syncIM);
 					}
 					for(Identity id:changesWrapper.addParticipants) {
-						addParticipant(ureqIdentity, id, group);
+						addParticipant(ureqIdentity, id, group, syncIM);
 					}
 					for(Identity id:changesWrapper.removeParticipants) {
-						removeParticipant(ureqIdentity, id, group);
+						removeParticipant(ureqIdentity, id, group, syncIM);
 					}
 				}
 			});
+			
+			syncIM(syncIM, group);
 		}
 	}
 
@@ -839,6 +859,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 
 	@Override
 	public BusinessGroupAddResponse addOwners(Identity ureqIdentity, List<Identity> addIdentities, BusinessGroup group) {
+		SyncUserListTask syncIM = new SyncUserListTask(group);
 		BusinessGroupAddResponse response = new BusinessGroupAddResponse();
 		for (Identity identity : addIdentities) {
 			group = loadBusinessGroup(group); // reload business group
@@ -852,18 +873,22 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 				response.getIdentitiesAlreadyInGroup().add(identity);
 			} else {
 	      // identity has permission and is not already in group => add it
-				addOwner(ureqIdentity, identity, group);
+				addOwner(ureqIdentity, identity, group, syncIM);
 				response.getAddedIdentities().add(identity);
 				log.audit("added identity '" + identity.getName() + "' to securitygroup with key " + group.getOwnerGroup().getKey());
 			}
 		}
+		syncIM(syncIM, group);
 		return response;
 	}
 	
-	private void addOwner(Identity ureqIdentity, Identity identity, BusinessGroup group) {
+	private void addOwner(Identity ureqIdentity, Identity identity, BusinessGroup group, SyncUserListTask syncIM) {
 		securityManager.addIdentityToSecurityGroup(identity, group.getOwnerGroup());
 		// add user to buddies rosters
-		addToRoster(ureqIdentity, identity, group);
+		if(syncIM != null) {
+			syncIM.addUserToAdd(identity.getName());
+		}
+		
 		// notify currently active users of this business group
 		BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT, group, identity);
 		// do logging
@@ -871,14 +896,15 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		// send notification mail in your controller!
 	}
 	
-	@Override
-	public void addParticipant(Identity ureqIdentity, Identity identityToAdd, BusinessGroup group) {
+	private void addParticipant(Identity ureqIdentity, Identity identityToAdd, BusinessGroup group, SyncUserListTask syncIM) {
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(group);
 
 		securityManager.addIdentityToSecurityGroup(identityToAdd, group.getPartipiciantGroup());
-
 		// add user to buddies rosters
-		addToRoster(ureqIdentity, identityToAdd, group);
+		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
@@ -894,6 +920,8 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerExecutor(){
 			public void execute() {
 				final BusinessGroup currBusinessGroup = loadBusinessGroup(group); // reload business group
+				SyncUserListTask syncIM = new SyncUserListTask(currBusinessGroup);
+				
 				for (final Identity identity : addIdentities) {
 					if (securityManager.isIdentityPermittedOnResourceable(identity, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_GUESTONLY)) {
 						response.getIdentitiesWithoutPermission().add(identity);
@@ -905,22 +933,23 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 						response.getIdentitiesAlreadyInGroup().add(identity);
 					} else {
 						// identity has permission and is not already in group => add it
-						addParticipant(ureqIdentity, identity, currBusinessGroup);
+						addParticipant(ureqIdentity, identity, currBusinessGroup, syncIM);
 						response.getAddedIdentities().add(identity);
 						log.audit("added identity '" + identity.getName() + "' to securitygroup with key " + currBusinessGroup.getPartipiciantGroup().getKey());
 					}
 				}
+				
+				syncIM(syncIM, group);
 			}});
 		return response;
 	}
 
-	@Override
-	public void removeParticipant(Identity ureqIdentity, Identity identity, BusinessGroup group) {
+	private void removeParticipant(Identity ureqIdentity, Identity identity, BusinessGroup group, SyncUserListTask syncIM) {
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(group);
 
 		securityManager.removeIdentityFromSecurityGroup(identity, group.getPartipiciantGroup());
 		// remove user from buddies rosters
-		removeFromRoster(identity, group);
+		syncIM.addUserToRemove(identity.getName());
 		//remove subscriptions if user gets removed
 		removeSubscriptions(identity, group);
 		
@@ -931,7 +960,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		// 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);
+			transferFirstIdentityFromWaitingToParticipant(ureqIdentity, group, syncIM);
 		}	
 		// send notification mail in your controller!
 		
@@ -939,14 +968,18 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 	
 	@Override
 	public void removeParticipants(final Identity ureqIdentity, final List<Identity> identities, final BusinessGroup group) {
+		final SyncUserListTask syncIM = new SyncUserListTask(group);
+		
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerExecutor(){
 			public void execute() {
 				for (Identity identity : identities) {
-				  removeParticipant(ureqIdentity, identity, group);
+				  removeParticipant(ureqIdentity, identity, group, syncIM);
 				  log.audit("removed identiy '" + identity.getName() + "' from securitygroup with key " + group.getPartipiciantGroup().getKey());
 				}
 			}
 		});
+		
+		syncIM(syncIM, group);
 	}
 
 	@Override
@@ -980,51 +1013,57 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 			
 			Long groupKey = currentMembership.getGroupKey();
 			BusinessGroup nextGroup = keyToGroupMap.get(groupKey);
-
-			final BusinessGroup currentGroup = nextGroup;
-			nextGroupMembership = CoordinatorManager.getInstance().getCoordinator().getSyncer()
-					.doInSync(currentGroup, new SyncerCallback<BusinessGroupMembershipViewImpl>(){
-				
-				public BusinessGroupMembershipViewImpl execute() {
-					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;
+			nextGroupMembership = removeGroupMembers(ureqIdentity, currentMembership, nextGroup, keyToIdentityMap, itMembership);
+		}
+	}
+	
+	private final BusinessGroupMembershipViewImpl removeGroupMembers(final Identity ureqIdentity, final BusinessGroupMembershipViewImpl currentMembership,
+			final BusinessGroup currentGroup, final Map<Long,Identity> keyToIdentityMap, final Iterator<BusinessGroupMembershipViewImpl> itMembership) {
+		
+		final SyncUserListTask syncIM = new SyncUserListTask(currentGroup);
+		BusinessGroupMembershipViewImpl next=  CoordinatorManager.getInstance().getCoordinator().getSyncer()
+				.doInSync(currentGroup, new SyncerCallback<BusinessGroupMembershipViewImpl>(){
+			
+			public BusinessGroupMembershipViewImpl execute() {
+				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(currentGroup.getKey().equals(membership.getGroupKey())) {
-							Identity id = keyToIdentityMap.get(membership.getIdentityKey());
-							if(membership.getOwnerGroupKey() != null) {
-								removeOwner(ureqIdentity, id, currentGroup);
-							}
-							if(membership.getParticipantGroupKey() != null) {
-								removeParticipant(ureqIdentity, id, currentGroup);
-							}
-							if(membership.getWaitingGroupKey() != null) {
-								removeFromWaitingList(ureqIdentity, id, currentGroup);
-							}
-						} else {
-							return membership;
+						if(membership.getParticipantGroupKey() != null) {
+							removeParticipant(ureqIdentity, id, currentGroup, syncIM);
 						}
-					} while (itMembership.hasNext());
-
-					return null;
-				}
-			});
+						if(membership.getWaitingGroupKey() != null) {
+							removeFromWaitingList(ureqIdentity, id, currentGroup);
+						}
+					} else {
+						return membership;
+					}
+				} while (itMembership.hasNext());
 
-		}
+				return null;
+			}
+		});
+		
+		syncIM(syncIM, currentGroup);
+		return next;
 	}
 
-	@Override
-	public void addToWaitingList(Identity ureqIdentity, Identity identity, BusinessGroup group) {
+	private void addToWaitingList(Identity ureqIdentity, Identity identity, BusinessGroup group) {
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(group);
 		securityManager.addIdentityToSecurityGroup(identity, group.getWaitingGroup());
 
@@ -1064,8 +1103,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		return response;
 	}
 
-	@Override
-	public void removeFromWaitingList(Identity ureqIdentity, Identity identity, BusinessGroup group) {
+	private final void removeFromWaitingList(Identity ureqIdentity, Identity identity, BusinessGroup group) {
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(group);
 		securityManager.removeIdentityFromSecurityGroup(identity, group.getWaitingGroup());
 		// notify currently active users of this business group
@@ -1106,13 +1144,14 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 			final BusinessGroup currBusinessGroup) {
 		
 		final BusinessGroupAddResponse response = new BusinessGroupAddResponse();
+		final SyncUserListTask syncIM = new SyncUserListTask(currBusinessGroup);
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(currBusinessGroup,new SyncerExecutor(){
 			public void execute() {
 				for (final Identity identity : identities) {
 					// check if idenity is allready in participant
 					if (!securityManager.isIdentityInSecurityGroup(identity,currBusinessGroup.getPartipiciantGroup()) ) {
 						// Idenity is not in participant-list => move idenity from waiting-list to participant-list
-						addParticipant(ureqIdentity, identity, currBusinessGroup);
+						addParticipant(ureqIdentity, identity, currBusinessGroup, syncIM);
 						removeFromWaitingList(ureqIdentity, identity, currBusinessGroup);
 						response.getAddedIdentities().add(identity);
 						// notification mail is handled in controller
@@ -1121,6 +1160,8 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 					}
 				}
 			}});
+		
+		syncIM(syncIM, currBusinessGroup);
 		return response;
 	}
 
@@ -1165,10 +1206,11 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 
 				BusinessGroup reloadedGroup = loadBusinessGroup(group);
 				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(null, identity, reloadedGroup);
+					addParticipant(null, identity, reloadedGroup, syncIM);
 					enrollStatus.setEnrolled(BGMembership.participant);
 					log.info("doEnroll (reservation) - setIsEnrolled ", identity.getName());
 					if(reservation != null) {
@@ -1191,17 +1233,18 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 						}
 					} else {
 						//enough place
-						addParticipant(null, identity, reloadedGroup);
+						addParticipant(null, identity, reloadedGroup, syncIM);
 						enrollStatus.setEnrolled(BGMembership.participant);
 						log.info("doEnroll - setIsEnrolled ", identity.getName());
 					}
 				} else {
 					if (log.isDebug()) log.debug("doEnroll as participant beginTransaction");
-					addParticipant(null, identity, reloadedGroup);
+					addParticipant(null, identity, reloadedGroup, 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;
 			}				
@@ -1224,9 +1267,11 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		List<BusinessGroup> groups = loadBusinessGroups(ownerKeys);	
 		for (BusinessGroup group : groups) {
 			if (group != null && !securityManager.isIdentityInSecurityGroup(ident, group.getOwnerGroup())){
-//				seems not to work, but would be the way to go!
-//				ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(group));
-				addOwner(addingIdentity, ident, group);
+				//seems not to work, but would be the way to go!
+				//ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(group));
+				SyncUserListTask syncIM = new SyncUserListTask(group);
+				addOwner(addingIdentity, ident, group, syncIM);
+				syncIM(syncIM, group);
 				ownerGroupnames += group.getName() + ", ";
 				addToAnyGroup = true;
 				if (!notifyAboutAdd.contains(group.getKey()) && mailKeys.contains(group.getKey())) notifyAboutAdd.add(group.getKey());
@@ -1241,12 +1286,14 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		for (BusinessGroup group : participantGroups) {
 			if (group != null && !securityManager.isIdentityInSecurityGroup(ident, group.getPartipiciantGroup())) {
 				final BusinessGroup toAddGroup = group;
-//				seems not to work, but would be the way to go!
-//				ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(group));
+				final SyncUserListTask syncIM = new SyncUserListTask(group);
+				//seems not to work, but would be the way to go!
+				//ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(group));
 				CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerExecutor(){
 					public void execute() {
-						addParticipant(addingIdentity, ident, toAddGroup);
+						addParticipant(addingIdentity, ident, toAddGroup, syncIM);
 					}});
+				syncIM(syncIM, group);
 				participantGroupnames += group.getName() + ", ";
 				addToAnyGroup = true;
 				if (!notifyAboutAdd.contains(group.getKey()) && mailKeys.contains(group.getKey())) notifyAboutAdd.add(group.getKey());
@@ -1275,7 +1322,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 	}
 
 
-	private void transferFirstIdentityFromWaitingToParticipant(Identity ureqIdentity, BusinessGroup group) {
+	private void transferFirstIdentityFromWaitingToParticipant(Identity ureqIdentity, BusinessGroup group, SyncUserListTask syncIM) {
 		CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(group);
 		// Check if waiting-list is enabled and auto-rank-up
 		if (group.getWaitingListEnabled() != null && group.getWaitingListEnabled().booleanValue()
@@ -1307,7 +1354,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 							//            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);
-							addParticipant(ureqIdentity, firstWaitingListIdentity, group);
+							addParticipant(ureqIdentity, firstWaitingListIdentity, group, syncIM);
 							removeFromWaitingList(ureqIdentity, firstWaitingListIdentity, group);
 						} finally {
 							ThreadLocalUserActivityLogger.setStickyActionType(formerStickyActionType);
@@ -1330,21 +1377,25 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 			log.warn("Called method transferFirstIdentityFromWaitingToParticipant but waiting-list or autoCloseRanks is disabled.");
 		}
 	}
-
-	private void addToRoster(Identity ureqIdentity, Identity identity, BusinessGroup group) {
-		if (InstantMessagingModule.isEnabled()) {
+	
+	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))) { 
-				String groupID = InstantMessagingModule.getAdapter().createChatRoomString(group);
-				String groupDisplayName = group.getName();
+
 				//course group enrolment is time critial so we move this in an separate thread and catch all failures 
-				TaskExecutorManager.getInstance().runTask(new SyncSingleUserTask(ureqIdentity, groupID, groupDisplayName, identity));
+				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) {
@@ -1353,10 +1404,10 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		return tools.isToolEnabled(CollaborationTools.TOOL_CHAT);
 	}
 	
-	public void removeOwner(Identity ureqIdentity, Identity identityToRemove, BusinessGroup group) {
+	private void removeOwner(Identity ureqIdentity, Identity identityToRemove, BusinessGroup group, SyncUserListTask syncIM) {
 		securityManager.removeIdentityFromSecurityGroup(identityToRemove, group.getOwnerGroup());
 		// remove user from buddies rosters
-		removeFromRoster(identityToRemove, group);
+		syncIM.addUserToRemove(identityToRemove.getName());
 		
 		//remove subsciptions if user gets removed
 		removeSubscriptions(identityToRemove, group);
@@ -1373,10 +1424,11 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 	
 	@Override
 	public void removeOwners(Identity ureqIdentity, Collection<Identity> identitiesToRemove, BusinessGroup group) {
-		//fxdiff VCRP-2: access control
+		SyncUserListTask syncIM = new SyncUserListTask(group);
 		for(Identity identityToRemove:identitiesToRemove) {
-			removeOwner(ureqIdentity, identityToRemove, group);
+			removeOwner(ureqIdentity, identityToRemove, group, syncIM);
 		}
+		syncIM(syncIM, group);
 	}
 	
 	private void removeSubscriptions(Identity identity, BusinessGroup group) {
@@ -1391,16 +1443,6 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 			}
 		}
 	}
-
-	private void removeFromRoster(Identity identity, BusinessGroup group) {
-		if (InstantMessagingModule.isEnabled()) {
-			// only remove user from roster if not in other security group
-			if (!isIdentityInBusinessGroup(identity, group)) {
-				String groupID = InstantMessagingModule.getAdapter().createChatRoomString(group);
-				InstantMessagingModule.getAdapter().removeUserFromFriendsRoster(groupID, identity.getName());
-			}
-		}
-	}
 	
 	@Override
 	@Transactional(readOnly=true)
diff --git a/src/main/java/org/olat/group/ui/main/BGTableItem.java b/src/main/java/org/olat/group/ui/main/BGTableItem.java
index 20efed0c806d1d538bc82edaaa3ee267c262168f..352de1b2a67d9686a74edba67490933cb3d7c26c 100644
--- a/src/main/java/org/olat/group/ui/main/BGTableItem.java
+++ b/src/main/java/org/olat/group/ui/main/BGTableItem.java
@@ -206,7 +206,7 @@ public class BGTableItem {
 		
 		public BGShort(BusinessGroup group) {
 			key = group.getKey();
-			name = group.getName().intern();
+			name = group.getName() == null ? null : group.getName().intern();
 			maxParticipants = group.getMaxParticipants();
 			waitingListEnabled = group.getWaitingListEnabled() == null ? false : group.getWaitingListEnabled().booleanValue();
 			autoCloseRanksEnabled = group.getAutoCloseRanksEnabled() == null ? false : group.getAutoCloseRanksEnabled().booleanValue();
@@ -214,7 +214,7 @@ public class BGTableItem {
 		
 		public BGShort(BusinessGroupView group) {
 			key = group.getKey();
-			name = group.getName().intern();
+			name = group.getName() == null ? null : group.getName().intern();
 			maxParticipants = group.getMaxParticipants();
 			numWaiting = group.getNumWaiting();
 			numOfOwners = group.getNumOfOwners();
diff --git a/src/main/java/org/olat/group/ui/portlet/BusinessGroupEntry.java b/src/main/java/org/olat/group/ui/portlet/BusinessGroupEntry.java
index 028ed69e0ad83251ce7b248ad2f6cd1f3bcfe82f..38e38b67688313813e4490d1b603861dc972a87b 100644
--- a/src/main/java/org/olat/group/ui/portlet/BusinessGroupEntry.java
+++ b/src/main/java/org/olat/group/ui/portlet/BusinessGroupEntry.java
@@ -34,12 +34,12 @@ class BusinessGroupEntry {
 	private final Date creationDate;
 	
 	public BusinessGroupEntry(String name, Date creationDate) {
-		this.name = name.intern();
+		this.name = name == null ? null : name.intern();
 		this.creationDate = creationDate;
 	}
 	
 	public BusinessGroupEntry(BusinessGroup group) {
-		this.name = group.getName().intern();
+		this.name = group.getName() == null ? "???" : group.getName().intern();
 		this.creationDate = group.getCreationDate();
 	}
 	
diff --git a/src/main/java/org/olat/instantMessaging/InstantMessaging.java b/src/main/java/org/olat/instantMessaging/InstantMessaging.java
index 1b7bb4bfc8aa44f862c7a71e4d312eb1db9a9fe9..29e568510c989479e6b9ce30def2c0b2bd55d103 100644
--- a/src/main/java/org/olat/instantMessaging/InstantMessaging.java
+++ b/src/main/java/org/olat/instantMessaging/InstantMessaging.java
@@ -26,6 +26,7 @@
 
 package org.olat.instantMessaging;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -52,14 +53,6 @@ public interface InstantMessaging {
 	 */
 	public boolean synchronizeBusinessGroupsWithIMServer();
 
-	/**
-	 * called when OLAT server is started and needs to sync the buddygroups with the IM server.
-	 * 
-	 * @param group
-	 */
-	//fxdiff: FXOLAT-219 decrease to load generated by synching learning groups (method not used)
-	//public boolean synchonizeBuddyRoster(BusinessGroup group);
-
 	/**
 	 * This method should only be called once as it creates the main controller and the groupchat controller for a single user
 	 * @param ureq
@@ -87,18 +80,13 @@ public interface InstantMessaging {
 	public GroupChatManagerController getGroupChatManagerController(UserRequest ureq);
 	
 	/**
-	 * @param groupOwnerUsername
+	 * Sync a group
 	 * @param groupId
 	 * @param groupname
-	 * @param addedUsername
-	 */
-	public boolean addUserToFriendsRoster(String groupOwnerUsername, String groupId, String groupname, String addedUsername);
-
-	/**
-	 * @param groupId
-	 * @param username an OLAT unique username
+	 * @param addedUsers
+	 * @param removedUsers
 	 */
-	public boolean removeUserFromFriendsRoster(String groupId, String username);
+	public boolean syncFriendsRoster(String groupId, String groupname, Collection<String> addedUsers, Collection<String> removedUsers);
 
 	/**
 	 * Delete roster group from instant messaging server
diff --git a/src/main/java/org/olat/instantMessaging/SmackInstantMessagingImpl.java b/src/main/java/org/olat/instantMessaging/SmackInstantMessagingImpl.java
index 9af6537f9a68c597a7393851f198e8a49da0b721..a1112b08bbc8a425f08190a868294975dcc736c6 100644
--- a/src/main/java/org/olat/instantMessaging/SmackInstantMessagingImpl.java
+++ b/src/main/java/org/olat/instantMessaging/SmackInstantMessagingImpl.java
@@ -27,6 +27,7 @@
 package org.olat.instantMessaging;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -152,13 +153,44 @@ public class SmackInstantMessagingImpl extends LogDelegator implements InstantMe
 		boolean hasAccount = accountService.hasAccount(imUsername);
 		if (!hasAccount) clientManager.getInstantMessagingCredentialsForUser(addedUsername);
 		// we do not check whether a group already exists, we create it each time
+		
+		buddyGroupService.createSharedGroup(groupId, groupname);
+		logDebug("Adding user to roster group::" + groupId + " username: " + addedUsername);
+		
 		List<String> list = new ArrayList<String>();
 		list.add(groupOwnerUsername);
-		buddyGroupService.createSharedGroup(groupId, groupname, list);
+		list.add(imUsername);
+		return buddyGroupService.addUserToSharedGroup(groupId, list);
+	}
+	
+	@Override
+	public boolean syncFriendsRoster(String groupId, String groupname, Collection<String> addedUsers, Collection<String> removedUsers) {
+		List<String> addedIMUserList = new ArrayList<String>();
+		if(addedUsers != null) {
+			for(String addedUser:addedUsers) {
+				String imUsername = nameHelper.getIMUsernameByOlatUsername(addedUser);
+				addedIMUserList.add(imUsername);
+				boolean hasAccount = accountService.hasAccount(imUsername);
+				if (!hasAccount) {
+					clientManager.getInstantMessagingCredentialsForUser(imUsername);
+				}
+			}
+		}
 		
-		logDebug("Adding user to roster group::" + groupId + " username: " + addedUsername);
+		List<String> removedIMUserList = new ArrayList<String>();
+		if(removedUsers != null) {
+			for(String removedUser:removedUsers) {
+				removedIMUserList.add(nameHelper.getIMUsernameByOlatUsername(removedUser));
+			}
+		}
 		
-		return buddyGroupService.addUserToSharedGroup(groupId, imUsername);
+		boolean allOk = true;
+		if(!addedIMUserList.isEmpty()) {
+			allOk = buddyGroupService.createSharedGroup(groupId, groupname);
+			allOk = buddyGroupService.addUserToSharedGroup(groupId, addedIMUserList);
+		}
+		allOk = buddyGroupService.removeUsersFromSharedGroup(groupId, removedIMUserList);
+		return allOk;
 	}
 
 	/**
@@ -319,8 +351,10 @@ public class SmackInstantMessagingImpl extends LogDelegator implements InstantMe
 		}
 		String groupId = InstantMessagingModule.getAdapter().createChatRoomString(group);
 		if (users.size() > 0 ) { // only sync groups with users
-			if (!buddyGroupService.createSharedGroup(groupId, group.getName(), usernames)){
+			if (!buddyGroupService.createSharedGroup(groupId, group.getName())){
 				logError("could not create shared group: "+groupId, null);
+			} else {
+				buddyGroupService.addUserToSharedGroup(groupId, usernames);
 			}
 			logDebug("synchronizing group::" + group.toString());
 		} else {
diff --git a/src/main/java/org/olat/instantMessaging/rosterandchat/ChatController.java b/src/main/java/org/olat/instantMessaging/rosterandchat/ChatController.java
index 05d902fe730e237ebda0179ae5f294a60a620939..978a53f8630fb9b81272bd1f6ccf353655f903d5 100644
--- a/src/main/java/org/olat/instantMessaging/rosterandchat/ChatController.java
+++ b/src/main/java/org/olat/instantMessaging/rosterandchat/ChatController.java
@@ -29,7 +29,6 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -50,7 +49,6 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.gui.control.floatingresizabledialog.FloatingResizableDialogController;
 import org.olat.core.id.Identity;
-import org.olat.core.id.OLATResourceable;
 import org.olat.core.id.UserConstants;
 import org.olat.core.util.event.EventBus;
 import org.olat.core.util.event.GenericEventListener;
@@ -62,8 +60,6 @@ import org.olat.instantMessaging.InstantMessagingEvent;
 import org.olat.instantMessaging.InstantMessagingModule;
 import org.olat.instantMessaging.groupchat.SendMessageForm;
 
-import bsh.This;
-
 /**
  * Description:<br />
  * Controller for a single Chat in a floating window
diff --git a/src/main/java/org/olat/instantMessaging/syncservice/InstantMessagingGroupSynchronisation.java b/src/main/java/org/olat/instantMessaging/syncservice/InstantMessagingGroupSynchronisation.java
index c4add3fa4eecb20008716d139cf34060e4bc0a4e..8eb1ba17f4f4745c778c82ced96b217d9c1dfebc 100644
--- a/src/main/java/org/olat/instantMessaging/syncservice/InstantMessagingGroupSynchronisation.java
+++ b/src/main/java/org/olat/instantMessaging/syncservice/InstantMessagingGroupSynchronisation.java
@@ -24,7 +24,7 @@
 */
 package org.olat.instantMessaging.syncservice;
 
-import java.util.List;
+import java.util.Collection;
 
 
 public interface InstantMessagingGroupSynchronisation {
@@ -37,51 +37,38 @@ public interface InstantMessagingGroupSynchronisation {
 	 * @param displayName name shown in the roster
 	 * @param initialMembers array with olat usernames
 	 */
-	public abstract boolean createSharedGroup(String groupId, String displayName, List<String> initialMembers);
+	public boolean createSharedGroup(String groupId, String displayName);
 	
-	/**
-	 * creates an empty shared group
-	 * @param groupId
-	 * @param displayName
-	 */
-	public abstract boolean createSharedGroup(String groupId, String displayName);
-
 	/**
 	 * Rename shared buddy group on the IM server
 	 * 
 	 * @param groupId
 	 * @param displayName
 	 */
-	public abstract boolean renameSharedGroup(String groupId, String displayName);
+	public boolean renameSharedGroup(String groupId, String displayName);
 
 	/**
 	 * @param groupId
 	 */
-	public abstract boolean deleteSharedGroup(String groupId);
+	public boolean deleteSharedGroup(String groupId);
 
 	/**
 	 * 
 	 * @param groupId
 	 * @param user
 	 */
-	public abstract boolean addUserToSharedGroup(String groupId, String username);
-
-	/**
-	 * @param groupId
-	 * @param users list of usernames
-	 */
-	public abstract boolean addUsersToSharedGroup(String groupId, List<String> usernames);
+	public boolean addUserToSharedGroup(String groupId, Collection<String> users);
 
 	/**
 	 * @param groupId
 	 * @param username
 	 */
-	public abstract boolean removeUserFromSharedGroup(String groupId, String username);
+	public boolean removeUserFromSharedGroup(String groupId, String username);
 
 	/**
 	 * @param groupId
 	 * @param users
 	 */
-	public abstract boolean removeUsersFromSharedGroup(String groupId, String[] users);
+	public boolean removeUsersFromSharedGroup(String groupId, Collection<String> users);
 
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/instantMessaging/syncservice/RemoteGroupCreationOverXMPP.java b/src/main/java/org/olat/instantMessaging/syncservice/RemoteGroupCreationOverXMPP.java
index 983550e4f2d87df6f95ed9c73e7a084e93e39b89..9e4c4b9504cd041292f782a08c5aa41d39ffad50 100644
--- a/src/main/java/org/olat/instantMessaging/syncservice/RemoteGroupCreationOverXMPP.java
+++ b/src/main/java/org/olat/instantMessaging/syncservice/RemoteGroupCreationOverXMPP.java
@@ -24,8 +24,7 @@
 */
 package org.olat.instantMessaging.syncservice;
 
-import java.util.Iterator;
-import java.util.List;
+import java.util.Collection;
 
 import org.jivesoftware.smack.PacketCollector;
 import org.jivesoftware.smack.SmackConfiguration;
@@ -66,47 +65,25 @@ public class RemoteGroupCreationOverXMPP implements InstantMessagingGroupSynchro
 		this.adminUser = adminUser;
 	}
 
-	/**
-	 * @see org.olat.instantMessaging.InstantMessagingGroupSynchronisation#addUserToSharedGroup(java.lang.String, org.olat.core.id.Identity)
-	 */
-	public boolean addUserToSharedGroup(String groupId, String username) {
-		boolean statusOk = sendPacket(new AddUserToGroup(username, groupId));
-		if (!statusOk) {
-			log.error("failed to add user to shard group: "+groupId + " username: "+username);
-		}
-		return statusOk;
-		
-	}
-
-	/**
-	 * @see org.olat.instantMessaging.InstantMessagingGroupSynchronisation#addUsersToSharedGroup(java.lang.String, java.util.List)
-	 */
-	public boolean addUsersToSharedGroup(String groupId, List<String> users) {
-		boolean statusOk = true;
-		for (Iterator<String> iterator = users.iterator(); iterator.hasNext();) {
-			String username = iterator.next();
-			if (!sendPacket(new AddUserToGroup(username, groupId))) {
-				log.error("adding user to shard group failed. group: "+groupId+" and user: "+username);
-				statusOk = false;
-			}
-		}
-		return statusOk;
-	}
-
 	/**
 	 * @see org.olat.instantMessaging.InstantMessagingGroupSynchronisation#createSharedGroup(java.lang.String, java.lang.String, java.util.List)
 	 */
-	public boolean createSharedGroup(String groupId, String displayName, List<String> initialMembers) {
-		//TODO:gs: pass description from groups as well
+	@Override
+	public boolean createSharedGroup(String groupId, String displayName) {
 		boolean statusOk = true;
 		if (!sendPacket(new GroupCreate(groupId, displayName, null))) {
 			log.error("could not create shared group: "+groupId);
 			statusOk = false;
 		}
-		if (initialMembers != null) {
+		return statusOk;
+	}
+	
+	@Override
+	public boolean addUserToSharedGroup(String groupId, Collection<String> users) {
+		boolean statusOk = true;
+		if (users != null && !users.isEmpty()) {
 			int cnt=0;
-			for (Iterator<String> iterator = initialMembers.iterator(); iterator.hasNext();) {
-				String username = iterator.next();
+			for (String username : users) {
 				if (!sendPacket(new AddUserToGroup(username, groupId))) {
 					log.error("adding user to shard group failed. group: "+groupId+" and user: "+username);
 					statusOk = false;
@@ -124,14 +101,11 @@ public class RemoteGroupCreationOverXMPP implements InstantMessagingGroupSynchro
 		}
 		return statusOk;
 	}
-	
-	public boolean createSharedGroup(String groupId, String displayName) {
-		return createSharedGroup(groupId, displayName, null);
-	}
 
 	/**
 	 * @see org.olat.instantMessaging.InstantMessagingGroupSynchronisation#deleteSharedGroup(java.lang.String)
 	 */
+	@Override
 	public boolean deleteSharedGroup(String groupId) {
 		boolean statusOk = sendPacket(new GroupDelete(groupId));
 		if(!statusOk) {
@@ -144,6 +118,7 @@ public class RemoteGroupCreationOverXMPP implements InstantMessagingGroupSynchro
 	/**
 	 * @see org.olat.instantMessaging.InstantMessagingGroupSynchronisation#removeUserFromSharedGroup(java.lang.String, java.lang.String)
 	 */
+	@Override
 	public boolean removeUserFromSharedGroup(String groupId, String username) {
 		boolean statusOk =  sendPacket(new RemoveUserFromGroup(username, groupId));
 		if(!statusOk) {
@@ -155,22 +130,26 @@ public class RemoteGroupCreationOverXMPP implements InstantMessagingGroupSynchro
 	/**
 	 * @see org.olat.instantMessaging.InstantMessagingGroupSynchronisation#removeUsersFromSharedGroup(java.lang.String, java.lang.String[])
 	 */
-	public boolean removeUsersFromSharedGroup(String groupId, String[] users) {
-		for (int i = 0; i < users.length; i++) {
-			String username = users[i];
-			if (!sendPacket(new RemoveUserFromGroup(username, groupId))) {
+	@Override
+	public boolean removeUsersFromSharedGroup(String groupId, Collection<String> users) {
+		boolean allOk = true;
+		for (String username : users) {
+			Boolean ok = sendPacket(new RemoveUserFromGroup(username, groupId));
+			if (!ok) {
 				log.error("removing user from shared group failed. group: "+groupId+" and user: "+username);
 			}
+			allOk &= ok;
 		}
-		return true;
+		return allOk;
 	}
 
 	/**
 	 * @see org.olat.instantMessaging.InstantMessagingGroupSynchronisation#renameSharedGroup(java.lang.String, java.lang.String)
 	 */
+	@Override
 	public boolean renameSharedGroup(String groupId, String displayName) {
 		//if group exists it will be renamed
-		return createSharedGroup(groupId, displayName, null);
+		return createSharedGroup(groupId, displayName);
 	}
 	
 	private boolean sendPacket(IQ packet) {
diff --git a/src/main/java/org/olat/instantMessaging/syncservice/SyncSingleUserTask.java b/src/main/java/org/olat/instantMessaging/syncservice/SyncSingleUserTask.java
deleted file mode 100644
index f3cc65e78efd0ab7eb2518810cb25a7021bd54b7..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/instantMessaging/syncservice/SyncSingleUserTask.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <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
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <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>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.
-*/
-package org.olat.instantMessaging.syncservice;
-
-import org.olat.core.id.Identity;
-import org.olat.instantMessaging.InstantMessagingModule;
-
-/**
- * Description:<br>
- * Asynchronously synchronizes a single user with the instant messaging server group administration.
- * This has to be done out of scope of the current thread to not disturb the enrolment mechanism which is very time critical
- * when hundred of people try to enrol to a certain group within a short time.
- * 
- * <P>
- * Initial Date:  30.04.2008 <br>
- * @author guido
- */
-public class SyncSingleUserTask implements Runnable {
-	
-	private Identity groupowner;
-	private String groupId;
-	private String groupDisplayName;
-	private Identity userToAdd;
-	
-	/**
-	 * 
-	 * @param groupowner
-	 * @param groupId
-	 * @param groupDisplayName
-	 * @param userToAdd
-	 */
-	public SyncSingleUserTask(Identity groupowner, String groupId, String groupDisplayName, Identity userToAdd) {
-		super();
-		this.groupowner = groupowner;
-		this.groupId = groupId;
-		this.groupDisplayName = groupDisplayName;
-		this.userToAdd = userToAdd;
-	}
-
-
-
-	/**
-	 * @see java.lang.Runnable#run()
-	 */
-	public void run() {
-		InstantMessagingModule.getAdapter().addUserToFriendsRoster(groupowner.getName(), groupId, groupDisplayName, userToAdd.getName());
-	}
-
-}
diff --git a/src/main/java/org/olat/instantMessaging/syncservice/SyncUserListTask.java b/src/main/java/org/olat/instantMessaging/syncservice/SyncUserListTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..c589436266a87b14be74a45b138b4dc3d0160038
--- /dev/null
+++ b/src/main/java/org/olat/instantMessaging/syncservice/SyncUserListTask.java
@@ -0,0 +1,77 @@
+/**
+ * <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.instantMessaging.syncservice;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.olat.group.BusinessGroup;
+import org.olat.instantMessaging.InstantMessagingModule;
+
+/**
+ * A task to sync a list of users with a roster
+ * 
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class SyncUserListTask implements Runnable {
+	
+	private final String groupId;
+	private final String groupDisplayName;
+	private final Set<String> userToAdd = new HashSet<String>();
+	private final Set<String> userToRemove = new HashSet<String>();
+	
+	/**
+	 * 
+	 * @param group
+	 */
+	public SyncUserListTask(BusinessGroup group) {
+		groupId = InstantMessagingModule.getAdapter().createChatRoomString(group);
+		groupDisplayName = group.getName();
+	}
+	
+	public Set<String> getUserToAdd() {
+		return userToAdd;
+	}
+
+	public void addUserToAdd(String username) {
+		userToAdd.add(username);
+	}
+
+	public Set<String> getUserToRemove() {
+		return userToRemove;
+	}
+	
+	public void addUserToRemove(String username) {
+		userToRemove.add(username);
+	}
+	
+	public boolean isEmpty() {
+		return userToRemove.isEmpty() && userToAdd.isEmpty();
+	}
+
+	/**
+	 * @see java.lang.Runnable#run()
+	 */
+	public void run() {
+		InstantMessagingModule.getAdapter().syncFriendsRoster(groupId, groupDisplayName, userToAdd, userToRemove);
+	}
+
+}
diff --git a/src/main/java/org/olat/instantMessaging/syncservice/TestNamespaceHandlers.java b/src/main/java/org/olat/instantMessaging/syncservice/TestNamespaceHandlers.java
deleted file mode 100644
index 4fb6360512e4f333bc618c3fb683dee4dbfd83e2..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/instantMessaging/syncservice/TestNamespaceHandlers.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <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
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <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>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.
-*/
-package org.olat.instantMessaging.syncservice;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import org.jivesoftware.smack.ConnectionConfiguration;
-import org.jivesoftware.smack.PacketCollector;
-import org.jivesoftware.smack.SmackConfiguration;
-import org.jivesoftware.smack.XMPPConnection;
-import org.jivesoftware.smack.XMPPException;
-import org.jivesoftware.smack.filter.PacketIDFilter;
-import org.jivesoftware.smack.packet.IQ;
-import org.jivesoftware.smack.provider.ProviderManager;
-
-
-public class TestNamespaceHandlers {
-
-	/**
-	 * @param args
-	 */
-	public static void main(String[] args) {
-		ConnectionConfiguration connConfig = new ConnectionConfiguration("idlnxgs.unizh.ch", 5222);
-		connConfig.setNotMatchingDomainCheckEnabled(false);
-		connConfig.setSASLAuthenticationEnabled(false);
-		connConfig.setReconnectionAllowed(false);
-		connConfig.setDebuggerEnabled(true);
-
-		ProviderManager providerMgr = ProviderManager.getInstance();
-		providerMgr.addIQProvider("query", UserCreate.NAMESPACE, new UserCreate.Provider());
-		providerMgr.addIQProvider("query", UserDelete.NAMESPACE, new UserCreate.Provider());
-		
-		providerMgr.addIQProvider("query", GroupCreate.NAMESPACE, new GroupCreate.Provider());
-		providerMgr.addIQProvider("query", GroupDelete.NAMESPACE, new GroupDelete.Provider());
-		
-		providerMgr.addIQProvider("query", AddUserToGroup.NAMESPACE, new AddUserToGroup.Provider());
-		providerMgr.addIQProvider("query", RemoveUserFromGroup.NAMESPACE, new RemoveUserFromGroup.Provider());
-		
-		providerMgr.addIQProvider("query", SessionCount.NAMESPACE, new SessionCount.Provider());
-		
-		providerMgr.addIQProvider("query", SessionItems.NAMESPACE, new SessionItems.Provider());
-		
-		providerMgr.addIQProvider("query", UserCheck.NAMESPACE, new UserCheck.Provider());
-		
-		providerMgr.addIQProvider("query", PluginVersion.NAMESPACE, new PluginVersion.Provider());
-		
-		XMPPConnection connection = new XMPPConnection(connConfig);
-		
-		
-		try {
-			connection.connect();
-			connection.login("admin", "admin");
-			
-			String username="demo";
-			String pw="test";
-			String email="test@test.ch";
-			String fullName="gregor grunz";
-			int i = 7;
-			
-			List packets = new ArrayList();
-//			for (int j = 1; j < 300; j++) {
-//				packets.add(new UserDelete(username+j));
-//				
-//			}
-			//packets.add(new UserCreate(username, pw, email, fullName));
-			packets.add(new UserCheck("administrator"));
-			packets.add(new UserCheck("author"));
-			//packets.add(new UserCheck("demo3"));
-			//packets.add(new UserCheck("demo4"));
-			//packets.add(new UserCheck("demo5"));
-			
-			//packets.add(new PluginVersion());
-			
-			//packets.add(new GroupCreate("testgroup","testgroup bla",  "this is the testgroup"));
-			//packets.add(new GroupCreate("testgroup","testgroup bla",  "this is the testgroup"));
-			//packets.add(new AddUserToGroup(username+i, "testgroup"));
-			//packets.add(new AddUserToGroup("admin", "testgroup"));
-			//packets.add(new RemoveUserFromGroup(username+i, "testgroup"));
-			//packets.add(new GroupDelete("testgroup"));
-			//packets.add(new GroupDelete("testgroup"));
-			//packets.add(new UserDelete(username+i));
-			//packets.add(new SessionCount());
-			//packets.add(new SessionItems());
-			
-			
-			
-			Thread.sleep(1000);
-			long start = System.currentTimeMillis();
-			for (Iterator iterator = packets.iterator(); iterator.hasNext();) {
-				IQ iqPacket = (IQ) iterator.next();
-				iqPacket.setFrom(connection.getUser());
-				PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(iqPacket.getPacketID()));
-				connection.sendPacket(iqPacket);
-				IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
-				collector.cancel();
-				
-				if (response == null) {
-					throw new XMPPException("No response from server on status set.");
-				}
-				if (response.getError() != null) {
-					throw new XMPPException("Error . Cause: "+response.getError().getMessage());
-				} else if (response.getType() == IQ.Type.ERROR) {
-					System.out.println("could not create user....");
-					throw new XMPPException("Error . Cause: "+response.getError().getMessage());
-				}
-				if (response instanceof SessionCount) {
-					SessionCount sCount = (SessionCount) response;
-					System.out.println("number of sessions: "+sCount.getNumberOfSessions());
-				}
-				
-				if (response instanceof IMSessionItems) {
-					IMSessionItems items = (IMSessionItems) response;
-					List<IMSessionItems.Item> l = items.getItems();
-					for (Iterator iterator2 = l.iterator(); iterator2.hasNext();) {
-						IMSessionItems.Item item = (IMSessionItems.Item) iterator2.next();
-						System.out.println("username: "+item.getUsername() );
-						System.out.println("presence msg: "+item.getPresenceMsg() );
-						System.out.println("status: "+item.getPresenceStatus() );
-						System.out.println("last acivity: "+item.getLastActivity() );
-						System.out.println("logintime: "+item.getLoginTime() );
-					}
-				}
-				if (response instanceof UserCheck) {
-					UserCheck check = (UserCheck) response;
-					System.out.println("has account responses: "+check.hasAccount());
-				}
-				if (response instanceof PluginVersion) {
-					PluginVersion version = (PluginVersion) response;
-					System.out.println("plugin version is: "+version.getVersion());
-				}
-				
-				
-				
-			}
-			long stop = System.currentTimeMillis();
-			System.out.println("time..."+(stop-start));
-	        
-	        
-	        Thread.sleep(1000);
-	        
-			connection.disconnect();
-			
-			Thread.sleep(10000);
-			System.exit(0);
-		} catch (XMPPException e) {
-			System.out.println("error occured 1: "+e);
-		} catch (InterruptedException e) {
-			System.out.println("error occured 2: "+e);
-			e.printStackTrace();
-		} catch (Exception e) {
-			System.out.println("error occured 3 : "+e);
-		} catch (Throwable e) {
-			System.out.println("error occured 4: "+e);
-		}
-		try {
-			Thread.sleep(30000);
-			System.exit(0);
-		} catch (InterruptedException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-	}
-
-}
diff --git a/src/main/java/org/olat/restapi/group/LearningGroupWebService.java b/src/main/java/org/olat/restapi/group/LearningGroupWebService.java
index 9ef4fec4543a05bd0da81c9437bba221d119a506..7527270fccfe87c7a3582014b5f0fcdf8d75039e 100644
--- a/src/main/java/org/olat/restapi/group/LearningGroupWebService.java
+++ b/src/main/java/org/olat/restapi/group/LearningGroupWebService.java
@@ -55,9 +55,6 @@ import org.olat.core.id.Identity;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.StringHelper;
-import org.olat.core.util.coordinate.CoordinatorManager;
-import org.olat.core.util.coordinate.SyncerCallback;
-import org.olat.core.util.coordinate.SyncerExecutor;
 import org.olat.core.util.notifications.SubscriptionContext;
 import org.olat.core.util.vfs.Quota;
 import org.olat.core.util.vfs.QuotaManager;
@@ -65,6 +62,7 @@ import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
 import org.olat.core.util.vfs.restapi.VFSWebServiceSecurityCallback;
 import org.olat.core.util.vfs.restapi.VFSWebservice;
 import org.olat.group.BusinessGroup;
+import org.olat.group.BusinessGroupAddResponse;
 import org.olat.group.BusinessGroupService;
 import org.olat.group.model.DisplayMembers;
 import org.olat.group.model.SearchBusinessGroupParams;
@@ -522,15 +520,8 @@ public class LearningGroupWebService {
 			if(identity == null || group == null) {
 				return Response.serverError().status(Status.NOT_FOUND).build();
 			}
-		//TODO gsync
-			CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerCallback<Boolean>(){
-				public Boolean execute() {
-					List<Identity> identityToAdd = Collections.singletonList(identity);
-					bgs.addOwners(ureq.getIdentity(), identityToAdd, group);
-					return Boolean.TRUE;
-				}
-			});// end of doInSync
-			
+
+			bgs.addOwners(ureq.getIdentity(), Collections.singletonList(identity), group);
 			return Response.ok().build();
 		} catch (Exception e) {
 			log.error("Trying to add an owner to a group", e);
@@ -574,21 +565,14 @@ public class LearningGroupWebService {
 			}
 			
 			final UserRequest ureq = RestSecurityHelper.getUserRequest(request);
-			
 			final BusinessGroupService bgs = CoreSpringFactory.getImpl(BusinessGroupService.class);
 			final BusinessGroup group = bgs.loadBusinessGroup(groupKey);
 			final Identity identity = BaseSecurityManager.getInstance().loadIdentityByKey(identityKey, false);
 			if(identity == null || group == null) {
 				return Response.serverError().status(Status.NOT_FOUND).build();
 			}
-		//TODO gsync
-			CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerCallback<Boolean>(){
-				public Boolean execute() {
-					bgs.removeOwners(ureq.getIdentity(), Collections.singletonList(identity), group);
-					return Boolean.TRUE;
-				}
-			});// end of doInSync
-
+			
+			bgs.removeOwners(ureq.getIdentity(), Collections.singletonList(identity), group);
 			return Response.ok().build();
 		} catch (Exception e) {
 			log.error("Trying to remove an owner to a group", e);
@@ -637,15 +621,14 @@ public class LearningGroupWebService {
 			if(identity == null || group == null) {
 				return Response.serverError().status(Status.NOT_FOUND).build();
 			}
-		//TODO gsync
-			CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerCallback<Boolean>(){
-				public Boolean execute() {
-					bgs.addParticipant(ureq.getIdentity(), identity, group);
-					return Boolean.TRUE;
-				}
-			});// end of doInSync
-			
-			return Response.ok().build();
+
+			BusinessGroupAddResponse state = bgs.addParticipants(ureq.getIdentity(), Collections.singletonList(identity), group);
+			if(state.getAddedIdentities().contains(identity)) {
+				return Response.ok().build();
+			} else if(state.getIdentitiesAlreadyInGroup().contains(identity)) {
+				return Response.ok().status(Status.NOT_MODIFIED).build();
+			}
+			return Response.serverError().status(Status.PRECONDITION_FAILED).build();
 		} catch (Exception e) {
 			log.error("Trying to add a participant to a group", e);
 			return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
@@ -693,12 +676,7 @@ public class LearningGroupWebService {
 			if(identity == null || group == null) {
 				return Response.serverError().status(Status.NOT_FOUND).build();
 			}
-			//TODO gsync
-			CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerExecutor(){
-				public void execute() {
-					bgs.removeParticipant(ureq.getIdentity(), identity, group);
-				}
-			});
+			bgs.removeParticipants(ureq.getIdentity(), Collections.singletonList(identity), group);
 
 			return Response.ok().build();
 		} catch (Exception e) {
diff --git a/src/test/java/org/olat/course/nodes/en/EnrollmentManagerTest.java b/src/test/java/org/olat/course/nodes/en/EnrollmentManagerTest.java
index 57ee89a4e497679efedf175d9d32141a7ed26594..66d29f75a0581d1bee6a82eacc65276ac9851f11 100644
--- a/src/test/java/org/olat/course/nodes/en/EnrollmentManagerTest.java
+++ b/src/test/java/org/olat/course/nodes/en/EnrollmentManagerTest.java
@@ -36,15 +36,22 @@ package org.olat.course.nodes.en;
 // DB.getInstance().loadObject(); püft ob schon in hibernate session.
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.Assert;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.olat.basesecurity.BaseSecurity;
 import org.olat.basesecurity.BaseSecurityManager;
+import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.persistence.DBFactory;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.control.WindowBackOffice;
@@ -63,6 +70,7 @@ import org.olat.core.logging.Tracing;
 import org.olat.core.util.Util;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.course.CourseFactory;
+import org.olat.course.groupsandrights.CourseGroupManager;
 import org.olat.course.nodes.ENCourseNode;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.course.run.environment.CourseEnvironment;
@@ -71,6 +79,7 @@ import org.olat.course.run.userview.UserCourseEnvironmentImpl;
 import org.olat.group.BusinessGroup;
 import org.olat.group.BusinessGroupService;
 import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
 import org.olat.test.JunitTestHelper;
 import org.olat.test.OlatTestCase;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -102,6 +111,10 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 	private BusinessGroupService businessGroupService;
 	@Autowired
 	private EnrollmentManager enrollmentManager;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private DB dbInstance;
 	
 	/**
 	 * @see junit.framework.TestCase#setUp()
@@ -116,13 +129,13 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 			Boolean enableWaitinglist = new Boolean(true);
 			Boolean enableAutoCloseRanks = new Boolean(true);
 			RepositoryEntry resource =  JunitTestHelper.createAndPersistRepositoryEntry();
-			System.out.println("testAddToWaitingListAndFireEvent: resource=" + resource);
+			log.info("testAddToWaitingListAndFireEvent: resource=" + resource);
 			bgWithWaitingList = businessGroupService.createBusinessGroup(id1, bgWithWaitingListName,
 					bgWithWaitingListDesc, -1, -1, enableWaitinglist, enableAutoCloseRanks, resource);
 			bgWithWaitingList.setMaxParticipants(new Integer(2));
-			System.out.println("TEST bgWithWaitingList=" + bgWithWaitingList);
-			System.out.println("TEST bgWithWaitingList.getMaxParticipants()=" + bgWithWaitingList.getMaxParticipants() );
-			System.out.println("TEST bgWithWaitingList.getWaitingListEnabled()=" + bgWithWaitingList.getWaitingListEnabled() );
+			log.info("TEST bgWithWaitingList=" + bgWithWaitingList);
+			log.info("TEST bgWithWaitingList.getMaxParticipants()=" + bgWithWaitingList.getMaxParticipants() );
+			log.info("TEST bgWithWaitingList.getWaitingListEnabled()=" + bgWithWaitingList.getWaitingListEnabled() );
 			// create mock objects
 			String PACKAGE = Util.getPackageName(EnrollmentManagerTest.class);
 			testTranslator = new PackageTranslator(PACKAGE, new Locale("de"));
@@ -142,7 +155,7 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 	 * Cancel enrollment. Check size after each step.
 	 */
 	@Test public void testEnroll() throws Exception {
-		System.out.println("testEnroll: start...");
+		log.info("testEnroll: start...");
 		ENCourseNode enNode = new ENCourseNode();
 		OLATResourceable ores = OresHelper.createOLATResourceableTypeWithoutCheck("TestCourse");
 		CourseEnvironment cenv = CourseFactory.createEmptyCourse(ores, "Test", "Test", "learningObjectives").getCourseEnvironment();
@@ -151,13 +164,13 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 		ienv.setIdentity(wg1);
 		UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(ienv, cenv);
 		CoursePropertyManager coursePropertyManager = userCourseEnv.getCourseEnvironment().getCoursePropertyManager();
-		System.out.println("enrollmentManager=" + enrollmentManager);
-		System.out.println("bgWithWaitingList=" + bgWithWaitingList);
+		log.info("enrollmentManager=" + enrollmentManager);
+		log.info("bgWithWaitingList=" + bgWithWaitingList);
 		assertTrue("bgWithWaitingList is null",bgWithWaitingList != null);
-		System.out.println("userCourseEnv=" + userCourseEnv);
-		System.out.println("userCourseEnv.getCourseEnvironment()=" + userCourseEnv.getCourseEnvironment());
+		log.info("userCourseEnv=" + userCourseEnv);
+		log.info("userCourseEnv.getCourseEnvironment()=" + userCourseEnv.getCourseEnvironment());
 		enrollmentManager.doEnroll(wg1,bgWithWaitingList, enNode, coursePropertyManager,this /*WindowControl mock*/,testTranslator,
-				new ArrayList()/*enrollableGroupNames*/, new ArrayList()/*enrollableAreaNames*/, userCourseEnv.getCourseEnvironment().getCourseGroupManager());	
+				new ArrayList<Long>()/*enrollableGroupNames*/, new ArrayList<Long>()/*enrollableAreaNames*/, userCourseEnv.getCourseEnvironment().getCourseGroupManager());	
 		assertTrue("Enrollment failed, user='wg1'", businessGroupService.isIdentityInBusinessGroup(wg1,bgWithWaitingList));	
 		int participantsCounter = BaseSecurityManager.getInstance().countIdentitiesOfSecurityGroup(bgWithWaitingList.getPartipiciantGroup());
 		assertTrue("Wrong number of participants," + participantsCounter , participantsCounter == 1);
@@ -167,7 +180,7 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 		userCourseEnv = new UserCourseEnvironmentImpl(ienv, cenv);
 		coursePropertyManager = userCourseEnv.getCourseEnvironment().getCoursePropertyManager();
 		enrollmentManager.doEnroll(wg2,bgWithWaitingList, enNode, coursePropertyManager,this /*WindowControl mock*/,testTranslator,
-				new ArrayList()/*enrollableGroupNames*/, new ArrayList()/*enrollableAreaNames*/, userCourseEnv.getCourseEnvironment().getCourseGroupManager());	
+				new ArrayList<Long>()/*enrollableGroupNames*/, new ArrayList<Long>()/*enrollableAreaNames*/, userCourseEnv.getCourseEnvironment().getCourseGroupManager());	
 		assertTrue("Enrollment failed, user='wg2'", businessGroupService.isIdentityInBusinessGroup(wg2,bgWithWaitingList));	
 		assertTrue("Enrollment failed, user='wg1'", businessGroupService.isIdentityInBusinessGroup(wg1,bgWithWaitingList));	
 		participantsCounter = BaseSecurityManager.getInstance().countIdentitiesOfSecurityGroup(bgWithWaitingList.getPartipiciantGroup());
@@ -178,7 +191,7 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 		userCourseEnv = new UserCourseEnvironmentImpl(ienv, cenv);
 		coursePropertyManager = userCourseEnv.getCourseEnvironment().getCoursePropertyManager();
 		enrollmentManager.doEnroll(wg3,bgWithWaitingList, enNode, coursePropertyManager,this /*WindowControl mock*/,testTranslator,
-				new ArrayList()/*enrollableGroupNames*/, new ArrayList()/*enrollableAreaNames*/, userCourseEnv.getCourseEnvironment().getCourseGroupManager());		
+				new ArrayList<Long>()/*enrollableGroupNames*/, new ArrayList<Long>()/*enrollableAreaNames*/, userCourseEnv.getCourseEnvironment().getCourseGroupManager());		
 		assertFalse("Wrong enrollment, user='wg3' is in PartipiciantGroup, must be on waiting-list", businessGroupService.isIdentityInBusinessGroup(wg3,bgWithWaitingList));	
 		assertFalse("Wrong enrollment, user='wg3' is in PartipiciantGroup, must be on waiting-list", BaseSecurityManager.getInstance().isIdentityInSecurityGroup(wg3, bgWithWaitingList.getPartipiciantGroup()));
 		assertTrue("Wrong enrollment, user='wg3' must be on waiting-list", BaseSecurityManager.getInstance().isIdentityInSecurityGroup(wg3, bgWithWaitingList.getWaitingGroup()));
@@ -228,18 +241,80 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 		waitingListCounter = BaseSecurityManager.getInstance().countIdentitiesOfSecurityGroup(bgWithWaitingList.getWaitingGroup());
 		assertTrue("Wrong number of waiting-list, must be 0, is " + waitingListCounter , waitingListCounter == 0);
 
-		System.out.println("testEnroll: done...");
+		log.info("testEnroll: done...");
 	}
 	
+	@Test
+	public void testConcurrentEnrollmentWithWaitingList() {
+		List<Identity> ids = new ArrayList<Identity>(30);	
+		for(int i=0; i<30; i++) {
+			Identity id = JunitTestHelper.createAndPersistIdentityAsUser("enroll-a-" + i + "-" + UUID.randomUUID().toString());
+			ids.add(id);
+		}
+		
+		ENCourseNode enNode = new ENCourseNode();
+		OLATResourceable ores = OresHelper.createOLATResourceableTypeWithoutCheck("TestEnrollmentCourse");
+		CourseEnvironment cenv = CourseFactory.createEmptyCourse(ores, "Test-Enroll", "Test", "Test enrollment with concurrent users").getCourseEnvironment();
+		BusinessGroup group = businessGroupService.createBusinessGroup(id1, "Enrollment", "Enroll", new Integer(1), new Integer(10), true, false, null);
+		Assert.assertNotNull(group);
+		dbInstance.commitAndCloseSession();
 
-	/**
-	 * @see junit.framework.TestCase#tearDown()
-	 */
-	@After public void tearDown() throws Exception {
+		final CountDownLatch doneSignal = new CountDownLatch(ids.size());
+		for(Identity id:ids) {
+			EnrollThread thread = new EnrollThread(id, group, enNode, cenv, doneSignal);
+			thread.start();
+		}
+		
 		try {
-			DBFactory.getInstance().closeSession();
-		} catch (Exception e) {
-			log.error("tearDown failed: ", e);
+			boolean interrupt = doneSignal.await(360, TimeUnit.SECONDS);
+			assertTrue("Test takes too long (more than 10s)", interrupt);
+		} catch (InterruptedException e) {
+			fail("" + e.getMessage());
+		}
+
+		List<Identity> enrolledIds = securityManager.getIdentitiesOfSecurityGroup(group.getPartipiciantGroup());
+		Assert.assertNotNull(enrolledIds);
+		Assert.assertEquals(10, enrolledIds.size());
+		
+		List<Identity> waitingIds = securityManager.getIdentitiesOfSecurityGroup(group.getWaitingGroup());
+		Assert.assertNotNull(waitingIds);
+		Assert.assertEquals(ids.size() - 10, waitingIds.size());
+	}
+	
+	
+
+	private class EnrollThread extends Thread {
+		private final ENCourseNode enNode;
+		private final Identity identity;
+		private final CourseEnvironment cenv;
+		private final BusinessGroup group;
+		private final CountDownLatch doneSignal;
+		
+		public EnrollThread(Identity identity, BusinessGroup group, ENCourseNode enNode, CourseEnvironment cenv, CountDownLatch doneSignal) {
+			this.enNode = enNode;
+			this.group = group;
+			this.identity = identity;
+			this.cenv = cenv;
+			this.doneSignal = doneSignal;
+		}
+
+		@Override
+		public void run() {
+			try {
+				IdentityEnvironment ienv = new IdentityEnvironment();
+				ienv.setIdentity(identity);
+				UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(ienv, cenv);
+				CoursePropertyManager coursePropertyManager = userCourseEnv.getCourseEnvironment().getCoursePropertyManager();
+				CourseGroupManager courseGroupManager = userCourseEnv.getCourseEnvironment().getCourseGroupManager();
+				
+				enrollmentManager.doEnroll(identity, group, enNode, coursePropertyManager, EnrollmentManagerTest.this /*WindowControl mock*/, testTranslator,
+						new ArrayList<Long>()/*enrollableGroupNames*/, new ArrayList<Long>()/*enrollableAreaNames*/, courseGroupManager);
+			} catch (Exception e) {
+				log.error("", e);
+			}	finally {
+				doneSignal.countDown();
+				DBFactory.getInstance().commitAndCloseSession();
+			}
 		}
 	}
 
diff --git a/src/test/java/org/olat/instantMessaging/IMUnitTest.java b/src/test/java/org/olat/instantMessaging/IMUnitTest.java
index 32ddd99e0b6085b6ae2bf68ad1c3563bc156c0ed..b5942fedf4a181249e718c779a3685363dbf28ae 100644
--- a/src/test/java/org/olat/instantMessaging/IMUnitTest.java
+++ b/src/test/java/org/olat/instantMessaging/IMUnitTest.java
@@ -29,6 +29,8 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import org.junit.After;
@@ -38,6 +40,7 @@ import org.olat.basesecurity.Authentication;
 import org.olat.basesecurity.BaseSecurityManager;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.persistence.DBFactory;
+import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.instantMessaging.ui.ConnectedUsersListEntry;
@@ -53,9 +56,11 @@ import org.olat.test.OlatTestCase;
  * @author guido
  */
 public class IMUnitTest extends OlatTestCase {
-	String testUserA = "anIdentity1";
-	String testUserB = "anIdentity2";
-	String testUserC = "testuser@thankyou2010.com";
+	private static final OLog log = Tracing.createLoggerFor(OlatTestCase.class);
+	
+	private final String testUserA = "anIdentity1";
+	private final String testUserB = "anIdentity2";
+	private final String testUserC = "testuser@thankyou2010.com";
 	
 	
 	/**
@@ -76,7 +81,7 @@ public class IMUnitTest extends OlatTestCase {
 			DB db = DBFactory.getInstance();
 			db.closeSession();
 		} catch (Exception e) {
-			Tracing.logError("Exception in tearDown(): " + e, IMUnitTest.class);
+			log.error("Exception in tearDown(): ", e);
 		}
 	}
 	
@@ -125,7 +130,10 @@ public class IMUnitTest extends OlatTestCase {
 			assertTrue(im.countConnectedUsers() >= 2); //there is may be as well an admin user connected
 			
 			//add user to roster
-			im.addUserToFriendsRoster(testUserA, groupId, groupName, testUserB);
+			List<String> userToAdd = new ArrayList<String>(2);
+			userToAdd.add(testUserA);
+			userToAdd.add(testUserB);
+			im.syncFriendsRoster(groupId, groupName, userToAdd, null);
 			Thread.sleep(1000);
 			assertEquals(1, imClientA.getRoster().getGroup(groupName).getEntryCount());
 			Thread.sleep(1000);
@@ -133,7 +141,7 @@ public class IMUnitTest extends OlatTestCase {
 			Thread.sleep(1000);
 			assertEquals(1, imClientA.getRoster().getGroup(groupName+"ABC").getEntryCount());
 			Thread.sleep(1000);
-			im.removeUserFromFriendsRoster(groupId, testUserB);
+			im.syncFriendsRoster(groupId, groupName, null, Collections.singletonList(testUserB));
 			Thread.sleep(1000);
 			im.deleteRosterGroup(groupId);
 			Thread.sleep(1000);
@@ -157,10 +165,8 @@ public class IMUnitTest extends OlatTestCase {
 			assertTrue(InstantMessagingModule.getAdapter().deleteAccount(testUserB));
 			
 			} catch (InterruptedException e) {
-				e.printStackTrace();
+				log.error("", e);
 			}
-			
 		}
 	}
-	
 }
diff --git a/src/test/java/org/olat/instantMessaging/IMUnitTestWithoutOLAT.java b/src/test/java/org/olat/instantMessaging/IMUnitTestWithoutOLAT.java
index cdfe82ecd9b6a2162c4ef84e80fa919e07d8184b..f143cbede363a42be3890a59861ea13fb8f5f34d 100644
--- a/src/test/java/org/olat/instantMessaging/IMUnitTestWithoutOLAT.java
+++ b/src/test/java/org/olat/instantMessaging/IMUnitTestWithoutOLAT.java
@@ -29,6 +29,9 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.junit.Test;
 import org.olat.test.MockServletContextWebContextLoader;
 import org.springframework.test.context.ContextConfiguration;
@@ -74,11 +77,17 @@ public class IMUnitTestWithoutOLAT extends AbstractJUnit4SpringContextTests {
 			assertFalse(im.hasAccount(tmpUsername));
 			assertTrue(im.createAccount(tmpUsername, password, fullname, username + j + email));
 			assertTrue(im.hasAccount(tmpUsername));
-			assertTrue(im.addUserToFriendsRoster(tmpUsermaster, groupId, groupname, tmpUsername));
+			List<String> userToAdd = new ArrayList<String>();
+			userToAdd.add(tmpUsermaster);
+			userToAdd.add(tmpUsername);
+			assertTrue(im.syncFriendsRoster(groupId, groupname, userToAdd, null));
 		}
 		assertTrue(im.renameRosterGroup(groupId, groupname + "CDEF"));
-		assertTrue(im.removeUserFromFriendsRoster(groupId, tmpUsermaster));
-		assertTrue(im.removeUserFromFriendsRoster(groupId, username + 1));
+		
+		List<String> userToRemove = new ArrayList<String>();
+		userToRemove.add(tmpUsermaster);
+		userToRemove.add(username + 1);
+		assertTrue(im.syncFriendsRoster(groupId, groupname, null, userToRemove));
 		assertTrue(im.deleteRosterGroup(groupId));
 		for (int j = 0; j < 4; j++) {
 			String tmpUsername = username + j;