/**
* 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.course.nodes.en;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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;
import org.olat.core.util.mail.MailHelper;
import org.olat.core.util.mail.MailManager;
import org.olat.core.util.mail.MailPackage;
import org.olat.core.util.mail.MailTemplate;
import org.olat.core.util.mail.MailerResult;
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.BusinessGroupRefImpl;
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.repository.RepositoryEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Description:<BR>
* Business-logic of enrollemnt
* <p>
* Handle enroll and cancel-enrollment
* <P>
* Initial Date: Nov 11, 2006
*
* @author Christian Guretzki
*/
@Service("enrollmentManager")
public class EnrollmentManager extends BasicManager {
@Autowired
private DB dbInstance;
@Autowired
private MailManager mailManager;
@Autowired
private BGAreaManager areaManager;
@Autowired
private BusinessGroupService businessGroupService;
public EnrollStatus doEnroll(Identity identity, Roles roles, BusinessGroup group, ENCourseNode enNode, CoursePropertyManager coursePropertyManager,
WindowControl wControl, Translator trans, List<Long> groupKeys, List<Long> areaKeys, CourseGroupManager cgm) {
final EnrollStatus enrollStatus = new EnrollStatus();
if (isLogDebugEnabled()) logDebug("doEnroll");
// check if the user is able to be enrolled
int groupsEnrolledCount = getBusinessGroupsWhereEnrolled(identity, groupKeys, areaKeys, cgm.getCourseEntry()).size();
int waitingListCount = getBusinessGroupsWhereInWaitingList(identity, groupKeys, areaKeys).size();
int enrollCountConfig = enNode.getModuleConfiguration().getIntegerSafe(ENCourseNode.CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT, 1);
if ( (groupsEnrolledCount + waitingListCount) < enrollCountConfig ) {
if (isLogDebugEnabled()) logDebug("Identity is not enrolled identity=" + identity.getName() + " group=" + group.getName());
// 1. Check if group has max size defined. If so check if group is full
// o_clusterREVIEW cg please review it - also where does the group.getMaxParticipants().equals("") come from??
// 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???
MailPackage doNotSendmailPackage = new MailPackage(false);
EnrollState state = businessGroupService.enroll(identity, roles, identity, group, doNotSendmailPackage);
if(state.isFailed()) {
enrollStatus.setErrorMessage(trans.translate(state.getI18nErrorMessage()));
} else {
if(state.getEnrolled() == BGMembership.participant) {
addUserToParticipantList(identity, group, enNode, coursePropertyManager, wControl, trans);
enrollStatus.setIsEnrolled(true);
} else if(state.getEnrolled() == BGMembership.waiting) {
addUserToWaitingList(identity, group, enNode, coursePropertyManager, wControl, trans);
enrollStatus.setIsInWaitingList(true);
}
}
} else {
enrollStatus.setErrorMessage(trans.translate("error.group.already.enrolled"));
}
if (isLogDebugEnabled()) logDebug("doEnroll finished");
return enrollStatus;
}
public void doCancelEnrollment(final Identity identity, final BusinessGroup enrolledGroup, final ENCourseNode enNode,
final CoursePropertyManager coursePropertyManager, WindowControl wControl, Translator trans) {
if (isLogDebugEnabled()) logDebug("doCancelEnrollment");
// 1. Remove group membership, fire events, do loggin etc.
// Remove participant. This will also check if a waiting-list with auto-close-ranks is configurated
// 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());
// 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);
//fxdiff VCRP-16: intern mail system
MailContext context = new MailContextImpl(wControl.getBusinessControl().getAsString());
MailerResult result = new MailerResult();
MailBundle bundle = mailManager.makeMailBundle(context, identity, mailTemplate, null, null, result);
if(bundle != null) {
mailManager.sendMessage(bundle);
}
MailHelper.printErrorsAndWarnings(result, wControl, trans.getLocale());
}
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.
businessGroupService.removeFromWaitingList(identity, Collections.singletonList(identity), enrolledWaitingListGroup, null);
// 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);
//fxdiff VCRP-16: intern mail system
MailContext context = new MailContextImpl(wControl.getBusinessControl().getAsString());
MailerResult result = new MailerResult();
MailBundle bundle = mailManager.makeMailBundle(context, identity, mailTemplate, null, null, result);
if(bundle != null) {
mailManager.sendMessage(bundle);
}
MailHelper.printErrorsAndWarnings(result, wControl, trans.getLocale());
}
// Helper Methods
// ////////////////
/**
* @param identity
* @param List<Long> groupKeys which are in the list
* @param List<Long> areaKeys which are in the list
* @return List<BusinessGroup> in which the identity is enrolled
*/
protected List<BusinessGroup> getBusinessGroupsWhereEnrolled(Identity identity, List<Long> groupKeys, List<Long> areaKeys, RepositoryEntry courseResource) {
List<BusinessGroup> groups = new ArrayList<BusinessGroup>();
//search in the enrollable bg keys for the groups where identity is attendee
if(groupKeys != null && !groupKeys.isEmpty()) {
SearchBusinessGroupParams params = new SearchBusinessGroupParams();
params.setAttendee(true);
params.setIdentity(identity);
params.setGroupKeys(groupKeys);
groups.addAll(businessGroupService.findBusinessGroups(params, courseResource, 0, 0));
}
//search in the enrollable area keys for the groups where identity is attendee
if(areaKeys != null && !areaKeys.isEmpty()) {
groups.addAll(areaManager.findBusinessGroupsOfAreaAttendedBy(identity, areaKeys, courseResource.getOlatResource()));
}
return groups;
}
/**
* @param identity
* @param groupNames
* @return true if this identity is any waiting-list group in this course that
* has a name that is in the group names list
*/
protected List<BusinessGroup> getBusinessGroupsWhereInWaitingList(Identity identity, List<Long> groupKeys, List<Long> areaKeys) {
List<BusinessGroup> groups = loadGroupsFromNames(groupKeys, areaKeys);
List<BusinessGroup> waitingInTheseGroups = new ArrayList<BusinessGroup> ();
// loop over all business-groups
for (BusinessGroup businessGroup:groups) {
if (businessGroupService.hasRoles(identity, businessGroup, GroupRoles.waiting.name())) {
waitingInTheseGroups.add(businessGroup);
}
}
return waitingInTheseGroups;
}
/**
* @param groupNames
* @return a list of business groups from any of the courses group contexts
* that match the names from the groupNames list. If a groupname is
* not found it won't be in the list. So groupNames.size() can very
* well by different than loadGroupsFromNames().size()
*/
protected List<BusinessGroup> loadGroupsFromNames(List<Long> groupKeys, List<Long> areaKeys) {
List<BusinessGroup> groups = new ArrayList<BusinessGroup>(businessGroupService.loadBusinessGroups(groupKeys));
List<BusinessGroup> areaGroups = areaManager.findBusinessGroupsOfAreaKeys(areaKeys);
// add groups from areas
for (BusinessGroup areaGroup:areaGroups) {
if (!groups.contains(areaGroup)) {
groups.add(areaGroup);
}
}
return groups;
}
public 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,")
//num of pending
.append(" (select count(reservations.key) from resourcereservation reservations ")
.append(" where reservations.resource.key=grp.resource.key")
.append(" ) as numOfReservationss,")
//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 businessgroup 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 numOfReservations = row[6] == null ? 0 : ((Number)row[6]).intValue();
int numOfWaiters = row[7] == null ? 0 : ((Number)row[7]).intValue();
boolean participant = row[8] == null ? false : ((Number)row[8]).intValue() > 0;
boolean waiting = row[9] == null ? false : ((Number)row[9]).intValue() > 0;
EnrollmentRow enrollment = new EnrollmentRow(key, name, desc,
maxParticipants, waitingListEnabled);
enrollment.setNumOfParticipants(numOfParticipants);
enrollment.setNumOfReservations(numOfReservations);
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.
*
* @param groups
* @return true : YES, there are waiting-list<br>
* false: NO, no waiting-list
*/
protected boolean hasAnyWaitingList(List<BusinessGroup> groups) {
for (BusinessGroup businessGroup :groups) {
if (businessGroup.getWaitingListEnabled().booleanValue()) {
return true;
}
}
return false;
}
// /////////////////
// Private Methods
// /////////////////
private boolean addUserToParticipantList(Identity identity, BusinessGroup group, ENCourseNode enNode,
CoursePropertyManager coursePropertyManager, WindowControl wControl, Translator trans) {
// 2. Set first enrollment date
String nowString = Long.toString(System.currentTimeMillis());
Property firstTime = coursePropertyManager
.findCourseNodeProperty(enNode, identity, null, ENCourseNode.PROPERTY_INITIAL_ENROLLMENT_DATE);
if (firstTime == null) {
// create firsttime
firstTime = coursePropertyManager.createCourseNodePropertyInstance(enNode, identity, null,
ENCourseNode.PROPERTY_INITIAL_ENROLLMENT_DATE, null, null, nowString, null);
coursePropertyManager.saveProperty(firstTime);
}
// 3. Set enrollmentdate property
Property thisTime = coursePropertyManager.findCourseNodeProperty(enNode, identity, null, ENCourseNode.PROPERTY_RECENT_ENROLLMENT_DATE);
if (thisTime == null) {
// create firsttime
thisTime = coursePropertyManager.createCourseNodePropertyInstance(enNode, identity, null,
ENCourseNode.PROPERTY_RECENT_ENROLLMENT_DATE, null, null, nowString, null);
coursePropertyManager.saveProperty(thisTime);
} else {
thisTime.setStringValue(nowString);
coursePropertyManager.updateProperty(thisTime);
}
// 4. Send notification mail
MailTemplate mailTemplate = BGMailHelper.createAddMyselfMailTemplate(group, identity);
MailContext context = new MailContextImpl(wControl.getBusinessControl().getAsString());
MailerResult result = new MailerResult();
MailBundle bundle = mailManager.makeMailBundle(context, identity, mailTemplate, null, null, result);
if(bundle != null) {
mailManager.sendMessage(bundle);
}
MailHelper.printErrorsAndWarnings(result, wControl, trans.getLocale());
return true;
}
private boolean addUserToWaitingList(Identity identity, BusinessGroup group, ENCourseNode enNode,
CoursePropertyManager coursePropertyManager, WindowControl wControl, Translator trans) {
// <- 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,
ENCourseNode.PROPERTY_INITIAL_WAITINGLIST_DATE);
if (firstTime == null) {
// create firsttime
firstTime = coursePropertyManager.createCourseNodePropertyInstance(enNode, identity, null,
ENCourseNode.PROPERTY_INITIAL_WAITINGLIST_DATE, null, null, nowString, null);
coursePropertyManager.saveProperty(firstTime);
}
// 3. Set waiting-list date property
Property thisTime = coursePropertyManager.findCourseNodeProperty(enNode, identity, null, ENCourseNode.PROPERTY_RECENT_WAITINGLIST_DATE);
if (thisTime == null) {
// create firsttime
thisTime = coursePropertyManager.createCourseNodePropertyInstance(enNode, identity, null,
ENCourseNode.PROPERTY_RECENT_WAITINGLIST_DATE, null, null, nowString, null);
coursePropertyManager.saveProperty(thisTime);
} else {
thisTime.setStringValue(nowString);
coursePropertyManager.updateProperty(thisTime);
}
// 4. Send notification mail
MailTemplate mailTemplate = BGMailHelper.createAddWaitinglistMailTemplate(group, identity);
//fxdiff VCRP-16: intern mail system
MailContext context = new MailContextImpl(wControl.getBusinessControl().getAsString());
MailerResult result = new MailerResult();
MailBundle bundle = mailManager.makeMailBundle(context, identity, mailTemplate, null, null, result);
if(bundle != null) {
mailManager.sendMessage(bundle);
}
MailHelper.printErrorsAndWarnings(result, wControl, trans.getLocale());
return true;
}
}