From f3a455b8f3047fe6b7b81d6639f97b3f39c473ed Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 1 Apr 2015 09:58:01 +0200
Subject: [PATCH] OO-1483: optimize the enrollment run view to load the infos
 in 1 - 2 queries

---
 .../olat/course/nodes/en/ENRunController.java | 182 ++++++-----
 .../olat/course/nodes/en/ENWebService.java    |   4 +-
 .../course/nodes/en/EnrollmentManager.java    | 113 ++++++-
 .../olat/course/nodes/en/EnrollmentRow.java   | 115 +++++++
 .../en/EnrollmentTableModelWithMaxSize.java}  | 146 +++++----
 .../org/olat/group/BusinessGroupService.java  |   6 +-
 .../manager/BusinessGroupRelationDAO.java     |  17 +-
 .../manager/BusinessGroupServiceImpl.java     |  15 +-
 ...a => EnrollmentManagerConcurrentTest.java} |  11 +-
 .../nodes/en/EnrollmentManagerSerialTest.java | 303 ++++++++++++++++++
 .../java/org/olat/test/AllTestsJunit4.java    |   3 +-
 11 files changed, 755 insertions(+), 160 deletions(-)
 create mode 100644 src/main/java/org/olat/course/nodes/en/EnrollmentRow.java
 rename src/main/java/org/olat/{group/ui/BusinessGroupTableModelWithMaxSize.java => course/nodes/en/EnrollmentTableModelWithMaxSize.java} (55%)
 rename src/test/java/org/olat/course/nodes/en/{EnrollmentManagerTest.java => EnrollmentManagerConcurrentTest.java} (98%)
 create mode 100644 src/test/java/org/olat/course/nodes/en/EnrollmentManagerSerialTest.java

diff --git a/src/main/java/org/olat/course/nodes/en/ENRunController.java b/src/main/java/org/olat/course/nodes/en/ENRunController.java
index 7f3d4f7acf2..831003147e2 100644
--- a/src/main/java/org/olat/course/nodes/en/ENRunController.java
+++ b/src/main/java/org/olat/course/nodes/en/ENRunController.java
@@ -29,7 +29,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.olat.NewControllerFactory;
-import org.olat.core.CoreSpringFactory;
+import org.olat.basesecurity.GroupRoles;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.EscapeMode;
@@ -45,24 +45,27 @@ 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.Identity;
+import org.olat.core.id.OLATResourceable;
 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.event.GenericEventListener;
 import org.olat.core.util.resource.OLATResourceableJustBeforeDeletedEvent;
+import org.olat.core.util.resource.OresHelper;
 import org.olat.course.groupsandrights.CourseGroupManager;
 import org.olat.course.nodes.ENCourseNode;
 import org.olat.course.nodes.ObjectivesHelper;
+import org.olat.course.nodes.en.EnrollmentTableModelWithMaxSize.Stats;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.group.BusinessGroup;
 import org.olat.group.BusinessGroupService;
 import org.olat.group.area.BGAreaManager;
-import org.olat.group.ui.BusinessGroupTableModelWithMaxSize;
 import org.olat.group.ui.edit.BusinessGroupModifiedEvent;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.util.logging.activity.LoggingResourceable;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * Description:<BR>
@@ -90,23 +93,25 @@ public class ENRunController extends BasicController implements GenericEventList
 	private VelocityContainer enrollVC;
 	private ENCourseNode enNode;
 
-	private BusinessGroupTableModelWithMaxSize groupListModel;
+	private EnrollmentTableModelWithMaxSize groupListModel;
 	private TableController tableCtr;
 
-	// Managers
-	private final BGAreaManager areaManager;
-	private final EnrollmentManager enrollmentManager;
-	private final CourseGroupManager courseGroupManager;
-	private final BusinessGroupService businessGroupService;
-	private final CoursePropertyManager coursePropertyManager;
+	@Autowired
+	private BGAreaManager areaManager;
+	@Autowired
+	private EnrollmentManager enrollmentManager;
+	@Autowired
+	private BusinessGroupService businessGroupService;
+
+	private CourseGroupManager courseGroupManager;
+	private CoursePropertyManager coursePropertyManager;
 
-	// workflow variables
-	private List<BusinessGroup> enrolledGroups;
-	private List<BusinessGroup> waitingListGroups;
-	
 	private boolean cancelEnrollEnabled;
 	private int maxEnrollCount;
 	
