Index: sections/sections-app/src/java/org/sakaiproject/tool/section/jsf/backingbean/StudentViewBean.java =================================================================== --- sections/sections-app/src/java/org/sakaiproject/tool/section/jsf/backingbean/StudentViewBean.java (revision 34683) +++ sections/sections-app/src/java/org/sakaiproject/tool/section/jsf/backingbean/StudentViewBean.java (working copy) @@ -23,8 +23,10 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; @@ -39,6 +41,7 @@ import org.sakaiproject.section.api.coursemanagement.EnrollmentRecord; import org.sakaiproject.section.api.exception.RoleConfigurationException; import org.sakaiproject.section.api.exception.SectionFullException; +import org.sakaiproject.section.api.facade.Role; import org.sakaiproject.tool.section.decorator.SectionDecorator; import org.sakaiproject.tool.section.decorator.StudentSectionDecorator; import org.sakaiproject.tool.section.jsf.JsfUtil; @@ -155,35 +158,59 @@ return; } refresh(); - //check that there are still places available + + // Check that there are still places available int maxEnrollments = Integer.MAX_VALUE; if(section.getMaxEnrollments() != null) { maxEnrollments = section.getMaxEnrollments().intValue(); } - int numEnrolled = getSectionManager().getTotalEnrollments(section.getUuid()); - if (numEnrolled >= maxEnrollments) { - if(log.isDebugEnabled()) log.debug("Attempted to join a section with no spaces"); - JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_membership_full", new String[] {section.getTitle()})); - return; - } - try { - EnrollmentRecord er; + + if (maxEnrollments == Integer.MAX_VALUE) { + // No maximum size is set for this section + try { + if(getSectionManager().joinSection(sectionUuid) == null) { + // This operation failed + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_already_member_in_category")); + } + } catch (RoleConfigurationException rce) { + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("role_config_error")); + } - if (maxEnrollments == Integer.MAX_VALUE) - er = getSectionManager().joinSection(sectionUuid); - else - er = getSectionManager().joinSection(sectionUuid, maxEnrollments); + } else { + + // Enforce a maximum size + Map roleMap = getSectionManager().getTotalEnrollmentsMap(sectionUuid); + + if (roleMap.size() < 3) { + log.warn("Cannot get section enrollment information for section " + sectionUuid); + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("role_config_error")); + return; + } + int studentsEnrolled = ((Integer) roleMap.get(Role.STUDENT)).intValue(); + int otherMembers = ((Integer) roleMap.get(Role.TA)).intValue() + + ((Integer) roleMap.get(Role.INSTRUCTOR)).intValue(); - if(er == null) { - // This operation failed - JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_already_member_in_category")); + if (studentsEnrolled >= maxEnrollments) { + if(log.isDebugEnabled()) log.debug("Attempted to join a section with no spaces"); + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_membership_full", new String[] {section.getTitle()})); + return; } - } catch (RoleConfigurationException rce) { - JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("role_config_error")); - } catch (SectionFullException sfe) { - if(log.isDebugEnabled()) log.debug("Attempted to join a section with no spaces"); - JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_membership_full", new String[] {section.getTitle()})); + + try { + if(getSectionManager().joinSection(sectionUuid, maxEnrollments + otherMembers) == null) { + // This operation failed + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_already_member_in_category")); + } + } catch (RoleConfigurationException rce) { + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("role_config_error")); + } catch (SectionFullException sfe) { + if(log.isDebugEnabled()) log.debug("Attempted to join a section with no spaces"); + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_membership_full", new String[] {section.getTitle()})); + } + } + + return; } public void processSwitchSection(ActionEvent event) { @@ -199,28 +226,50 @@ } refresh(); - //check that there are still places available + // Check that there are still places available + int maxEnrollments = Integer.MAX_VALUE; if(section.getMaxEnrollments() != null) { maxEnrollments = section.getMaxEnrollments().intValue(); } - int numEnrolled = getSectionManager().getTotalEnrollments(section.getUuid()); - if (numEnrolled >= maxEnrollments) { - if(log.isDebugEnabled()) log.debug("Attempted to join a section with no spaces"); - JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_membership_full", new String[] {section.getTitle()})); - return; + + if (maxEnrollments == Integer.MAX_VALUE) { + // No maximum size is set for this section + try { + getSectionManager().switchSection(sectionUuid); + } catch (RoleConfigurationException rce) { + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("role_config_error")); + } + } else { + // Enforce a maximum size + Map roleMap = getSectionManager().getTotalEnrollmentsMap(sectionUuid); + + if (roleMap.size() < 3) { + log.warn("Cannot get section enrollment information for section " + sectionUuid); + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("role_config_error")); + return; + } + int studentsEnrolled = ((Integer) roleMap.get(Role.STUDENT)).intValue(); + int otherMembers = ((Integer) roleMap.get(Role.TA)).intValue() + + ((Integer) roleMap.get(Role.INSTRUCTOR)).intValue(); + + if (studentsEnrolled >= maxEnrollments) { + if(log.isDebugEnabled()) log.debug("Attempted to join a section with no spaces"); + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_membership_full", new String[] {section.getTitle()})); + return; + } + try { + getSectionManager().switchSection(sectionUuid, maxEnrollments + otherMembers); + } catch (RoleConfigurationException rce) { + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("role_config_error")); + } catch (SectionFullException sfe) { + if(log.isDebugEnabled()) log.debug("Attempted to join a section with no spaces"); + JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_membership_full", new String[] {section.getTitle()})); + } + } - try { - if (maxEnrollments == Integer.MAX_VALUE) - getSectionManager().switchSection(sectionUuid); - else - getSectionManager().switchSection(sectionUuid, maxEnrollments); - } catch (RoleConfigurationException rce) { - JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("role_config_error")); - } catch (SectionFullException sfe) { - if(log.isDebugEnabled()) log.debug("Attempted to join a section with no spaces"); - JsfUtil.addErrorMessage(JsfUtil.getLocalizedMessage("student_view_membership_full", new String[] {section.getTitle()})); - } + + return; } public boolean isExternallyManaged() { Index: sections/sections-impl/sakai/impl/src/java/org/sakaiproject/component/section/sakai/SectionManagerImpl.java =================================================================== --- sections/sections-impl/sakai/impl/src/java/org/sakaiproject/component/section/sakai/SectionManagerImpl.java (revision 34683) +++ sections/sections-impl/sakai/impl/src/java/org/sakaiproject/component/section/sakai/SectionManagerImpl.java (working copy) @@ -24,10 +24,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; @@ -693,7 +695,7 @@ return (String)roleStrings.iterator().next(); } - private String getSectionTaRole(Group group) throws RoleConfigurationException { + private String getSectionTaRole(AuthzGroup group) throws RoleConfigurationException { Set roleStrings = group.getRolesIsAllowed(SectionAwareness.TA_MARKER); if(roleStrings.size() != 1) { if(log.isDebugEnabled()) log.debug("Group " + group + @@ -704,6 +706,17 @@ return (String)roleStrings.iterator().next(); } + private String getSectionInstructorRole(AuthzGroup group) throws RoleConfigurationException { + Set roleStrings = group.getRolesIsAllowed(SectionAwareness.INSTRUCTOR_MARKER); + if(roleStrings.size() != 1) { + if(log.isDebugEnabled()) log.debug("Group " + group + + " must have one and only one role with permission " + + SectionAwareness.INSTRUCTOR_MARKER); + throw new RoleConfigurationException("Can't add a user to a section as an Instructor, since there is no Instructor-flagged role"); + } + return (String)roleStrings.iterator().next(); + } + /** * {@inheritDoc} */ @@ -1039,6 +1052,39 @@ /** * {@inheritDoc} */ + @SuppressWarnings("unchecked") + public Map getTotalEnrollmentsMap(String learningContextUuid) { + + Map roleMap = new HashMap(); + + AuthzGroup authzGroup; + try { + authzGroup = authzGroupService.getAuthzGroup(learningContextUuid); + } catch (GroupNotDefinedException e) { + log.error("learning context " + learningContextUuid + " is neither a site nor a section"); + return roleMap; + } + + String studentRole, taRole, instructorRole; + try { + studentRole = getSectionStudentRole(authzGroup); + taRole = getSectionTaRole(authzGroup); + instructorRole = getSectionInstructorRole(authzGroup); + } catch (RoleConfigurationException rce) { + log.warn("Can't get total enrollments map, since one of the student, TA or Instructor-flagged roles occurs more than once in " + learningContextUuid); + return roleMap; + } + + roleMap.put(Role.STUDENT, authzGroup.getUsersHasRole(studentRole).size()); + roleMap.put(Role.TA, authzGroup.getUsersHasRole(taRole).size()); + roleMap.put(Role.INSTRUCTOR, authzGroup.getUsersHasRole(instructorRole).size()); + return roleMap; + } + + + /** + * {@inheritDoc} + */ public CourseSection addSection(String courseUuid, String title, String category, Integer maxEnrollments, String location, Time startTime, Index: sections/sections-impl/standalone/src/java/org/sakaiproject/component/section/SectionManagerHibernateImpl.java =================================================================== --- sections/sections-impl/standalone/src/java/org/sakaiproject/component/section/SectionManagerHibernateImpl.java (revision 34683) +++ sections/sections-impl/standalone/src/java/org/sakaiproject/component/section/SectionManagerHibernateImpl.java (working copy) @@ -25,10 +25,12 @@ import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Set; @@ -615,6 +617,20 @@ /** * {@inheritDoc} */ + public Map getTotalEnrollmentsMap(String learningContextUuid) { + + // Not fully implemented, which is OK as this implementation does + // not support transaction-backed enforcement of maximum section size anyway + Map roleMap = new HashMap(); + roleMap.put(Role.STUDENT, getTotalEnrollments(learningContextUuid)); + roleMap.put(Role.TA, 0); + roleMap.put(Role.INSTRUCTOR, 0); + return roleMap; + } + + /** + * {@inheritDoc} + */ public CourseSection addSection(final String courseUuid, final String title, final String category, final Integer maxEnrollments, final String location, final Time startTime, Index: sections/sections-api/src/java/org/sakaiproject/section/api/SectionManager.java =================================================================== --- sections/sections-api/src/java/org/sakaiproject/section/api/SectionManager.java (revision 34683) +++ sections/sections-api/src/java/org/sakaiproject/section/api/SectionManager.java (working copy) @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import org.sakaiproject.section.api.coursemanagement.Course; @@ -211,6 +212,15 @@ public int getTotalEnrollments(String learningContextUuid); /** + * Returns the total number of students enrolled in a learning context by section role. + * Useful for comparing to the max number of enrollments allowed in a section. + * + * @param sectionUuid + * @return + */ + public Map getTotalEnrollmentsMap(String learningContextUuid); + + /** * Adds a user to a section under the specified role. If a student is added * to a section, s/he will be automatically removed from any other section * of the same category in this site. So adding 'student1' to 'Lab1', for