+	//registered in event bus
+	private List<Long> registeredGroupKeys;
+	
 	/**
 	 * @param moduleConfiguration
 	 * @param ureq
@@ -123,38 +128,36 @@ public class ENRunController extends BasicController implements GenericEventList
 		addLoggingResourceable(LoggingResourceable.wrap(enNode));
 
 		// init managers
-		areaManager = CoreSpringFactory.getImpl(BGAreaManager.class);
-		enrollmentManager = CoreSpringFactory.getImpl(EnrollmentManager.class);
-		businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class);
 		courseGroupManager = userCourseEnv.getCourseEnvironment().getCourseGroupManager();
 		coursePropertyManager = userCourseEnv.getCourseEnvironment().getCoursePropertyManager();
 
 		// Get groupnames from configuration
-		enrollableGroupKeys = (List<Long>)moduleConfig.get(ENCourseNode.CONFIG_GROUP_IDS);
+		enrollableGroupKeys = moduleConfig.getList(ENCourseNode.CONFIG_GROUP_IDS, Long.class);
 		if(enrollableGroupKeys == null) {
 			String groupNamesConfig = (String)moduleConfig.get(ENCourseNode.CONFIG_GROUPNAME);
 			enrollableGroupKeys = businessGroupService.toGroupKeys(groupNamesConfig, courseGroupManager.getCourseEntry());
 		}
 
-		enrollableAreaKeys = (List<Long>)moduleConfig.get(ENCourseNode.CONFIG_AREA_IDS);
+		enrollableAreaKeys = moduleConfig.getList(ENCourseNode.CONFIG_AREA_IDS, Long.class);
 		if(enrollableAreaKeys == null) {
 			String areaInitVal = (String) moduleConfig.get(ENCourseNode.CONFIG_AREANAME);
 			enrollableAreaKeys = areaManager.toAreaKeys(areaInitVal, courseGroupManager.getCourseResource());
 		}
 
-		cancelEnrollEnabled = ((Boolean) moduleConfig.get(ENCourseNode.CONF_CANCEL_ENROLL_ENABLED)).booleanValue();
+		maxEnrollCount = moduleConfiguration.getIntegerSafe(ENCourseNode.CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT, 1);
+		cancelEnrollEnabled = moduleConfig.getBooleanSafe(ENCourseNode.CONF_CANCEL_ENROLL_ENABLED);
 
-		Identity identity = userCourseEnv.getIdentityEnvironment().getIdentity();
-		enrolledGroups = enrollmentManager.getBusinessGroupsWhereEnrolled(identity, enrollableGroupKeys, enrollableAreaKeys, courseGroupManager.getCourseEntry());
-		waitingListGroups = enrollmentManager.getBusinessGroupsWhereInWaitingList(identity, enrollableGroupKeys, enrollableAreaKeys);
-		registerGroupChangedEvents(enrollableGroupKeys, enrollableAreaKeys, ureq.getIdentity());
+		registerGroupChangedEvents(enrollableGroupKeys, enrollableAreaKeys, getIdentity());
 		// Set correct view
 		enrollVC = createVelocityContainer("enrollmultiple");
-		List<BusinessGroup> groups = enrollmentManager.loadGroupsFromNames(enrollableGroupKeys, enrollableAreaKeys);
+
+		List<EnrollmentRow> enrollmentRows = enrollmentManager.getEnrollments(getIdentity(), enrollableGroupKeys, enrollableAreaKeys, 256);
+		groupListModel = new EnrollmentTableModelWithMaxSize(enrollmentRows, getTranslator(), getIdentity(), cancelEnrollEnabled, maxEnrollCount);
+		Stats stats = groupListModel.getStats();
+		tableCtr = createTableController(ureq, stats.isSomeGroupWaitingListEnabled());
+		tableCtr.setTableDataModel(groupListModel);
 		
-		tableCtr = createTableController(ureq, enrollmentManager.hasAnyWaitingList(groups));
-		maxEnrollCount = moduleConfiguration.getIntegerSafe(ENCourseNode.CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT, 1);
-		doEnrollView(ureq);
+		doEnrollView(stats);
 
 		// push title and learning objectives, only visible on intro page
 		enrollVC.contextPut("menuTitle", enNode.getShortTitle());
@@ -184,29 +187,33 @@ public class ENRunController extends BasicController implements GenericEventList
 	@Override
 	public void event(UserRequest ureq, Controller source, Event event) {	
 		String cmd = event.getCommand();
-		 if (source == tableCtr) {
+		if (source == tableCtr) {
 			if (cmd.equals(Table.COMMANDLINK_ROWACTION_CLICKED)) {
 				TableEvent te = (TableEvent) event;
 				String actionid = te.getActionId();
 				int rowid = te.getRowId();
-				BusinessGroup choosenGroup = groupListModel.getBusinessGroupAt(rowid);
-				addLoggingResourceable(LoggingResourceable.wrap(choosenGroup));
+				EnrollmentRow row = groupListModel.getObject(rowid);
+				Long choosenGroupKey = row.getKey();
 				
 				if (actionid.equals(CMD_ENROLL_IN_GROUP)) {
-					log.debug("CMD_ENROLL_IN_GROUP ureq.getComponentID()=" + ureq.getComponentID() + "  ureq.getComponentTimestamp()=" + ureq.getComponentTimestamp());
+					BusinessGroup choosenGroup = businessGroupService.loadBusinessGroup(choosenGroupKey);
+					addLoggingResourceable(LoggingResourceable.wrap(choosenGroup));
+					
+					if(log.isDebug()) {
+						log.debug("CMD_ENROLL_IN_GROUP ureq.getComponentID()=" + ureq.getComponentID() + "  ureq.getComponentTimestamp()=" + ureq.getComponentTimestamp());
+					}
+					
 					EnrollStatus enrollStatus = enrollmentManager.doEnroll(ureq.getIdentity(), ureq.getUserSession().getRoles(), choosenGroup, enNode, coursePropertyManager, getWindowControl(), getTranslator(),
 							                                                   enrollableGroupKeys, enrollableAreaKeys, courseGroupManager);
-					if (enrollStatus.isEnrolled() ) {
-						enrolledGroups.add(choosenGroup);
-					} else if (enrollStatus.isInWaitingList() ) {
-						waitingListGroups.add(choosenGroup);
+					if (enrollStatus.isEnrolled() || enrollStatus.isInWaitingList() ) {
+						//OK
 					} else {
 						getWindowControl().setError(enrollStatus.getErrorMessage());
 					}
 					// events are already fired BusinessGroupManager level :: BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT, choosenGroup,  ureq.getIdentity());
 					// but async
 					// fire event to indicate runmaincontroller that the menuview is to update
-					doEnrollView(ureq);
+					doEnrollView(updateModel());
 					if (enrollStatus.isEnrolled() ) {
 						fireEvent(ureq, new BusinessGroupModifiedEvent(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT, choosenGroup, getIdentity()));
 					} else {
@@ -214,24 +221,30 @@ public class ENRunController extends BasicController implements GenericEventList
 					}
 
 				} else if (actionid.equals(CMD_ENROLLED_CANCEL)) {
- 					if (enrollmentManager.getBusinessGroupsWhereInWaitingList(getIdentity(), enrollableGroupKeys, enrollableAreaKeys).contains(choosenGroup)) {
+					BusinessGroup choosenGroup = businessGroupService.loadBusinessGroup(choosenGroupKey);
+					addLoggingResourceable(LoggingResourceable.wrap(choosenGroup));
+					
+					List<String> roles = businessGroupService
+							.getIdentityRolesInBusinessGroup(getIdentity(), choosenGroup);
+ 					if (roles.contains(GroupRoles.waiting.name())) {
 						enrollmentManager.doCancelEnrollmentInWaitingList(ureq.getIdentity(), choosenGroup, enNode, coursePropertyManager, getWindowControl(), getTranslator());
-						waitingListGroups.remove(choosenGroup);
-					} else if(enrollmentManager.getBusinessGroupsWhereEnrolled(getIdentity(), enrollableGroupKeys, enrollableAreaKeys, courseGroupManager.getCourseEntry()).contains(choosenGroup)) {
+					} else if(roles.contains(GroupRoles.participant.name())) {
 						enrollmentManager.doCancelEnrollment(ureq.getIdentity(), choosenGroup, enNode, coursePropertyManager, getWindowControl(), getTranslator());
-						enrolledGroups.remove(choosenGroup);
 					}
+ 					
 					// fire event to indicate runmaincontroller that the menuview is to update
 					fireEvent(ureq, new BusinessGroupModifiedEvent(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, choosenGroup, getIdentity()));
 					// events are already fired BusinessGroupManager level :: BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, group,  ureq.getIdentity());
 					// but async
-					doEnrollView(ureq);
+					doEnrollView(updateModel());
 				} else if(CMD_VISIT_CARD.equals(actionid)) {
+					List<String> roles = businessGroupService.getIdentityRolesInBusinessGroup(getIdentity(), row);
+					
 					String businessPath;
-					if(businessGroupService.isIdentityInBusinessGroup(getIdentity(), choosenGroup)) {
-						businessPath = "[BusinessGroup:" + choosenGroup.getKey() + "]";
+					if(roles.contains(GroupRoles.coach.name()) || roles.contains(GroupRoles.participant.name())) {
+						businessPath = "[BusinessGroup:" + choosenGroupKey + "]";
 					} else {
-						businessPath = "[GroupCard:" + choosenGroup.getKey() + "]";
+						businessPath = "[GroupCard:" + choosenGroupKey + "]";
 					}
 					NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl());
 				}
@@ -239,54 +252,61 @@ public class ENRunController extends BasicController implements GenericEventList
 		}
 	}
 
+	@Override
 	public void event(Event event) {
 		if (event instanceof OLATResourceableJustBeforeDeletedEvent) {
 			dispose();
 		}	
 	}	
 
-	private void doEnrollView(UserRequest ureq) {
-		enrollVC.contextPut("multiEnroll", (maxEnrollCount > 1 && enrolledGroups.size()+waitingListGroups.size()<maxEnrollCount));
-		if(!enrolledGroups.isEmpty()||!waitingListGroups.isEmpty()){
-			String[] hintNumbers = new String[2];
-			hintNumbers[0]=String.valueOf(enrolledGroups.size()+waitingListGroups.size());
-			hintNumbers[1]=String.valueOf(maxEnrollCount-enrolledGroups.size()-waitingListGroups.size());
+	private void doEnrollView(Stats stats) {
+		//num. of groups where the user is participant or in the waiting list
+		int numOfParticipatingGroups = stats.getParticipantingGroupNames().size();
+		int numOfWaitingGroups = stats.getWaitingGroupNames().size();
+		
+		enrollVC.contextPut("multiEnroll", (maxEnrollCount > 1 && numOfParticipatingGroups + numOfWaitingGroups < maxEnrollCount));
+		if(numOfParticipatingGroups > 0 || numOfWaitingGroups > 0){
+			String[] hintNumbers = new String[]{
+				String.valueOf(numOfParticipatingGroups + numOfWaitingGroups),
+				String.valueOf(maxEnrollCount - numOfParticipatingGroups - numOfWaitingGroups)
+			};
 			enrollVC.contextPut("multipleHint", translate("multiple.select.hint.outstanding", hintNumbers));
-		}else{
+		} else {
 			enrollVC.contextPut("multipleHint", translate("multiple.select.hint", String.valueOf(maxEnrollCount)));
 		}
 		
-		if (!enrolledGroups.isEmpty()) {
+		if (numOfParticipatingGroups > 0) {
 			enrollVC.contextPut("isEnrolledView", Boolean.TRUE);
-			ArrayList<String> groupnames = new ArrayList<String>();
-			for(BusinessGroup group:enrolledGroups){
-				groupnames.add(StringHelper.escapeHtml(group.getName()));
+			List<String> groupnames = new ArrayList<String>(numOfParticipatingGroups);
+			for(String groupName: stats.getParticipantingGroupNames()){
+				groupnames.add(StringHelper.escapeHtml(groupName));
 			}
 			enrollVC.contextPut("groupNames", groupnames);	
-		}else{
+		} else {
 			enrollVC.contextPut("isEnrolledView", Boolean.FALSE);
 		} 
 		
-		if (!waitingListGroups.isEmpty()){
+		if (numOfWaitingGroups > 0){
 			enrollVC.contextPut("isInWaitingList", Boolean.TRUE);
-			ArrayList<String> waitingListNames = new ArrayList<String>();
-			for(BusinessGroup waitingGroup:waitingListGroups){
-				waitingListNames.add(StringHelper.escapeHtml(waitingGroup.getName()));
+			List<String> waitingListNames = new ArrayList<String>(numOfWaitingGroups);
+			for(String groupName:stats.getWaitingGroupNames()){
+				waitingListNames.add(StringHelper.escapeHtml(groupName));
 			}
 			enrollVC.contextPut("waitingListNames", waitingListNames);    	
-		}else{
+		} else {
 			enrollVC.contextPut("isInWaitingList", Boolean.FALSE);
 		}
-		// 1. Fetch groups from database
-		List<BusinessGroup> groups = enrollmentManager.loadGroupsFromNames(enrollableGroupKeys, enrollableAreaKeys);
-		List<Integer> members = courseGroupManager.getNumberOfMembersFromGroups(groups);
-		// 2. Build group list
-		groupListModel = new BusinessGroupTableModelWithMaxSize(groups, members, getTranslator(), ureq.getIdentity(), cancelEnrollEnabled, maxEnrollCount);
-		tableCtr.setTableDataModel(groupListModel);
-		tableCtr.modelChanged();
 		// 3. Add group list to view
 		enrollVC.put("grouplisttable", tableCtr.getInitialComponent());
 	}
+	
+	private Stats updateModel() {
+		List<EnrollmentRow> enrollmentRows = enrollmentManager.getEnrollments(getIdentity(), enrollableGroupKeys, enrollableAreaKeys, 256);
+		groupListModel = new EnrollmentTableModelWithMaxSize(enrollmentRows, getTranslator(), getIdentity(), cancelEnrollEnabled, maxEnrollCount);
+		Stats stats = groupListModel.getStats();
+		tableCtr.setTableDataModel(groupListModel);
+		return stats;
+	}
 
 	private TableController createTableController(UserRequest ureq, boolean hasAnyWaitingList) {
 		TableGuiConfiguration tableConfig = new TableGuiConfiguration();
@@ -301,7 +321,7 @@ public class ENRunController extends BasicController implements GenericEventList
 		descCd.setEscapeHtml(EscapeMode.antisamy);
 		tableCtr.addColumnDescriptor(descCd);
 		tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("grouplist.table.partipiciant", 2, null, getLocale()));
-		tableCtr.addColumnDescriptor(hasAnyWaitingList,new DefaultColumnDescriptor("grouplist.table.waitingList", 3, null, getLocale()));
+		tableCtr.addColumnDescriptor(hasAnyWaitingList, new DefaultColumnDescriptor("grouplist.table.waitingList", 3, null, getLocale()));
 		DefaultColumnDescriptor stateColdEsc = new DefaultColumnDescriptor("grouplist.table.state", 4, null, getLocale());
 		stateColdEsc.setEscapeHtml(EscapeMode.none);
 		tableCtr.addColumnDescriptor(stateColdEsc);
@@ -318,24 +338,28 @@ public class ENRunController extends BasicController implements GenericEventList
 	 * 
 	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
 	 */
+	@Override
 	protected void doDispose() {
-		deregisterGroupChangedEvents(enrollableGroupKeys, enrollableAreaKeys);
+		deregisterGroupChangedEvents();
 	}
-
+	
 	/*
 	 * Add as listener to BusinessGroups so we are being notified about changes.
 	 */
-	private void registerGroupChangedEvents(List<Long> enrollableGroupKeys, List<Long> enrollableAreaKeys, Identity identity) {
-		List<BusinessGroup> groups = enrollmentManager.loadGroupsFromNames(enrollableGroupKeys, enrollableAreaKeys);
-		for (BusinessGroup group: groups) {
-			CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, identity, group);
+	private void registerGroupChangedEvents(List<Long> groupKeys, List<Long> areaKeys, Identity identity) {
+		registeredGroupKeys = enrollmentManager.getBusinessGroupKeys(groupKeys, areaKeys);
+		for (Long groupKey: registeredGroupKeys) {
+			OLATResourceable ores = OresHelper.createOLATResourceableInstance(BusinessGroup.class, groupKey);
+			CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, identity, ores);
 		}
 	}
 	
-	private void deregisterGroupChangedEvents(List<Long> enrollableGroupKeys, List<Long> enrollableAreaKeys) {
-		List<BusinessGroup> groups = enrollmentManager.loadGroupsFromNames(enrollableGroupKeys, enrollableAreaKeys);
-		for (BusinessGroup group:groups) {
-			CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, group);
+	private void deregisterGroupChangedEvents() {
+		if(registeredGroupKeys != null) {
+			for (Long groupKey:registeredGroupKeys) {
+				OLATResourceable ores = OresHelper.createOLATResourceableInstance(BusinessGroup.class, groupKey);
+				CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, ores);
+			}
 		}
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/en/ENWebService.java b/src/main/java/org/olat/course/nodes/en/ENWebService.java
index edb037c859b..a5b45281a3f 100644
--- a/src/main/java/org/olat/course/nodes/en/ENWebService.java
+++ b/src/main/java/org/olat/course/nodes/en/ENWebService.java
@@ -241,8 +241,8 @@ public class ENWebService extends AbstractCourseNodeWebService {
 					Long groupKey = new Long(groupId);
 					keys.add(groupKey);
 				}
-				List<BusinessGroupShort> groups = bgm.loadShortBusinessGroups(keys);
-				for(BusinessGroupShort bg:groups) {
+				List<BusinessGroupShort> groupsShort = bgm.loadShortBusinessGroups(keys);
+				for(BusinessGroupShort bg:groupsShort) {
 					groupNames.add(bg.getName());
 				}
 			}
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 a3a71b2d590..3cb929df4d5 100644
--- a/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java
+++ b/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java
@@ -31,11 +31,16 @@ import java.util.List;
 
 import org.olat.basesecurity.BaseSecurity;
 import org.olat.basesecurity.GroupRoles;
+import org.olat.basesecurity.IdentityRef;
+import org.olat.core.commons.persistence.DB;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Roles;
 import org.olat.core.manager.BasicManager;
+import org.olat.core.util.Formatter;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.filter.FilterFactory;
 import org.olat.core.util.mail.MailBundle;
 import org.olat.core.util.mail.MailContext;
 import org.olat.core.util.mail.MailContextImpl;
@@ -48,9 +53,11 @@ 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.BusinessGroupImpl;
 import org.olat.group.BusinessGroupService;
 import org.olat.group.area.BGAreaManager;
 import org.olat.group.model.BGMembership;
+import org.olat.group.model.BusinessGroupRefImpl;
 import org.olat.group.model.EnrollState;
 import org.olat.group.model.SearchBusinessGroupParams;
 import org.olat.group.ui.BGMailHelper;
@@ -72,6 +79,8 @@ import org.springframework.stereotype.Service;
 @Service("enrollmentManager")
 public class EnrollmentManager extends BasicManager {
 
+	@Autowired
+	private DB dbInstance;
 	@Autowired
 	private MailManager mailManager;
 	@Autowired
@@ -126,9 +135,9 @@ public class EnrollmentManager extends BasicManager {
 		// and move the users accordingly
 		MailPackage doNotSendmailPackage = new MailPackage(false);
 		businessGroupService.removeParticipants(identity, Collections.singletonList(identity), enrolledGroup, doNotSendmailPackage);
-		logInfo("doCancelEnrollment in group " + enrolledGroup, identity.getName());
+		logInfo(" doCancelEnrollment in group " + enrolledGroup, identity.getName());
 
-		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);
@@ -233,6 +242,106 @@ public class EnrollmentManager extends BasicManager {
 		}
 		return groups;
 	}
+	protected List<Long> getBusinessGroupKeys(List<Long> groupKeys, List<Long> areaKeys) {
+		List<Long> allKeys = new ArrayList<>();
+		if(groupKeys != null && !groupKeys.isEmpty()) {
+			allKeys.addAll(groupKeys);
+		}
+		if(areaKeys != null && !areaKeys.isEmpty()) {
+			List<Long> areaGroupKeys = areaManager.findBusinessGroupKeysOfAreaKeys(areaKeys);
+			allKeys.addAll(areaGroupKeys);
+		}
+		return allKeys;
+	}
+	
+	protected List<EnrollmentRow> getEnrollments(IdentityRef identity, List<Long> groupKeys, List<Long> areaKeys,
+			int descriptionMaxSize) {
+		List<Long> allGroupKeys = getBusinessGroupKeys(groupKeys, areaKeys);
+		if(allGroupKeys.isEmpty()) return Collections.emptyList();
+		
+		// groupKey, name, description, maxParticipants, waitingListEnabled;
+		// numInWaitingList, numOfParticipants, participant, waiting;
+
+		StringBuilder sb = new StringBuilder();
+		sb.append("select grp.key, grp.name, grp.description, grp.maxParticipants, grp.waitingListEnabled, ")
+		  //num of participant
+		  .append(" (select count(participants.key) from bgroupmember participants ")
+		  .append("  where participants.group=baseGroup and participants.role='").append(GroupRoles.participant.name()).append("'")
+		  .append(" ) as numOfParticipants,")
+		  //length of the waiting list
+		  .append(" (select count(waiters.key) from bgroupmember waiters ")
+		  .append("  where grp.waitingListEnabled=true and waiters.group=baseGroup and waiters.role='").append(GroupRoles.waiting.name()).append("'")
+		  .append(" ) as numOfWaiters,")
+		  //participant?
+		  .append(" (select count(meParticipant.key) from bgroupmember meParticipant ")
+		  .append("  where meParticipant.group=baseGroup and meParticipant.role='").append(GroupRoles.participant.name()).append("'")
+		  .append("  and meParticipant.identity.key=:identityKey")
+		  .append(" ) as numOfMeParticipant,")
+		  //waiting?
+		  .append(" (select count(meWaiting.key) from bgroupmember meWaiting ")
+		  .append("  where grp.waitingListEnabled=true and meWaiting.group=baseGroup and meWaiting.role='").append(GroupRoles.waiting.name()).append("'")
+		  .append("  and meWaiting.identity.key=:identityKey")
+		  .append(" ) as numOfMeWaiting")
+		  
+		  .append(" from ").append(BusinessGroupImpl.class.getName()).append(" grp ")
+		  .append(" inner join grp.baseGroup as baseGroup ")
+		  .append(" where grp.key in (:groupKeys)");
+		
+		List<Object[]> rows = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), Object[].class)
+			.setParameter("groupKeys", allGroupKeys)
+			.setParameter("identityKey", identity.getKey())
+			.getResultList();
+		
+		List<EnrollmentRow> enrollments = new ArrayList<>(rows.size());
+		for(Object[] row:rows) {
+			Long key = ((Number)row[0]).longValue();
+			String name = (String)row[1];
+			String desc = (String)row[2];
+			if(StringHelper.containsNonWhitespace(desc) && descriptionMaxSize > 0) {
+				desc = FilterFactory.getHtmlTagsFilter().filter(desc);
+				desc = Formatter.truncate(desc, 256);
+			}
+
+			int maxParticipants = row[3] == null ? -1 : ((Number)row[3]).intValue();
+			
+			Object enabled = row[4];
+			boolean waitingListEnabled;
+			if(enabled == null) {
+				waitingListEnabled = false;
+			} else if(enabled instanceof Boolean) {
+				waitingListEnabled = ((Boolean)enabled).booleanValue();
+			} else if(enabled instanceof Number) {
+				int val = ((Number)enabled).intValue();
+				waitingListEnabled = val == 1;
+			} else {
+				waitingListEnabled = false;
+			}
+			
+			int numOfParticipants = row[5] == null ? 0 : ((Number)row[5]).intValue();
+			int numOfWaiters = row[6] == null ? 0 : ((Number)row[6]).intValue();
+			boolean participant = row[7] == null ? false : ((Number)row[7]).intValue() > 0;
+			boolean waiting = row[8] == null ? false : ((Number)row[8]).intValue() > 0;
+			
+			EnrollmentRow enrollment = new EnrollmentRow(key, name, desc,
+					maxParticipants, waitingListEnabled);
+			enrollment.setNumOfParticipants(numOfParticipants);
+			enrollment.setNumInWaitingList(numOfWaiters);
+			enrollment.setParticipant(participant);
+			enrollment.setWaiting(waiting);
+			
+			if(waitingListEnabled && waiting) {
+				int pos = businessGroupService.getPositionInWaitingListFor(identity, new BusinessGroupRefImpl(key));
+				enrollment.setPositionInWaitingList(pos);
+			} else {
+				enrollment.setPositionInWaitingList(-1);
+			}
+			
+			enrollments.add(enrollment);
+		}
+		
+		return enrollments;
+	}
 
 	/**
 	 * Check if in any business-group a waiting-list is configured.
diff --git a/src/main/java/org/olat/course/nodes/en/EnrollmentRow.java b/src/main/java/org/olat/course/nodes/en/EnrollmentRow.java
new file mode 100644
index 00000000000..7b1a9076ec6
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/en/EnrollmentRow.java
@@ -0,0 +1,115 @@
+/**
+ * <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.course.nodes.en;
+
+import org.olat.group.BusinessGroupRef;
+
+
+/**
+ * 
+ * Initial date: 31.03.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class EnrollmentRow implements BusinessGroupRef {
+	
+	private final Long groupKey;
+	private final String name;
+	private final String description;
+	private final int maxParticipants;
+	private final boolean waitingListEnabled;
+	
+	
+	private boolean waiting;
+	private boolean participant;
+	private int numInWaitingList;
+	private int numOfParticipants;
+	private int positionInWaitingList;
+	
+	public EnrollmentRow(Long groupKey, String name, String description,
+			int maxParticipants, boolean waitingListEnabled) {
+		this.groupKey = groupKey;
+		this.name = name;
+		this.description = description;
+		this.maxParticipants = maxParticipants;
+		this.waitingListEnabled = waitingListEnabled;
+	}
+	
+	@Override
+	public Long getKey() {
+		return groupKey;
+	}
+	
+	public String getName() {
+		return name;
+	}
+	
+	public String getDescription() {
+		return description;
+	}
+
+	public int getMaxParticipants() {
+		return maxParticipants;
+	}
+	
+	public boolean isWaitingListEnabled() {
+		return waitingListEnabled;
+	}
+
+	public boolean isWaiting() {
+		return waiting;
+	}
+	
+	public void setWaiting(boolean waiting) {
+		this.waiting = waiting;
+	}
+	
+	public int getPositionInWaitingList() {
+		return positionInWaitingList;
+	}
+	
+	public boolean isParticipant() {
+		return participant;
+	}
+	
+	public void setParticipant(boolean participant) {
+		this.participant = participant;
+	}
+	
+	public int getNumInWaitingList() {
+		return numInWaitingList;
+	}
+	
+	public void setNumInWaitingList(int numInWaitingList) {
+		this.numInWaitingList = numInWaitingList;
+	}
+
+	public int getNumOfParticipants() {
+		return numOfParticipants;
+	}
+
+	public void setNumOfParticipants(int numOfParticipants) {
+		this.numOfParticipants = numOfParticipants;
+	}
+
+	public void setPositionInWaitingList(int positionInWaitingList) {
+		this.positionInWaitingList = positionInWaitingList;
+	}
+}
diff --git a/src/main/java/org/olat/group/ui/BusinessGroupTableModelWithMaxSize.java b/src/main/java/org/olat/course/nodes/en/EnrollmentTableModelWithMaxSize.java
similarity index 55%
rename from src/main/java/org/olat/group/ui/BusinessGroupTableModelWithMaxSize.java
rename to src/main/java/org/olat/course/nodes/en/EnrollmentTableModelWithMaxSize.java
index 446263118ca..d1b794f6559 100644
--- a/src/main/java/org/olat/group/ui/BusinessGroupTableModelWithMaxSize.java
+++ b/src/main/java/org/olat/course/nodes/en/EnrollmentTableModelWithMaxSize.java
@@ -23,23 +23,17 @@
 * under the Apache 2.0 license as the original file.
 */
 
-package org.olat.group.ui;
+package org.olat.course.nodes.en;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import org.olat.basesecurity.GroupRoles;
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.components.table.DefaultTableDataModel;
 import org.olat.core.gui.components.table.TableDataModelWithMarkableRows;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.id.Identity;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
-import org.olat.core.util.Formatter;
-import org.olat.core.util.filter.FilterFactory;
-import org.olat.group.BusinessGroup;
-import org.olat.group.BusinessGroupService;
 
 /**
  * Description:<BR>
@@ -50,16 +44,15 @@ import org.olat.group.BusinessGroupService;
  * 
  * @author gnaegi
  */
-public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<BusinessGroup> implements TableDataModelWithMarkableRows<BusinessGroup>  {
-	private static final OLog log = Tracing.createLoggerFor(BusinessGroupTableModelWithMaxSize.class);
+public class EnrollmentTableModelWithMaxSize extends DefaultTableDataModel<EnrollmentRow> implements TableDataModelWithMarkableRows<EnrollmentRow>  {
+	private static final OLog log = Tracing.createLoggerFor(EnrollmentTableModelWithMaxSize.class);
 	
 	private static final int COLUMN_COUNT = 7;
-	private List<Integer> members;
-	private Translator trans;
-	private Identity identity;
-	private boolean cancelEnrollEnabled;
-	private BusinessGroupService businessGroupService;
-	private int maxEnrolCount;
+
+	private final Translator trans;
+	private final Identity identity;
+	private final boolean cancelEnrollEnabled;
+	private final int maxEnrolCount;
 
 	/**
 	 * @param groups List of business groups
@@ -67,12 +60,11 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 	 *          The index of the list corresponds with the index of the group list
 	 * @param trans
 	 */
-	public BusinessGroupTableModelWithMaxSize(List<BusinessGroup> groups, List<Integer> members, Translator trans, Identity identity, boolean cancelEnrollEnabled, int maxEnrolCount) {
+	public EnrollmentTableModelWithMaxSize(List<EnrollmentRow> groups, Translator trans, Identity identity,
+			boolean cancelEnrollEnabled, int maxEnrolCount) {
 		super(groups);
-		this.members = members;
 		this.trans = trans;
 		this.identity = identity;
-		businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class);
 		this.cancelEnrollEnabled = cancelEnrollEnabled;
 		this.maxEnrolCount = maxEnrolCount;
 	}
@@ -80,6 +72,7 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 	/**
 	 * @see org.olat.core.gui.components.table.TableDataModel#getColumnCount()
 	 */
+	@Override
 	public int getColumnCount() {
 		return COLUMN_COUNT;
 	}
@@ -87,18 +80,14 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 	/**
 	 * @see org.olat.core.gui.components.table.TableDataModel#getValueAt(int, int)
 	 */
+	@Override
 	public Object getValueAt(int row, int col) {
-		BusinessGroup businessGroup = objects.get(row);
-		Integer numbParts = members.get(row);
-		Integer max = businessGroup.getMaxParticipants();
+		EnrollmentRow enrollmentRow = objects.get(row);
+		int numbParts = enrollmentRow.getNumOfParticipants();
+		Integer max = enrollmentRow.getMaxParticipants();
 		switch (col) {
-			case 0:
-				return businessGroup.getName();
-			case 1:
-				String description = businessGroup.getDescription();
-				description = FilterFactory.getHtmlTagsFilter().filter(description);
-				description = Formatter.truncate(description, 256);
-				return description;
+			case 0: return enrollmentRow.getName();
+			case 1: return enrollmentRow.getDescription();
 			case 2:
 				// Belegt/Plätze
 				if (max == null) { 
@@ -107,51 +96,50 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 				}
 				// return format 2/10
 				StringBuilder buf = new StringBuilder();
-				buf.append(numbParts);
-				buf.append(trans.translate("grouplist.table.partipiciant.delimiter"));
-				buf.append(businessGroup.getMaxParticipants());
-				if(numbParts>businessGroup.getMaxParticipants()) {
-				  log.info("Group overflow detected for the group: " + businessGroup + ", participants: " + numbParts + " maxParticipamts: " + businessGroup.getMaxParticipants());
+				buf.append(numbParts)
+				   .append(trans.translate("grouplist.table.partipiciant.delimiter"))
+				   .append(enrollmentRow.getMaxParticipants());
+				if(numbParts > enrollmentRow.getMaxParticipants()) {
+					log.info("Group overflow detected for the group: " + enrollmentRow.getKey() + "[name=" + enrollmentRow.getName() + "], participants: " + numbParts + " maxParticipamts: " + enrollmentRow.getMaxParticipants());
 				}
 				return buf.toString();
 			case 3:
 				// Waiting-list
-				if (businessGroup.getWaitingListEnabled().booleanValue()) {
+				if (enrollmentRow.isWaitingListEnabled()) {
 					// Waitinglist is enabled => show current size
-					int intValue = businessGroupService.countMembers(businessGroup, GroupRoles.waiting.name());
-					return new Integer(intValue);
+					return new Integer(enrollmentRow.getNumInWaitingList());
 				}
 				return trans.translate("grouplist.table.noWaitingList");
 			case 4:
 				// Status
-				if (businessGroupService.hasRoles(identity, businessGroup, GroupRoles.participant.name())) {
+				if (enrollmentRow.isParticipant()) {
 					return trans.translate("grouplist.table.state.onPartipiciantList"); 
-				} else if (businessGroupService.hasRoles(identity, businessGroup, GroupRoles.waiting.name())) {
-					int pos = businessGroupService.getPositionInWaitingListFor(identity,businessGroup);
+				} else if (enrollmentRow.isWaiting()) {
+					int pos = enrollmentRow.getPositionInWaitingList();
 					String[] onWaitingListArgs = new String[] { Integer.toString(pos) };
 					return trans.translate("grouplist.table.state.onWaitingList",onWaitingListArgs); 
-				} else if (max != null && !businessGroup.getWaitingListEnabled().booleanValue() && (numbParts.intValue() >= max.intValue()) ) {
+				} else if (max != null && !enrollmentRow.isWaitingListEnabled() && numbParts >= max.intValue()) {
 					return trans.translate("grouplist.table.state.enroll.full"); 
-				}	else if (max != null && businessGroup.getWaitingListEnabled().booleanValue() && (numbParts.intValue() >= max.intValue()) ) {
+				}	else if (max != null && enrollmentRow.isWaitingListEnabled() && numbParts >= max.intValue()) {
 					return trans.translate("grouplist.table.state.WaitingList");
 				}
 				return trans.translate("grouplist.table.state.notEnrolled");
 			case 5:
 				// Action enroll
-				if (getEnrolCount(identity) >= maxEnrolCount || isEnrolledIn(businessGroup, identity)) {
+				if (getEnrolCount() >= maxEnrolCount || isEnrolledIn(enrollmentRow)) {
 					// Already too much enrollments or already enrolled in the bg of the row => does not show action-link 'enroll'
 					return Boolean.FALSE;
 				}
-				if (max != null && !businessGroup.getWaitingListEnabled().booleanValue() && (numbParts.intValue() >= max.intValue()) ) {
+				if (max != null && !enrollmentRow.isWaitingListEnabled() && numbParts >= max.intValue()) {
 					// group is full => => does not show action-link 'enroll'
 					return Boolean.FALSE;
 				}
 				return Boolean.TRUE;
 			case 6:
 				// Action cancel enrollment
-				if (isEnrolledIn(businessGroup, identity)) {
+				if (isEnrolledIn(enrollmentRow)) {
           // check if user is on waiting-list
-					if (businessGroupService.hasRoles(identity, businessGroup, GroupRoles.waiting.name())) {
+					if (enrollmentRow.isWaiting()) {
             // user is on waitinglist => show allways action cancelEnrollment for waitinglist 
  					  return Boolean.TRUE;
 					}
@@ -161,20 +149,19 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 					}
 				}
 				return Boolean.FALSE;
-			default:
-				return "ERROR";
+			default: return "ERROR";
 		}
 	}
 
 	@Override
 	public Object createCopyWithEmptyList() {
-		return new BusinessGroupTableModelWithMaxSize(new ArrayList<BusinessGroup>(), members, trans, identity, cancelEnrollEnabled, maxEnrolCount);
+		return new EnrollmentTableModelWithMaxSize(new ArrayList<EnrollmentRow>(), trans, identity, cancelEnrollEnabled, maxEnrolCount);
 	}
 
 	/**
 	 * @param owned
 	 */
-	public void setEntries(List<BusinessGroup> owned) {
+	public void setEntries(List<EnrollmentRow> owned) {
 		this.objects = owned;
 	}
 
@@ -182,7 +169,7 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 	 * @param row
 	 * @return the business group at the given row
 	 */
-	public BusinessGroup getBusinessGroupAt(int row) {
+	public EnrollmentRow getRowAt(int row) {
 		return objects.get(row);
 	}
 	
@@ -192,12 +179,8 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 	 * @param ident
 	 * @return true: Found identity in PartipiciantGroup or WaitingGroup.
 	 */
-	private boolean isEnrolledIn(BusinessGroup businessGroup, Identity ident) {
-		if (businessGroupService.hasRoles(ident, businessGroup, GroupRoles.participant.name())
-				|| businessGroupService.hasRoles(ident, businessGroup, GroupRoles.waiting.name())) {
-			return true;
-		} 
-		return false;
+	private boolean isEnrolledIn(EnrollmentRow enrollmentRow) {
+		return enrollmentRow.isWaiting() || enrollmentRow.isParticipant();
 	}
 	
 	/**
@@ -205,11 +188,11 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 	 * @param ident
 	 * @return amount of business groups the identity is enrolled in
 	 */		
-	private int getEnrolCount(Identity ident) {
+	private int getEnrolCount() {
 		int enrolCount=0;
 		// loop over all business-groups
-		for (BusinessGroup businessGroup:objects) {
-			if (isEnrolledIn(businessGroup, ident) ) {
+		for (EnrollmentRow enrollmentRow:objects) {
+			if (isEnrolledIn(enrollmentRow) ) {
 				enrolCount++;
 				// optimize, enough is enough
 				if (maxEnrolCount == enrolCount) {
@@ -222,10 +205,47 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 
 	@Override
 	public String getRowCssClass(int rowId) {
-		BusinessGroup businessGroup = objects.get(rowId);
-		boolean isEnrolled = isEnrolledIn(businessGroup, identity);
-		return (isEnrolled ? "o_row_selected" : "");
+		EnrollmentRow enrollmentRow = objects.get(rowId);
+		return isEnrolledIn(enrollmentRow) ? "o_row_selected" : "";
+	}
+	
+	public Stats getStats() {
+		Stats stats = new Stats();
+		for(int i=getRowCount(); i-->0; ) {
+			EnrollmentRow row = getObject(i);
+			if(row.isWaitingListEnabled() && row.isWaiting()) {
+				stats.getWaitingGroupNames().add(row.getName());
+			}
+			if(row.isParticipant()) {
+				stats.getParticipantingGroupNames().add(row.getName());
+			}
+			if(row.isWaitingListEnabled()) {
+				stats.setSomeGroupWaitingListEnabled(true);
+			}
+		}
+		return stats;
 	}
+	
+	public static class Stats {
+		
+		private boolean someGroupWaitingListEnabled = false;
+		private final List<String> participantingGroupNames = new ArrayList<>(5);
+		private final List<String> waitingGroupNames = new ArrayList<>(5);
+
+		public boolean isSomeGroupWaitingListEnabled() {
+			return someGroupWaitingListEnabled;
+		}
+
+		public void setSomeGroupWaitingListEnabled(boolean someGroupWaitingListEnabled) {
+			this.someGroupWaitingListEnabled = someGroupWaitingListEnabled;
+		}
 
- 
+		public List<String> getParticipantingGroupNames() {
+			return participantingGroupNames;
+		}
+
+		public List<String> getWaitingGroupNames() {
+			return waitingGroupNames;
+		}
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/group/BusinessGroupService.java b/src/main/java/org/olat/group/BusinessGroupService.java
index 67d43fd7d11..e7d8213f76a 100644
--- a/src/main/java/org/olat/group/BusinessGroupService.java
+++ b/src/main/java/org/olat/group/BusinessGroupService.java
@@ -346,7 +346,7 @@ public interface BusinessGroupService {
 	 * @param businessGroup
 	 * @return
 	 */
-	public int getPositionInWaitingListFor(Identity identity, BusinessGroup businessGroup);
+	public int getPositionInWaitingListFor(IdentityRef identity, BusinessGroupRef businessGroup);
 	
 	//memberships
 	/**
@@ -531,7 +531,9 @@ public interface BusinessGroupService {
 	 * @param businessGroup
 	 * @return True if coach or participant
 	 */
-	public boolean isIdentityInBusinessGroup(Identity identity, BusinessGroup businessGroup);
+	public boolean isIdentityInBusinessGroup(IdentityRef identity, BusinessGroupRef businessGroup);
+	
+	public List<String> getIdentityRolesInBusinessGroup(IdentityRef identity, BusinessGroupRef businessGroup);
 	
 	/**
 	 * Checks if an identity is in the list of business groups either as owner or as participant
diff --git a/src/main/java/org/olat/group/manager/BusinessGroupRelationDAO.java b/src/main/java/org/olat/group/manager/BusinessGroupRelationDAO.java
index d758ceacdd6..524193da609 100644
--- a/src/main/java/org/olat/group/manager/BusinessGroupRelationDAO.java
+++ b/src/main/java/org/olat/group/manager/BusinessGroupRelationDAO.java
@@ -305,9 +305,24 @@ public class BusinessGroupRelationDAO {
 		return members;
 	}
 	
+	public List<Long> getMemberKeysOrderByDate(BusinessGroupRef group, String... roles) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select membership.identity.key from ").append(BusinessGroupImpl.class.getName()).append(" as bgroup ")
+		  .append(" inner join bgroup.baseGroup as baseGroup")
+		  .append(" inner join baseGroup.members as membership")
+		  .append(" where bgroup.key=:businessGroupKey and membership.role in (:roles) order by membership.creationDate");
+
+		List<String> roleList = GroupRoles.toList(roles);
+		List<Long> members = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Long.class)
+				.setParameter("businessGroupKey", group.getKey())
+				.setParameter("roles", roleList)
+				.getResultList();
+		return members;
+	}
+	
 	public List<Identity> getMembersOrderByDate(BusinessGroupRef group, String... roles) {
 		StringBuilder sb = new StringBuilder();
-		sb.append("select membership.identity from ").append(BusinessGroupImpl.class.getName()).append(" as bgroup ")
+		sb.append("select membership.identity.key from ").append(BusinessGroupImpl.class.getName()).append(" as bgroup ")
 		  .append(" inner join bgroup.baseGroup as baseGroup")
 		  .append(" inner join baseGroup.members as membership")
 		  .append(" where bgroup.key=:businessGroupKey and membership.role in (:roles) order by membership.creationDate");
diff --git a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java
index ecb3cd91596..2f84d88b7f2 100644
--- a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java
+++ b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java
@@ -1303,12 +1303,12 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 	}
 	
 	@Override
-	public int getPositionInWaitingListFor(Identity identity, BusinessGroup businessGroup) {
+	public int getPositionInWaitingListFor(IdentityRef identity, BusinessGroupRef businessGroup) {
 		// get position in waiting-list
-		List<Identity> identities = businessGroupRelationDAO.getMembersOrderByDate(businessGroup, GroupRoles.waiting.name());
+		List<Long> identities = businessGroupRelationDAO.getMemberKeysOrderByDate(businessGroup, GroupRoles.waiting.name());
 		for (int i = 0; i<identities.size(); i++) {
-			Identity waitingListIdentity = identities.get(i);
-			if (waitingListIdentity.equals(identity) ) {
+			Long waitingListIdentity = identities.get(i);
+			if (waitingListIdentity.equals(identity.getKey()) ) {
 				return i+1;// '+1' because list begins with 0 
 			}
 		}
@@ -1694,7 +1694,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 	}
 
 	@Override
-	public boolean isIdentityInBusinessGroup(Identity identity, BusinessGroup businessGroup) {
+	public boolean isIdentityInBusinessGroup(IdentityRef identity, BusinessGroupRef businessGroup) {
 		if(businessGroup == null || identity == null) return false;
 		List<String> roles = businessGroupRelationDAO.getRoles(identity, businessGroup);
 		if(roles == null || roles.isEmpty() || (roles.size() == 1 &&  GroupRoles.waiting.name().equals(roles.get(0)))) {
@@ -1703,6 +1703,11 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		return roles.size() > 0;
 	}
 
+	@Override
+	public List<String> getIdentityRolesInBusinessGroup(IdentityRef identity, BusinessGroupRef businessGroup) {
+		return businessGroupRelationDAO.getRoles(identity, businessGroup);
+	}
+
 	@Override
 	public List<BusinessGroupMembership> getBusinessGroupsMembership(Collection<BusinessGroup> businessGroups) {
 		return businessGroupDAO.getBusinessGroupsMembership(businessGroups);
diff --git a/src/test/java/org/olat/course/nodes/en/EnrollmentManagerTest.java b/src/test/java/org/olat/course/nodes/en/EnrollmentManagerConcurrentTest.java
similarity index 98%
rename from src/test/java/org/olat/course/nodes/en/EnrollmentManagerTest.java
rename to src/test/java/org/olat/course/nodes/en/EnrollmentManagerConcurrentTest.java
index 255181589fa..f2d1c643e6e 100644
--- a/src/test/java/org/olat/course/nodes/en/EnrollmentManagerTest.java
+++ b/src/test/java/org/olat/course/nodes/en/EnrollmentManagerConcurrentTest.java
@@ -82,9 +82,9 @@ import org.springframework.beans.factory.annotation.Autowired;
  * @author patrick
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
-public class EnrollmentManagerTest extends OlatTestCase implements WindowControl {
+public class EnrollmentManagerConcurrentTest extends OlatTestCase implements WindowControl {
 	//
-	private static OLog log = Tracing.createLoggerFor(EnrollmentManagerTest.class);
+	private static OLog log = Tracing.createLoggerFor(EnrollmentManagerConcurrentTest.class);
 	/*
 	 * ::Test Setup::
 	 */
@@ -128,7 +128,7 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 			log.info("TEST bgWithWaitingList.getMaxParticipants()=" + bgWithWaitingList.getMaxParticipants() );
 			log.info("TEST bgWithWaitingList.getWaitingListEnabled()=" + bgWithWaitingList.getWaitingListEnabled() );
 			// create mock objects
-			testTranslator = Util.createPackageTranslator(EnrollmentManagerTest.class, new Locale("de"));
+			testTranslator = Util.createPackageTranslator(EnrollmentManagerConcurrentTest.class, new Locale("de"));
 			// Identities
 			wg1 = JunitTestHelper.createAndPersistIdentityAsUser("wg1");
 			wg1Roles = securityManager.getRoles(wg1);
@@ -147,7 +147,8 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 	 * Enroll 3 identities (group with max-size=2 and waiting-list).
 	 * Cancel enrollment. Check size after each step.
 	 */
-	@Test public void testEnroll() throws Exception {
+	@Test
+	public void testEnroll() throws Exception {
 		log.info("testEnroll: start...");
 		ENCourseNode enNode = new ENCourseNode();
 		OLATResourceable ores = OresHelper.createOLATResourceableTypeWithoutCheck("TestCourse");
@@ -299,7 +300,7 @@ public class EnrollmentManagerTest extends OlatTestCase implements WindowControl
 				CoursePropertyManager coursePropertyManager = userCourseEnv.getCourseEnvironment().getCoursePropertyManager();
 				CourseGroupManager courseGroupManager = userCourseEnv.getCourseEnvironment().getCourseGroupManager();
 				
-				enrollmentManager.doEnroll(identity, JunitTestHelper.getUserRoles(), group, enNode, coursePropertyManager, EnrollmentManagerTest.this /*WindowControl mock*/, testTranslator,
+				enrollmentManager.doEnroll(identity, JunitTestHelper.getUserRoles(), group, enNode, coursePropertyManager, EnrollmentManagerConcurrentTest.this /*WindowControl mock*/, testTranslator,
 						new ArrayList<Long>()/*enrollableGroupNames*/, new ArrayList<Long>()/*enrollableAreaNames*/, courseGroupManager);
 				DBFactory.getInstance().commitAndCloseSession();
 			} catch (Exception e) {
diff --git a/src/test/java/org/olat/course/nodes/en/EnrollmentManagerSerialTest.java b/src/test/java/org/olat/course/nodes/en/EnrollmentManagerSerialTest.java
new file mode 100644
index 00000000000..6ff9a65ffe4
--- /dev/null
+++ b/src/test/java/org/olat/course/nodes/en/EnrollmentManagerSerialTest.java
@@ -0,0 +1,303 @@
+package org.olat.course.nodes.en;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.basesecurity.GroupRoles;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.group.BusinessGroup;
+import org.olat.group.BusinessGroupService;
+import org.olat.group.area.BGArea;
+import org.olat.group.area.BGAreaManager;
+import org.olat.group.manager.BusinessGroupRelationDAO;
+import org.olat.repository.RepositoryEntry;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 31.03.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class EnrollmentManagerSerialTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private BGAreaManager areaManager;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private EnrollmentManager enrollmentManager;
+	@Autowired
+	private BusinessGroupService businessGroupService;
+	@Autowired
+	private BusinessGroupRelationDAO businessGroupRelationDao;
+	
+	@Test
+	public void getEnrollmentRows_withWaitingList() {
+		Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("en-coach-1");
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-part-1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-part-2");
+		Identity waiter1 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-wait-3");
+		Identity out = JunitTestHelper.createAndPersistIdentityAsRndUser("en-out-4");
+		RepositoryEntry resource =  JunitTestHelper.createAndPersistRepositoryEntry();
+		BusinessGroup group = businessGroupService.createBusinessGroup(coach, "en-1", "en-1", 0, 10, true, false, resource);
+		businessGroupRelationDao.addRole(participant1, group, GroupRoles.participant.name());
+		businessGroupRelationDao.addRole(participant2, group, GroupRoles.participant.name());
+		businessGroupRelationDao.addRole(waiter1, group, GroupRoles.waiting.name());
+		Assert.assertNotNull(group);
+		dbInstance.commitAndCloseSession();
+		
+		List<Long> groupKeys = new ArrayList<>();
+		groupKeys.add(group.getKey());
+		
+		//check participant 1
+		List<EnrollmentRow> enrollments = enrollmentManager.getEnrollments(participant1, groupKeys, null, 128);
+		Assert.assertNotNull(enrollments);
+		Assert.assertEquals(1, enrollments.size());
+		EnrollmentRow enrollment = enrollments.get(0);
+		Assert.assertEquals(group.getKey(), enrollment.getKey());
+		Assert.assertEquals(group.getName(), enrollment.getName());
+		Assert.assertEquals(2, enrollment.getNumOfParticipants());
+		Assert.assertEquals(1, enrollment.getNumInWaitingList());
+		Assert.assertTrue(enrollment.isParticipant());
+		Assert.assertFalse(enrollment.isWaiting());
+		
+		//check waiter
+		List<EnrollmentRow> waitingEnrollments = enrollmentManager.getEnrollments(waiter1, groupKeys, null, 128);
+		Assert.assertNotNull(waitingEnrollments);
+		Assert.assertEquals(1, waitingEnrollments.size());
+		EnrollmentRow waitingEnrollment = waitingEnrollments.get(0);
+		Assert.assertEquals(group.getKey(), waitingEnrollment.getKey());
+		Assert.assertEquals(group.getName(), waitingEnrollment.getName());
+		Assert.assertEquals(2, waitingEnrollment.getNumOfParticipants());
+		Assert.assertEquals(1, waitingEnrollment.getNumInWaitingList());
+		Assert.assertEquals(1, waitingEnrollment.getPositionInWaitingList());
+		Assert.assertFalse(waitingEnrollment.isParticipant());
+		Assert.assertTrue(waitingEnrollment.isWaiting());
+		
+		//check out
+		List<EnrollmentRow> outEnrollments = enrollmentManager.getEnrollments(out, groupKeys, null, 128);
+		Assert.assertNotNull(outEnrollments);
+		Assert.assertEquals(1, outEnrollments.size());
+		EnrollmentRow outEnrollment = outEnrollments.get(0);
+		Assert.assertEquals(group.getKey(), outEnrollment.getKey());
+		Assert.assertEquals(group.getName(), outEnrollment.getName());
+		Assert.assertEquals(2, outEnrollment.getNumOfParticipants());
+		Assert.assertEquals(1, outEnrollment.getNumInWaitingList());
+		Assert.assertFalse(outEnrollment.isParticipant());
+		Assert.assertFalse(outEnrollment.isWaiting());
+	}
+	
+	@Test
+	public void getEnrollmentRows_withoutWaitingList() {
+		Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("en-coach-1");
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-part-1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-part-2");
+		Identity waiter1 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-wait-3");
+		Identity out = JunitTestHelper.createAndPersistIdentityAsRndUser("en-out-4");
+		RepositoryEntry resource =  JunitTestHelper.createAndPersistRepositoryEntry();
+		BusinessGroup group = businessGroupService.createBusinessGroup(coach, "en-1", "en-1", 0, 10, false, false, resource);
+		
+		businessGroupRelationDao.addRole(participant1, group, GroupRoles.participant.name());
+		businessGroupRelationDao.addRole(participant2, group, GroupRoles.participant.name());
+		businessGroupRelationDao.addRole(waiter1, group, GroupRoles.waiting.name());
+		Assert.assertNotNull(group);
+		dbInstance.commitAndCloseSession();
+		
+		List<Long> groupKeys = new ArrayList<>();
+		groupKeys.add(group.getKey());
+		
+		//check participant 1
+		List<EnrollmentRow> enrollments = enrollmentManager.getEnrollments(participant1, groupKeys, null, 128);
+		Assert.assertNotNull(enrollments);
+		Assert.assertEquals(1, enrollments.size());
+		EnrollmentRow enrollment = enrollments.get(0);
+		Assert.assertEquals(group.getKey(), enrollment.getKey());
+		Assert.assertEquals(group.getName(), enrollment.getName());
+		Assert.assertEquals(2, enrollment.getNumOfParticipants());
+		Assert.assertEquals(0, enrollment.getNumInWaitingList());
+		Assert.assertTrue(enrollment.isParticipant());
+		Assert.assertFalse(enrollment.isWaiting());
+		
+		//check waiter (which not exists in enroll because the flag waiting list is set to false)
+		List<EnrollmentRow> waitingEnrollments = enrollmentManager.getEnrollments(waiter1, groupKeys, null, 128);
+		Assert.assertNotNull(waitingEnrollments);
+		Assert.assertEquals(1, waitingEnrollments.size());
+		EnrollmentRow waitingEnrollment = waitingEnrollments.get(0);
+		Assert.assertEquals(group.getKey(), waitingEnrollment.getKey());
+		Assert.assertEquals(group.getName(), waitingEnrollment.getName());
+		Assert.assertEquals(2, waitingEnrollment.getNumOfParticipants());
+		Assert.assertEquals(0, waitingEnrollment.getNumInWaitingList());
+		Assert.assertEquals(-1, waitingEnrollment.getPositionInWaitingList());
+		Assert.assertFalse(waitingEnrollment.isParticipant());
+		Assert.assertFalse(waitingEnrollment.isWaiting());
+		
+		//check out
+		List<EnrollmentRow> outEnrollments = enrollmentManager.getEnrollments(out, groupKeys, null, 128);
+		Assert.assertNotNull(outEnrollments);
+		Assert.assertEquals(1, outEnrollments.size());
+		EnrollmentRow outEnrollment = outEnrollments.get(0);
+		Assert.assertEquals(group.getKey(), outEnrollment.getKey());
+		Assert.assertEquals(group.getName(), outEnrollment.getName());
+		Assert.assertEquals(2, outEnrollment.getNumOfParticipants());
+		Assert.assertEquals(0, outEnrollment.getNumInWaitingList());
+		Assert.assertFalse(outEnrollment.isParticipant());
+		Assert.assertFalse(outEnrollment.isWaiting());
+	}
+	
+	@Test
+	public void getEnrollmentRows_withAreas() {
+		Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("en-area-1");
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-part-1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-part-2");
+		Identity participant3 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-part-3");
+		Identity participant4 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-part-4");
+		Identity participant5 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-part-5");
+		Identity waiter1 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-wait-3");
+		Identity waiter2 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-wait-4");
+		Identity waiter3 = JunitTestHelper.createAndPersistIdentityAsRndUser("en-wait-5");
+		
+		//create a resource, an area, a group
+		RepositoryEntry resource =  JunitTestHelper.createAndPersistRepositoryEntry();
+		String areaName = UUID.randomUUID().toString();
+		BGArea area = areaManager.createAndPersistBGArea("en-area-" + areaName, "description:" + areaName, resource.getOlatResource());
+		BusinessGroup group1 = businessGroupService.createBusinessGroup(null, "en-area-group", "area-group-desc", 0, 10, false, false, resource);
+		BusinessGroup group2 = businessGroupService.createBusinessGroup(null, "en-group-2", "area-group-desc", 0, 10, true, false, resource);
+		BusinessGroup group3 = businessGroupService.createBusinessGroup(null, "en-group-3", "area-group-desc", 0, 10, true, false, resource);
+		
+		businessGroupRelationDao.addRole(participant1, group1, GroupRoles.participant.name());
+		businessGroupRelationDao.addRole(participant2, group2, GroupRoles.participant.name());
+		businessGroupRelationDao.addRole(participant3, group2, GroupRoles.participant.name());
+		businessGroupRelationDao.addRole(participant4, group2, GroupRoles.participant.name());
+		businessGroupRelationDao.addRole(participant5, group2, GroupRoles.participant.name());
+		businessGroupRelationDao.addRole(waiter1, group2, GroupRoles.waiting.name());
+		businessGroupRelationDao.addRole(waiter2, group2, GroupRoles.waiting.name());
+		businessGroupRelationDao.addRole(waiter3, group2, GroupRoles.waiting.name());
+		
+		areaManager.addBGToBGArea(group1, area);
+		areaManager.addBGToBGArea(group2, area);
+		dbInstance.commitAndCloseSession();
+		
+		List<Long> groupKeys = new ArrayList<>();
+		groupKeys.add(group2.getKey());
+		groupKeys.add(group3.getKey());
+		List<Long> areaKeys = new ArrayList<>();
+		areaKeys.add(area.getKey());
+		
+		//check id enrollments
+		List<EnrollmentRow> idEnrollments = enrollmentManager.getEnrollments(id, groupKeys, areaKeys, 128);
+		Assert.assertNotNull(idEnrollments);
+		Assert.assertEquals(3, idEnrollments.size());
+		
+		//check enrollment group 1
+		EnrollmentRow enrollment1 = getEnrollmentRowFor(group1, idEnrollments);
+		Assert.assertEquals(group1.getKey(), enrollment1.getKey());
+		Assert.assertEquals(group1.getName(), enrollment1.getName());
+		Assert.assertEquals(1, enrollment1.getNumOfParticipants());
+		Assert.assertEquals(0, enrollment1.getNumInWaitingList());
+		Assert.assertFalse(enrollment1.isParticipant());
+		Assert.assertFalse(enrollment1.isWaiting());
+
+		//check enrollment group 2
+		EnrollmentRow enrollment2 = getEnrollmentRowFor(group2, idEnrollments);
+		Assert.assertEquals(group2.getKey(), enrollment2.getKey());
+		Assert.assertEquals(group2.getName(), enrollment2.getName());
+		Assert.assertEquals(4, enrollment2.getNumOfParticipants());
+		Assert.assertEquals(3, enrollment2.getNumInWaitingList());
+		Assert.assertFalse(enrollment2.isParticipant());
+		Assert.assertFalse(enrollment2.isWaiting());
+		
+		//check enrollment group 3
+		EnrollmentRow enrollment3 = getEnrollmentRowFor(group3, idEnrollments);
+		Assert.assertEquals(group3.getKey(), enrollment3.getKey());
+		Assert.assertEquals(group3.getName(), enrollment3.getName());
+		Assert.assertEquals(0, enrollment3.getNumOfParticipants());
+		Assert.assertEquals(0, enrollment3.getNumInWaitingList());
+		Assert.assertFalse(enrollment3.isParticipant());
+		Assert.assertFalse(enrollment3.isWaiting());
+		
+		
+		//check enrollments of participant5
+		List<EnrollmentRow> part5Enrollments = enrollmentManager.getEnrollments(participant5, groupKeys, areaKeys, 128);
+		Assert.assertNotNull(part5Enrollments);
+		Assert.assertEquals(3, part5Enrollments.size());
+		
+		EnrollmentRow enrollment2_w5 = getEnrollmentRowFor(group2, part5Enrollments);
+		Assert.assertEquals(group2.getKey(), enrollment2_w5.getKey());
+		Assert.assertEquals(group2.getName(), enrollment2_w5.getName());
+		Assert.assertEquals(4, enrollment2_w5.getNumOfParticipants());
+		Assert.assertEquals(3, enrollment2_w5.getNumInWaitingList());
+		Assert.assertTrue(enrollment2_w5.isParticipant());
+		Assert.assertFalse(enrollment2_w5.isWaiting());
+		
+
+		//check enrollments of waiter 3
+		List<EnrollmentRow> wait3Enrollments = enrollmentManager.getEnrollments(waiter3, groupKeys, areaKeys, 128);
+		Assert.assertNotNull(wait3Enrollments);
+		Assert.assertEquals(3, wait3Enrollments.size());
+		
+		EnrollmentRow enrollment2_p3 = getEnrollmentRowFor(group2, wait3Enrollments);
+		Assert.assertEquals(group2.getKey(), enrollment2_p3.getKey());
+		Assert.assertEquals(group2.getName(), enrollment2_p3.getName());
+		Assert.assertEquals(4, enrollment2_p3.getNumOfParticipants());
+		Assert.assertEquals(3, enrollment2_p3.getNumInWaitingList());
+		Assert.assertFalse(enrollment2_p3.isParticipant());
+		Assert.assertTrue(enrollment2_p3.isWaiting());
+	}
+	
+	private EnrollmentRow getEnrollmentRowFor(BusinessGroup group, List<EnrollmentRow> enrollments) {
+		if(enrollments == null || enrollments.isEmpty()) return null;
+		
+		EnrollmentRow row = null;
+		for(EnrollmentRow enrollment:enrollments) {
+			if(enrollment.getKey().equals(group.getKey())) {
+				row = enrollment;
+			}
+		}
+
+		return row;
+	}
+	
+	/**
+	 * Test the bevahior with no data. It's important because some of the value returned
+	 * by the database can be null, and this is database dependant.
+	 */
+	@Test
+	public void getEnrollmentRows_null() {
+		Identity dummy = JunitTestHelper.createAndPersistIdentityAsRndUser("en-dummy-1");
+		dbInstance.commitAndCloseSession();
+
+		//null
+		List<EnrollmentRow> nullEnrollments = enrollmentManager.getEnrollments(dummy, null, null, 128);
+		Assert.assertNotNull(nullEnrollments);
+		Assert.assertEquals(0, nullEnrollments.size());
+		
+		//wrong keys
+		List<Long> groupKeys = new ArrayList<>();
+		groupKeys.add(27l);
+		List<Long> areaKeys = new ArrayList<>();
+		areaKeys.add(27l);
+		
+		List<EnrollmentRow> groupEnrollments = enrollmentManager.getEnrollments(dummy, groupKeys, null, 128);
+		Assert.assertNotNull(groupEnrollments);
+		Assert.assertEquals(0, groupEnrollments.size());
+		
+		List<EnrollmentRow> areaEnrollments = enrollmentManager.getEnrollments(dummy, null, areaKeys, 128);
+		Assert.assertNotNull(areaEnrollments);
+		Assert.assertEquals(0, areaEnrollments.size());
+		
+		List<EnrollmentRow> groupAndAreaEnrollments = enrollmentManager.getEnrollments(dummy, groupKeys, areaKeys, 128);
+		Assert.assertNotNull(groupAndAreaEnrollments);
+		Assert.assertEquals(0, groupAndAreaEnrollments.size());
+	}
+
+}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index a1a51517ef2..a204b45e151 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -122,7 +122,8 @@ import org.junit.runners.Suite;
 	org.olat.instantMessaging.InstantMessagePreferencesDAOTest.class,
 	org.olat.instantMessaging.RosterDAOTest.class,
 	org.olat.instantMessaging.InstantMessageServiceTest.class,
-	org.olat.course.nodes.en.EnrollmentManagerTest.class,
+	org.olat.course.nodes.en.EnrollmentManagerSerialTest.class,
+	org.olat.course.nodes.en.EnrollmentManagerConcurrentTest.class,
 	org.olat.course.nodes.gta.manager.GTAManagerTest.class,
 	org.olat.course.assessment.AssessmentManagerTest.class,
 	org.olat.course.assessment.manager.UserCourseInformationsManagerTest.class,
-- 
GitLab