/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/edu-services/trunk/sections-service/sections-impl/sakai/impl/src/java/org/sakaiproject/component/section/sakai/SectionManagerImpl.java $
* $Id: SectionManagerImpl.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008, 2009 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.component.section.sakai;
import java.sql.Time;
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 java.util.Calendar;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.thread_local.api.ThreadLocalManager;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.section.api.SectionAwareness;
import org.sakaiproject.section.api.SectionManager;
import org.sakaiproject.section.api.coursemanagement.Course;
import org.sakaiproject.section.api.coursemanagement.CourseSection;
import org.sakaiproject.section.api.coursemanagement.EnrollmentRecord;
import org.sakaiproject.section.api.coursemanagement.Meeting;
import org.sakaiproject.section.api.coursemanagement.ParticipationRecord;
import org.sakaiproject.section.api.coursemanagement.SectionEnrollments;
import org.sakaiproject.section.api.coursemanagement.User;
import org.sakaiproject.section.api.exception.MembershipException;
import org.sakaiproject.section.api.exception.RoleConfigurationException;
import org.sakaiproject.section.api.exception.SectionFullException;
import org.sakaiproject.section.api.facade.Role;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.component.section.sakai.facade.SakaiUtil;
import org.sakaiproject.coursemanagement.api.CourseManagementService;
import org.sakaiproject.coursemanagement.api.Section;
import org.sakaiproject.coursemanagement.api.exception.IdNotFoundException;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.authz.api.AuthzGroup;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.authz.api.AuthzPermissionException;
import org.sakaiproject.authz.api.GroupFullException;
import org.sakaiproject.authz.api.GroupNotDefinedException;
import org.sakaiproject.authz.api.GroupProvider;
import org.sakaiproject.authz.api.Member;
import org.sakaiproject.entity.api.EntityManager;
import org.sakaiproject.entity.api.Reference;
import org.sakaiproject.entity.api.ResourceProperties;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteAdvisor;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.user.api.UserNotDefinedException;
/**
* A sakai-based implementation of the Section Management API, using the
* new grouping capability of the framework.
*
* @author <a href="mailto:jholtzman@berkeley.edu">Josh Holtzman</a>
*
*/
public abstract class SectionManagerImpl implements SectionManager, SiteAdvisor {
private static final Log log = LogFactory.getLog(SectionManagerImpl.class);
// Sakai services set by method injection
protected abstract SiteService siteService();
// Sakai services set by dependency injection
protected AuthzGroupService authzGroupService;
protected GroupProvider groupProvider;
protected SecurityService securityService;
protected UserDirectoryService userDirectoryService;
protected SessionManager sessionManager;
protected EntityManager entityManager;
protected EventTrackingService eventTrackingService;
protected CourseManagementService courseManagementService;
protected ThreadLocalManager threadLocalManager;
// Configuration setting
protected ExternalIntegrationConfig config;
/**
* Initialization called once all dependencies are set.
*/
public void init() {
if(log.isInfoEnabled()) log.info("init()");
siteService().addSiteAdvisor(this);
// A group provider may not exist, so we can't use spring to inject it
groupProvider = (GroupProvider)ComponentManager.get(GroupProvider.class);
}
/**
* Cleans up any resources in use before destroying this service.
*/
public void destroy() {
if(log.isInfoEnabled()) log.info("destroy()");
siteService().removeSiteAdvisor(this);
}
// SiteAdvisor methods
/**
* {@inheritDoc}
*/
public void update(Site site) {
// NOTE: This code will be called any time a site is saved (including site creation).
// Be very careful...
// If we're on a non-course site, do nothing
if( ! "course".equalsIgnoreCase(site.getType())) {
if(log.isDebugEnabled()) log.debug("SiteAdvisor " + this.getClass().getCanonicalName() + " ignoring site " + site.getTitle() + ", which is not a course site");
return;
}
// Get our app config and the site properties
ExternalIntegrationConfig appConfig = getConfiguration(null);
ResourceProperties siteProps = site.getProperties();
// If we're configured to be mandatory auto or mandatory manual, handle those conditions and return
if(handlingMandatoryConfigs(appConfig, site)) {
if(log.isDebugEnabled()) log.debug(this.getClass().getCanonicalName() + " finished decorating site " + site.getTitle() + " for " + appConfig);
return;
}
// Set the defaults for non-mandatory sites
setSiteDefaults(site, appConfig, siteProps);
// If this site is manually managed, it could have been just changed to be manual.
// In that case, we flip the formerly "provided" users to be non-provided so they stay in the section.
if("false".equals(siteProps.getProperty(CourseImpl.EXTERNALLY_MAINTAINED))) {
if(log.isDebugEnabled()) log.debug("SiteAdvisor " + this.getClass().getCanonicalName() + " stripping provider IDs from all sections in site " + site.getTitle() + ". The site is internally managed.");
for(Iterator iter = getSiteGroups(site).iterator(); iter.hasNext();) {
Group group = (Group)iter.next();
if(group.getProviderGroupId() == null) {
// This wasn't provided, so skip it
continue;
}
group.setProviderGroupId(null);
// Add members to the groups based on the current (provided) memberships
Set members = group.getMembers();
for(Iterator memberIter = members.iterator(); memberIter.hasNext();) {
Member member = (Member)memberIter.next();
if(member.isProvided()) {
group.addMember(member.getUserId(), member.getRole().getId(), member.isActive(), false);
}
}
}
} else {
// This is an externally managed site, so remove any groups without a category and without a providerId
for(Iterator<Group> iter = getSiteGroups(site).iterator(); iter.hasNext();) {
Group group = iter.next();
ResourceProperties props = group.getProperties();
if(group.getProviderGroupId() == null && (props != null &&
props.getProperty(CourseSectionImpl.CATEGORY) != null)) {
// This is a section, and it doesn't have a provider id
iter.remove();
}
}
// Update the sections from CM.
syncSections(site);
}
}
private boolean handlingMandatoryConfigs(ExternalIntegrationConfig appConfig, Site site) {
ResourceProperties siteProps = site.getProperties();
switch(appConfig) {
case MANUAL_MANDATORY:
// If we're configured to treat all sites as manual, set the site to manual control
siteProps.addProperty(CourseImpl.EXTERNALLY_MAINTAINED, Boolean.FALSE.toString());
return true;
case AUTOMATIC_MANDATORY:
// If we're configured to treat all sites as automatic, set the site to external control and update the sections
siteProps.addProperty(CourseImpl.EXTERNALLY_MAINTAINED, Boolean.TRUE.toString());
siteProps.addProperty(CourseImpl.STUDENT_REGISTRATION_ALLOWED, Boolean.FALSE.toString());
siteProps.addProperty(CourseImpl.STUDENT_SWITCHING_ALLOWED, Boolean.FALSE.toString());
syncSections(site);
return true;
default:
return false;
}
}
private void setSiteDefaults(Site site, ExternalIntegrationConfig appConfig, ResourceProperties siteProps) {
// If the site doesn't have a property set for "Externally Maintained", it's either a new site or one
// that was created before this SiteAdvisor was registered.
if(siteProps.getProperty(CourseImpl.EXTERNALLY_MAINTAINED) == null) {
// Set this property to external if the app config is AUTOMATIC_DEFAULT, else make it internally managed
// FIXME -- This might have unforseen consequences...
if(log.isDebugEnabled()) log.debug("Site '" + site.getTitle() + "' has no EXTERNALLY_MAINTAINED flag.");
if(appConfig == ExternalIntegrationConfig.AUTOMATIC_DEFAULT) {
siteProps.addProperty(CourseImpl.EXTERNALLY_MAINTAINED, Boolean.TRUE.toString());
siteProps.addProperty(CourseImpl.STUDENT_REGISTRATION_ALLOWED, Boolean.FALSE.toString());
siteProps.addProperty(CourseImpl.STUDENT_SWITCHING_ALLOWED, Boolean.FALSE.toString());
} else if(appConfig == ExternalIntegrationConfig.MANUAL_DEFAULT) {
siteProps.addProperty(CourseImpl.EXTERNALLY_MAINTAINED, Boolean.FALSE.toString());
return;
}
}
}
/**
* Replaces all existing sections (whether internally or externally defined) with
* sections that are externally defined.
*
* @param site
* @param appConfig
*/
private void syncSections(Site site) {
if(log.isInfoEnabled()) log.info("Synchronizing internal sections with externally defined sections in site " + site.getId());
// Use the group provider to split the complex string
if(groupProvider == null) {
log.warn("SectionManager can not automatically generate sections without" +
"a properly configured GroupProvider");
return;
}
// Get the provider Ids associated with this site. We can't use
// authzGroupService.getProviderIds(), since we're inspecting the provider IDs
// on the in-memory object, not what's in persistence
String siteProviderId = site.getProviderGroupId();
List<String> providerIdList;
if(StringUtils.trimToNull(siteProviderId) == null) {
providerIdList = new ArrayList<String>();
} else {
String[] providerIdArray = groupProvider.unpackId(siteProviderId);
providerIdList = Arrays.asList(providerIdArray);
}
Set<Group> sectionsToSync = new HashSet<Group>();
// Remove any formerly provided sections that are no longer in the list of provider ids,
// and sync existing sections if they are still listed in the provider ids.
Collection<Group> groups = getSiteGroups(site);
if(groups != null) {
for(Iterator<Group> iter = groups.iterator(); iter.hasNext();) {
Group group = iter.next();
String providerId = group.getProviderGroupId();
if(providerId == null) {
// This wasn't provided, so skip it
continue;
}
if(group.getProperties() == null || StringUtils.trimToNull(
group.getProperties().getProperty(CourseSectionImpl.CATEGORY)) == null) {
// This isn't a section, so skip it
continue;
}
if(providerIdList.contains(providerId)) {
if(log.isDebugEnabled()) log.debug("Synchronizing section " + group.getReference());
sectionsToSync.add(group);
} else {
if(log.isDebugEnabled()) log.debug("Removing section " + group.getReference());
iter.remove();
}
}
}
// Sync existing sections
Set<String> sectionProviderIdsToSync = new HashSet<String>();
for(Iterator<Group> iter = sectionsToSync.iterator(); iter.hasNext();) {
Group group = iter.next();
sectionProviderIdsToSync.add(group.getProviderGroupId());
syncExternalCourseSectionWithSite(group);
}
// Add provided sections that we're not synchronizing
for(Iterator<String> iter = providerIdList.iterator(); iter.hasNext();) {
String providerId = iter.next();
if( ! sectionProviderIdsToSync.contains(providerId)) {
addExternalCourseSectionToSite(site, providerId);
}
}
}
/**
* Synchronizes the state of an internal CourseSection with the state of an externally
* defined (CM) section. This is done so meeting times, locations, etc are kept
* in sync with changes made to these data outside Sakai.
*
* @param group
*/
private void syncExternalCourseSectionWithSite(Group group) {
String providerId = group.getProviderGroupId();
Section officialSection = null;
try {
officialSection = courseManagementService.getSection(providerId);
} catch (IdNotFoundException ide) {
log.error("Site " + group.getContainingSite().getId() + " has a provider id, " + providerId + ", that has no matching section in CM.");
return;
}
CourseSectionUtil.decorateGroupWithCmSection(group, officialSection);
}
/**
* Adds an externally managed CourseSection (a decorated group) to a site. The CourseSection is
* constructed by finding the official section from CM and converting it to a CourseSection.
*
* @param site The site in which we are adding a CourseSection
* @param sectionId The Enterprise ID of the section to add.
*
* @return The CourseSection that was added to the site
*/
private CourseSection addExternalCourseSectionToSite(Site site, String sectionEid) {
if(log.isDebugEnabled()) log.debug("Adding section " + sectionEid + " to site " + site.getId());
// Create a new sakai section (group) for this providerId
Section officialSection = null;
try {
officialSection = courseManagementService.getSection(sectionEid);
} catch (IdNotFoundException ide) {
log.error("Site " + site.getId() + " has a provider id, " + sectionEid + ", that has no matching section in CM.");
return null;
}
Group group = site.addGroup();
group.setProviderGroupId(sectionEid);
return CourseSectionUtil.decorateGroupWithCmSection(group, officialSection);
}
// SectionManager Methods
/**
* Filters out framework groups that do not have a category. A section's
* category is determined by
*
*/
public List<CourseSection> getSections(String siteContext) {
if(log.isDebugEnabled()) log.debug("Getting sections for context " + siteContext);
List<CourseSection> sectionList = new ArrayList<CourseSection>();
Collection sections;
try {
sections = getSiteGroups(getSite(siteContext));
} catch (IdUnusedException e) {
log.error("No site with id = " + siteContext);
return new ArrayList<CourseSection>();
}
for(Iterator iter = sections.iterator(); iter.hasNext();) {
Group group = (Group)iter.next();
// Only use groups with a category defined. If there is no category,
// it is not a section.
if(StringUtils.trimToNull(
group.getProperties().getProperty(CourseSectionImpl.CATEGORY)) != null) {
sectionList.add(new CourseSectionImpl(group));
}
}
return sectionList;
}
/**
* {@inheritDoc}
*/
public List<CourseSection> getSectionsInCategory(String siteContext, String categoryId) {
if(log.isDebugEnabled()) log.debug("Getting " + categoryId + " sections for context " + siteContext);
List<CourseSection> sectionList = new ArrayList<CourseSection>();
Collection sections;
try {
sections = getSiteGroups(getSite(siteContext));
} catch (IdUnusedException e) {
log.error("No site with id = " + siteContext);
return new ArrayList<CourseSection>();
}
for(Iterator iter = sections.iterator(); iter.hasNext();) {
Group group = (Group)iter.next();
if(categoryId.equals(group.getProperties().getProperty(CourseSectionImpl.CATEGORY))) {
sectionList.add(new CourseSectionImpl(group));
}
}
return sectionList;
}
/**
* Like getSectionsInCategory but returns ids which are more useful to other methods
*
* @param siteContext The site context
* @param categoryId
*
* @return A List of section uuids
*/
private List<String> getSectionIdsInCategory(String siteContext, String categoryId) {
if(log.isDebugEnabled()) log.debug("Getting " + categoryId + " sections for context " + siteContext);
List<String> sectionList = new ArrayList<String>();
Collection sections;
try {
sections = getSiteGroups(getSite(siteContext));
} catch (IdUnusedException e) {
log.error("No site with id = " + siteContext);
return sectionList;
}
for(Iterator iter = sections.iterator(); iter.hasNext();) {
Group group = (Group)iter.next();
if(categoryId.equals(group.getProperties().getProperty(CourseSectionImpl.CATEGORY))) {
sectionList.add(group.getReference());
}
}
return sectionList;
}
/**
* {@inheritDoc}
*/
public CourseSection getSection(String sectionUuid) {
Group group;
group = findGroup(sectionUuid);
if(group == null) {
log.error("Unable to find section " + sectionUuid);
return null;
}
return new CourseSectionImpl(group);
}
/**
* {@inheritDoc}
*/
public List<ParticipationRecord> getSiteInstructors(String siteContext) {
CourseImpl course = (CourseImpl)getCourse(siteContext);
if(course == null) {
return new ArrayList<ParticipationRecord>();
}
Site site = course.getSite();
Set sakaiUserIds = site.getUsersIsAllowed(SectionAwareness.INSTRUCTOR_MARKER);
List sakaiMembers = userDirectoryService.getUsers(sakaiUserIds);
List<ParticipationRecord> membersList = new ArrayList<ParticipationRecord>();
for(Iterator iter = sakaiMembers.iterator(); iter.hasNext();) {
org.sakaiproject.user.api.User sakaiUser = (org.sakaiproject.user.api.User)iter.next();
User user = SakaiUtil.convertUser(sakaiUser);
InstructorRecordImpl record = new InstructorRecordImpl(course, user);
membersList.add(record);
}
return membersList;
}
/**
* {@inheritDoc}
*/
public List<ParticipationRecord> getSiteTeachingAssistants(String siteContext) {
CourseImpl course = (CourseImpl)getCourse(siteContext);
if(course == null) {
return new ArrayList<ParticipationRecord>();
}
Site site = course.getSite();
Set sakaiUserIds = site.getUsersIsAllowed(SectionAwareness.TA_MARKER);
List sakaiMembers = userDirectoryService.getUsers(sakaiUserIds);
List<ParticipationRecord> membersList = new ArrayList<ParticipationRecord>();
for(Iterator iter = sakaiMembers.iterator(); iter.hasNext();) {
org.sakaiproject.user.api.User sakaiUser = (org.sakaiproject.user.api.User)iter.next();
User user = SakaiUtil.convertUser(sakaiUser);
TeachingAssistantRecordImpl record = new TeachingAssistantRecordImpl(course, user);
membersList.add(record);
}
return membersList;
}
/**
* {@inheritDoc}
*/
public List<EnrollmentRecord> getSiteEnrollments(String siteContext) {
CourseImpl course = (CourseImpl)getCourse(siteContext);
if(course == null) {
return new ArrayList<EnrollmentRecord>();
}
Site site = course.getSite();
Set sakaiUserIds = site.getUsersIsAllowed(SectionAwareness.STUDENT_MARKER);
List sakaiMembers = userDirectoryService.getUsers(sakaiUserIds);
List<EnrollmentRecord> membersList = new ArrayList<EnrollmentRecord>();
for(Iterator iter = sakaiMembers.iterator(); iter.hasNext();) {
org.sakaiproject.user.api.User sakaiUser = (org.sakaiproject.user.api.User)iter.next();
User user = SakaiUtil.convertUser(sakaiUser);
EnrollmentRecordImpl record = new EnrollmentRecordImpl(course, null, user);
membersList.add(record);
}
return membersList;
}
/**
* {@inheritDoc}
*/
public List<ParticipationRecord> getSectionTeachingAssistants(String sectionUuid) {
Group group = findGroup(sectionUuid);
CourseSection section = getSection(sectionUuid);
if(section == null) {
return new ArrayList<ParticipationRecord>();
}
if(log.isDebugEnabled()) log.debug("Getting section enrollments in " + sectionUuid);
String taRole;
try {
taRole = getSectionTaRole(group);
} catch (RoleConfigurationException rce) {
return new ArrayList<ParticipationRecord>();
}
Set sakaiUserUids = group.getUsersHasRole(taRole);
List sakaiUsers = userDirectoryService.getUsers(sakaiUserUids);
List<ParticipationRecord> membersList = new ArrayList<ParticipationRecord>();
for(Iterator iter = sakaiUsers.iterator(); iter.hasNext();) {
User user = SakaiUtil.convertUser((org.sakaiproject.user.api.User) iter.next());
TeachingAssistantRecordImpl record = new TeachingAssistantRecordImpl(section, user);
membersList.add(record);
}
return membersList;
}
/**
* {@inheritDoc}
*/
public List<EnrollmentRecord> getSectionEnrollments(String sectionUuid) {
Group group = findGroup(sectionUuid);
CourseSection section = getSection(sectionUuid);
if(section == null) {
return new ArrayList<EnrollmentRecord>();
}
if(log.isDebugEnabled()) log.debug("Getting section enrollments in " + sectionUuid);
String studentRole;
try {
studentRole = getSectionStudentRole(group);
} catch (RoleConfigurationException rce) {
log.error(rce);
return new ArrayList<EnrollmentRecord>();
}
Set sakaiUserUids = group.getUsersHasRole(studentRole);
List sakaiUsers = userDirectoryService.getUsers(sakaiUserUids);
List<EnrollmentRecord> membersList = new ArrayList<EnrollmentRecord>();
for(Iterator iter = sakaiUsers.iterator(); iter.hasNext();) {
User user = SakaiUtil.convertUser((org.sakaiproject.user.api.User) iter.next());
EnrollmentRecordImpl record = new EnrollmentRecordImpl(section, null, user);
membersList.add(record);
}
return membersList;
}
/**
* {@inheritDoc}
*/
public List<EnrollmentRecord> findSiteEnrollments(String siteContext, String pattern) {
List<EnrollmentRecord> fullList = getSiteEnrollments(siteContext);
List<EnrollmentRecord> filteredList = new ArrayList<EnrollmentRecord>();
for(Iterator iter = fullList.iterator(); iter.hasNext();) {
EnrollmentRecord record = (EnrollmentRecord)iter.next();
User user = record.getUser();
if(user.getDisplayName().toLowerCase().startsWith(pattern.toLowerCase()) ||
user.getSortName().toLowerCase().startsWith(pattern.toLowerCase()) ||
user.getDisplayId().toLowerCase().startsWith(pattern.toLowerCase())) {
filteredList.add(record);
}
}
return filteredList;
}
/**
* {@inheritDoc}
*/
public String getCategoryName(String categoryId, Locale locale) {
return courseManagementService.getSectionCategoryDescription(categoryId);
}
/**
* {@inheritDoc}
*/
public List<String> getSectionCategories(String siteContext) {
return courseManagementService.getSectionCategories();
}
/**
* {@inheritDoc}
*/
public Course getCourse(String siteContext) {
if(log.isDebugEnabled()) log.debug("Getting course for context " + siteContext);
Site site;
try {
site = getSite(siteContext);
} catch (IdUnusedException e) {
log.error("Could not find site with id = " + siteContext);
return null;
}
return new CourseImpl(site);
}
/**
* {@inheritDoc}
*/
public SectionEnrollments getSectionEnrollmentsForStudents(String siteContext, Set studentUids) {
if(studentUids == null || studentUids.isEmpty()) {
if(log.isDebugEnabled()) log.debug("Null or empty set of student Uids passed to getSectionEnrollmentsForStudents");
return new SectionEnrollmentsImpl(new ArrayList());
}
// Get all sections
List allSections = getSections(siteContext);
// Get all student enrollments in each section, and combine them into a single collection
List<ParticipationRecord> allSectionEnrollments = new ArrayList<ParticipationRecord>();
for(Iterator sectionIter = allSections.iterator(); sectionIter.hasNext();) {
CourseSection section = (CourseSection)sectionIter.next();
List sectionEnrollments = getSectionEnrollments(section.getUuid());
for(Iterator enrollmentIter = sectionEnrollments.iterator(); enrollmentIter.hasNext();) {
EnrollmentRecord enr = (EnrollmentRecord)enrollmentIter.next();
if(studentUids.contains(enr.getUser().getUserUid())) {
allSectionEnrollments.add(enr);
}
}
}
return new SectionEnrollmentsImpl(allSectionEnrollments);
}
/**
* Posts an event to Sakai's event tracking service. only posts events
* that modify some object. Read-only events are not tracked.
*
* @param message The message to post
* @param objectReference The object that was modified in the event
*/
private void postEvent(String message, String objectReference) {
Event event = eventTrackingService.newEvent(message, objectReference, true);
eventTrackingService.post(event);
}
/**
* {@inheritDoc}
*/
public EnrollmentRecord joinSection(String sectionUuid) throws RoleConfigurationException {
try {
return joinSection(sectionUuid, 0);
} catch (SectionFullException e) {
// will never happen
return null;
}
}
/**
* {@inheritDoc}
*/
public EnrollmentRecord joinSection(String sectionUuid, int maxSize) throws RoleConfigurationException, SectionFullException {
CourseSection section = getSection(sectionUuid);
// It's possible that this section has been deleted
if(section == null) {
log.info("Section " + sectionUuid + " has been deleted, so it can't be joined.");
return null;
}
// Disallow if we're in an externally managed site
ensureInternallyManaged(section.getCourse().getUuid());
// Don't let users join multiple sections in the same category
ArrayList categorySections = (ArrayList) getSectionIdsInCategory(section.getCourse().getSiteContext(), section.getCategory());
String userUid = userDirectoryService.getCurrentUser().getId();
// We are not checking the user's role here, but if the user is attempting to join a section
// in this category as a student, then the user's role in any existing category in this section
// must be the same
List membershipInCategory = authzGroupService.getAuthzUserGroupIds(categorySections, userUid);
if (!membershipInCategory.isEmpty()) {
log.info("User " + userUid + " can not enroll in section " + sectionUuid + ". This user is already in section " + membershipInCategory.get(0));
return null;
}
return joinSection(section, maxSize);
}
/**
* Join a section by CourseSection
*/
private EnrollmentRecord joinSection(CourseSection section, int maxSize) throws RoleConfigurationException, SectionFullException {
Group group = findGroup(section.getUuid());
String role = getSectionStudentRole(group);
try {
authzGroupService.joinGroup(section.getUuid(), role, maxSize);
postEvent("section.student.join", section.getUuid());
} catch (AuthzPermissionException e) {
log.info("access denied while attempting to join authz group: ", e);
return null;
} catch (GroupNotDefinedException e) {
log.info("can not find group while attempting to join authz group: ", e);
return null;
} catch (GroupFullException e) {
throw new SectionFullException("section full");
}
// Return the membership record that the app understands
String userUid = sessionManager.getCurrentSessionUserId();
User user = SakaiUtil.getUserFromSakai(userUid);
// Remove from thread local cache
clearGroup(section.getUuid());
clearSite(group.getContainingSite().getId());
return new EnrollmentRecordImpl(section, null, user);
}
private String getSectionStudentRole(AuthzGroup group) throws RoleConfigurationException {
Set roleStrings = group.getRolesIsAllowed(SectionAwareness.STUDENT_MARKER);
if(roleStrings.size() != 1) {
if(log.isDebugEnabled()) log.debug("Group " + group +
" must have one and only one role with permission " +
SectionAwareness.STUDENT_MARKER);
throw new RoleConfigurationException("Can't add a user to a section as a student, since there is no student-flagged role");
}
return (String)roleStrings.iterator().next();
}
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 +
" must have one and only one role with permission " +
SectionAwareness.TA_MARKER);
throw new RoleConfigurationException("Can't add a user to a section as a TA, since there is no TA-flagged role");
}
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}
*/
public void switchSection(String newSectionUuid) throws RoleConfigurationException {
try {
switchSection(newSectionUuid, 0);
} catch (SectionFullException e) {
// will never happen
return;
}
}
/**
* {@inheritDoc}
*/
public void switchSection(String newSectionUuid, int maxSize) throws RoleConfigurationException, SectionFullException {
CourseSection newSection = getSection(newSectionUuid);
// It's possible that this section has been deleted
if(newSection == null) {
return;
}
// Disallow if we're in an externally managed site
ensureInternallyManaged(newSection.getCourse().getUuid());
String userUid = sessionManager.getCurrentSessionUserId();
// Join the new section (could fail if it has filled up since the UI check)
try {
if (joinSection(newSection, maxSize) == null)
// Joining a new section has failed, so don't remove the user from old section
return;
} catch (GroupFullException e) {
throw new SectionFullException("section full");
}
// Find out which sections in this category the student belongs to (should be only one)
ArrayList categorySections = (ArrayList) getSectionIdsInCategory(newSection.getCourse().getSiteContext(), newSection.getCategory());
List membershipInCategory = authzGroupService.getAuthzUserGroupIds(categorySections, userUid);
// Remove any section membership for a section of the same category.
// We can not use dropEnrollmentFromCategory because security checks won't
// allow a student to update the authZ groups directly.
boolean errorDroppingSection = false;
String oldSectionUuid = null;
for(Iterator iter = membershipInCategory.iterator(); iter.hasNext();) {
String sectionUuid = (String) iter.next();
// Skip the current section
if(sectionUuid.equals(newSectionUuid)) {
continue;
}
try {
authzGroupService.unjoinGroup(sectionUuid);
oldSectionUuid = sectionUuid;
} catch (GroupNotDefinedException e) {
errorDroppingSection = true;
log.error("There is no authzGroup with id " + sectionUuid);
} catch (AuthzPermissionException e) {
errorDroppingSection = true;
log.error("Permission denied while " + userUid + " attempted to unjoin authzGroup " + sectionUuid);
}
}
// Only allow the user to remain in the new section if there were no errors dropping section(s)
if(errorDroppingSection) {
// Unjoin the newly joined section
try {
authzGroupService.unjoinGroup(newSectionUuid);
} catch (GroupNotDefinedException e) {
log.debug("Error unjoining newly joined group " + newSectionUuid);
} catch (AuthzPermissionException e) {
log.error("Permission denied while " + userUid + " attempted to unjoin authzGroup " + newSectionUuid);
}
return;
}
// Success, post the events
postEvent("section.student.unjoin", oldSectionUuid);
postEvent("section.student.switch", newSectionUuid);
}
private boolean isMember(String userUid, CourseSection section) {
return authzGroupService.getUserRole(userUid, section.getUuid()) != null;
}
/**
* {@inheritDoc}
*/
public ParticipationRecord addSectionMembership(String userUid, Role role, String sectionUuid)
throws MembershipException, RoleConfigurationException {
if(role.isStudent()) {
// Disallow if we're in an externally managed site
ensureInternallyManaged(getSection(sectionUuid).getCourse().getUuid());
return addStudentToSection(userUid, sectionUuid);
} else if(role.isTeachingAssistant()) {
return addTaToSection(userUid, sectionUuid);
} else {
throw new RuntimeException("Adding a user to a section with role instructor or none is not supported");
}
}
private ParticipationRecord addTaToSection(String userUid, String sectionUuid) throws RoleConfigurationException {
CourseSectionImpl section = (CourseSectionImpl)getSection(sectionUuid);
// It's possible that this section has been deleted
if(section == null) {
return null;
}
Group group = section.getGroup();
User user = SakaiUtil.getUserFromSakai(userUid);
// Add the membership to the framework
String role = getSectionTaRole(group);
group.addMember(userUid, role, true, false);
try {
siteService().saveGroupMembership(group.getContainingSite());
postEvent("section.add.ta", sectionUuid);
} catch (IdUnusedException e) {
log.error("unable to find site: ", e);
return null;
} catch (PermissionException e) {
log.error("access denied while attempting to save site: ", e);
return null;
}
// Return the enrollment record
return new TeachingAssistantRecordImpl(section, user);
}
private EnrollmentRecord addStudentToSection(String userUid, String sectionUuid) throws RoleConfigurationException {
User user = SakaiUtil.getUserFromSakai(userUid);
CourseSectionImpl newSection = (CourseSectionImpl)getSection(sectionUuid);
// It's possible that this section has been deleted
if(newSection == null) {
return null;
}
Group group = newSection.getGroup();
String studentRole = getSectionStudentRole(group);
// Remove any section membership for a section of the same category.
dropEnrollmentFromCategory(userUid, newSection.getCourse().getSiteContext(), newSection.getCategory());
// Add the membership to the framework
if(studentRole == null) {
throw new RoleConfigurationException("Can't add a student to a section, since there is no student-flgagged role");
}
group.addMember(userUid, studentRole, true, false);
try {
siteService().saveGroupMembership(group.getContainingSite());
postEvent("section.add.student", sectionUuid);
} catch (IdUnusedException e) {
log.error("unable to find site: ", e);
return null;
} catch (PermissionException e) {
log.error("access denied while attempting to save site: ", e);
return null;
}
// Return the enrollment record
return new EnrollmentRecordImpl(newSection, null, user);
}
/**
* {@inheritDoc}
*/
public void setSectionMemberships(Set userUids, Role role, String sectionUuid) throws RoleConfigurationException {
if(role.isStudent()) {
// Disallow if we're in an externally managed site
ensureInternallyManaged(getSection(sectionUuid).getCourse().getUuid());
}
CourseSectionImpl section = (CourseSectionImpl)getSection(sectionUuid);
// It's possible that this section has been deleted
if(section == null) {
return;
}
Group group = section.getGroup();
String sakaiRoleString;
if(role.isTeachingAssistant()) {
sakaiRoleString = getSectionTaRole(group);
} else if(role.isStudent()) {
sakaiRoleString = getSectionStudentRole(group);
} else {
String str = "Only students and TAs can be added to sections";
log.error(str);
throw new RuntimeException(str);
}
if(sakaiRoleString == null) {
throw new RoleConfigurationException("Can't set memberships for role " + role +
". No sakai role string can be found for this role.");
}
// Remove the current members in this role
Set currentUserIds = group.getUsersHasRole(sakaiRoleString);
for(Iterator iter = currentUserIds.iterator(); iter.hasNext();) {
String userUid = (String)iter.next();
group.removeMember(userUid);
}
// Add the new members (sure would be nice to have transactions here!)
for(Iterator iter = userUids.iterator(); iter.hasNext();) {
String userUid = (String)iter.next();
group.addMember(userUid, sakaiRoleString, true, false);
}
try {
siteService().saveGroupMembership(group.getContainingSite());
postEvent("section.members.reset", sectionUuid);
} catch (IdUnusedException e) {
log.error("unable to find site: ", e);
} catch (PermissionException e) {
log.error("access denied while attempting to save authz group: ", e);
}
}
/**
* {@inheritDoc}
*/
public void dropSectionMembership(String userUid, String sectionUuid) {
CourseSectionImpl section = (CourseSectionImpl)getSection(sectionUuid);
Group group = section.getGroup();
// If we're trying to drop a student, ensure that we're not automatically managed
Member member = group.getMember(userUid);
String studentRole = null;
try {
studentRole = getSectionStudentRole(group);
} catch (RoleConfigurationException e1) {
log.error("Can't find the student role for section" + sectionUuid);
}
if(studentRole != null && studentRole.equals(member.getRole().getId())) {
// We can not drop students from a section in externally managed sites
ensureInternallyManaged(section.getCourse().getUuid());
}
group.removeMember(userUid);
try {
siteService().saveGroupMembership(group.getContainingSite());
postEvent("section.student.drop", sectionUuid);
} catch (IdUnusedException e) {
log.error("unable to find site: ", e);
} catch (PermissionException e) {
log.error("access denied while attempting to save site: ", e);
}
}
/**
* {@inheritDoc}
*/
public void dropEnrollmentFromCategory(String studentUid, String siteContext, String category) {
// Disallow if we're in an externally managed site
ensureInternallyManaged(getCourse(siteContext).getUuid());
if(log.isDebugEnabled()) log.debug("Dropping " + studentUid + " from all sections in category " + category + " in site " + siteContext);
// Get the sections in this category
Site site;
try {
site = getSite(siteContext);
} catch (IdUnusedException ide) {
log.error("Unable to find site " + siteContext);
return;
}
Collection groups = getSiteGroups(site);
for(Iterator iter = groups.iterator(); iter.hasNext();) {
// Drop the user from this section if they are enrolled
Group group= (Group)iter.next();
CourseSectionImpl section = new CourseSectionImpl(group);
// Don't drop someone from a non-section groups
if(section.getCategory() == null) {
continue;
}
if(section.getCategory().equals(category)) {
// Sakai's current site service code will mark the group as changed
// even if the specified user is not a member, resulting in many
// unnecessary (and possibly unintended) updates.
if (group.getMember(studentUid) != null) {
group.removeMember(studentUid);
}
}
}
try {
siteService().saveGroupMembership(site);
postEvent("section.student.drop.category", site.getReference());
} catch (IdUnusedException e) {
log.error("unable to find site: ", e);
return;
} catch (PermissionException e) {
log.error("access denied while attempting to save site: ", e);
return;
}
}
protected static final String GRP_PREFIX = "section_grp_";
protected Group findGroup(String learningContextUuid) {
if(log.isDebugEnabled()) log.debug("findGroup called for " + learningContextUuid);
Group grp = getGroupInCache(learningContextUuid);
if(grp == null) {
if(log.isDebugEnabled()) log.debug("Looking up group " + learningContextUuid + " from the site service");
grp = siteService().findGroup(learningContextUuid);
//SAK-19996 there are conditions under which this could be null -DH
if (grp != null) {
Site site = grp.getContainingSite();
// Make sure there aren't multiple copies of the same sites and groups in the
// cache.
String siteId = site.getId();
Site cachedSite = getSiteInCache(siteId);
if (cachedSite != null) {
grp = cachedSite.getGroup(learningContextUuid);
} else {
setSiteInCache(siteId, site);
}
setGroupInCache(learningContextUuid, grp);
}
}
return grp;
}
protected Group getGroupInCache(String learningContextUuid) {
return (Group)threadLocalManager.get(GRP_PREFIX + learningContextUuid);
}
protected void setGroupInCache(String learningContextUuid, Group grp) {
threadLocalManager.set(GRP_PREFIX + learningContextUuid, grp);
}
protected void clearGroup(String learningContextUuid) {
threadLocalManager.set(GRP_PREFIX + learningContextUuid, null);
}
protected static final String SITE_PREFIX = "section_site_";
protected Site getSite(String siteId) throws IdUnusedException {
Site site = getSiteInCache(siteId);
if(site == null) {
if(log.isDebugEnabled()) log.debug("Looking up site " + siteId + " from the site service");
site = siteService().getSite(siteId);
setSiteInCache(siteId, site);
}
return site;
}
protected Collection<Group> getSiteGroups(Site site) {
Collection<Group> groups = site.getGroups();
for(Group group : groups) {
setGroupInCache(group.getId(), group);
}
return groups;
}
protected Site getSiteInCache(String siteId) {
return (Site)threadLocalManager.get(SITE_PREFIX + siteId);
}
protected void setSiteInCache(String siteId, Site site) {
threadLocalManager.set(SITE_PREFIX + siteId, site);
}
protected void clearSite(String learningContextUuid) {
threadLocalManager.set(SITE_PREFIX + learningContextUuid, null);
}
/**
* {@inheritDoc}
*/
public int getTotalEnrollments(String learningContextUuid) {
Group group = findGroup(learningContextUuid);
if (group == null) {
log.error("learning context " + learningContextUuid + " not found");
return 0;
}
Set users = group.getUsersIsAllowed(SectionAwareness.STUDENT_MARKER);
return users.size();
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Map getTotalEnrollmentsMap(String learningContextUuid) {
Map roleMap = new HashMap<Role, Integer>();
Group group = findGroup(learningContextUuid);
if (group == null) {
log.error("learning context " + learningContextUuid + " is neither a site nor a section");
return roleMap;
}
roleMap.put(Role.STUDENT, group.getUsersIsAllowed(SectionAwareness.STUDENT_MARKER).size());
roleMap.put(Role.TA, group.getUsersIsAllowed(SectionAwareness.TA_MARKER).size());
roleMap.put(Role.INSTRUCTOR, group.getUsersIsAllowed(SectionAwareness.INSTRUCTOR_MARKER).size());
return roleMap;
}
/**
* {@inheritDoc}
*/
public CourseSection addSection(String courseUuid, String title,
String category, Integer maxEnrollments,
String location, Time startTime,
Time endTime, boolean monday,
boolean tuesday, boolean wednesday, boolean thursday,
boolean friday, boolean saturday, boolean sunday) {
// Get the site
Reference ref = entityManager.newReference(courseUuid);
Site site;
try {
site = getSite(ref.getId());
} catch (IdUnusedException e) {
log.error("Unable to find site " + courseUuid);
return null;
}
CourseSection section = new CourseSectionImpl(getCourse(site.getId()), title,
null, category,maxEnrollments, location, startTime, endTime, monday,
tuesday, wednesday, thursday, friday, saturday, sunday);
List<CourseSection> sections = new ArrayList<CourseSection>();
sections.add(section);
Collection<CourseSection> retValue = addSections(courseUuid, sections);
if (retValue.size() == 1){
return retValue.iterator().next();
}
log.error("section creation failed");
return null;
}
/**
* Throws a SecurityException if an attempt is made to modify a section or its
* student memberships when the site is configured for external management.
*/
private void ensureInternallyManaged(String courseUuid) {
if(isExternallyManaged(courseUuid)) {
throw new SecurityException("Can not make changes to sections or student memberships in site " + courseUuid + ". It is externally managed.");
}
}
private List<Meeting> filterMeetings(List<Meeting> meetings) {
// Remove any empty meetings
List<Meeting> filteredMeetings = new ArrayList<Meeting>();
for(Iterator<Meeting> iter = meetings.iterator(); iter.hasNext();) {
Meeting meeting = iter.next();
if( ! meeting.isEmpty()) {
filteredMeetings.add(meeting);
}
}
return filteredMeetings;
}
private CourseSection addSectionToSite(Site site, String title, String category, Integer maxEnrollments, List<Meeting> meetings) {
Group group = site.addGroup();
// Construct a CourseSection for this group
CourseSectionImpl courseSection = new CourseSectionImpl(group);
// Set the fields of the course section
courseSection.setTitle(title);
courseSection.setDescription(group.getContainingSite().getTitle() + ", " + title);
courseSection.setCategory(category);
courseSection.setMaxEnrollments(maxEnrollments);
courseSection.setMeetings(filterMeetings(meetings));
// Decorate the framework group
courseSection.decorateGroup(group);
return courseSection;
}
public Collection<CourseSection> addSections(String courseUuid, Collection<CourseSection> sections) {
// Disallow if we're in an externally managed site
ensureInternallyManaged(courseUuid);
// Get the site
Reference ref = entityManager.newReference(courseUuid);
Site site;
try {
site = getSite(ref.getId());
} catch (IdUnusedException e) {
log.error("Unable to find site " + courseUuid);
return null;
}
List<CourseSection> addedSections = new ArrayList<CourseSection>();
// Add the decorated groups to the site
for(Iterator<CourseSection> iter = sections.iterator(); iter.hasNext();) {
CourseSection section = iter.next();
addedSections.add(
addSectionToSite(site, section.getTitle(), section.getCategory(),
section.getMaxEnrollments(), section.getMeetings()));
}
// Save the site, along with the new section
try {
siteService().save(site);
clearSite(ref.getId());
for(Iterator<CourseSection> iter = addedSections.iterator(); iter.hasNext();) {
postEvent("section.add", iter.next().getUuid());
}
return addedSections;
} catch (IdUnusedException ide) {
log.error("Error saving site... could not find site " + site.getId(), ide);
} catch (PermissionException pe) {
log.error("Error saving site... permission denied for site " + site.getId(), pe);
}
return null;
}
/**
* {@inheritDoc}
*/
public void updateSection(String sectionUuid, String title,
Integer maxEnrollments, String location, Time startTime,
Time endTime, boolean monday, boolean tuesday,
boolean wednesday, boolean thursday, boolean friday,
boolean saturday, boolean sunday) {
// Create a list of meetings with a single meeting
List<Meeting> meetings = new ArrayList<Meeting>();
MeetingImpl meeting = new MeetingImpl(location, startTime, endTime, monday, tuesday, wednesday, thursday, friday, saturday, sunday);
meetings.add(meeting);
// Update the section with a single meeting
updateSection(sectionUuid, title, maxEnrollments, meetings);
}
public void updateSection(String sectionUuid, String title, Integer maxEnrollments, List<Meeting> meetings) {
CourseSectionImpl section = (CourseSectionImpl)getSection(sectionUuid);
if(section == null) {
throw new RuntimeException("Unable to find section " + sectionUuid);
}
// Disallow if we're in an externally managed site
ensureInternallyManaged(section.getCourse().getUuid());
// Decorate the framework section
Group group = findGroup(sectionUuid);
if (group != null) {
Site site = group.getContainingSite();
if (site != null) {
// Set the decorator's fields
section.setTitle(title);
section.setDescription(site.getTitle() + ", " + title);
section.setMaxEnrollments(maxEnrollments);
section.setMeetings(filterMeetings(meetings));
section.decorateGroup(group);
// Save the site with its new section
try {
siteService().save(site);
clearSite(site.getId());
postEvent("section.update", sectionUuid);
} catch (IdUnusedException ide) {
log.error("Error saving site... could not find site for section " + group, ide);
} catch (PermissionException pe) {
log.error("Error saving site... permission denied for section " + group, pe);
}
} else {
log.error("Error updating section: could not find site for section " + sectionUuid);
}
} else {
log.error("Error updating section: could not find group for section " + sectionUuid);
}
}
/**
* {@inheritDoc}
*/
public void disbandSection(String sectionUuid) {
Set<String> set = new HashSet<String>();
set.add(sectionUuid);
disbandSections(set);
}
/**
* {@inheritDoc}
*/
public void disbandSections(Set<String> sectionUuids) {
if(sectionUuids == null || sectionUuids.isEmpty()) {
return;
}
// Determine the course (site) we're in
String firstSectionUuid = sectionUuids.iterator().next();
CourseSection firstSection = getSection(firstSectionUuid);
if(firstSection == null) {
if(log.isDebugEnabled()) log.debug("Unable to remove section " + firstSectionUuid);
return;
}
Course course = firstSection.getCourse();
// Disallow if we're in an externally managed site
ensureInternallyManaged(course.getUuid());
Site site = null;
for(Iterator<String> iter = sectionUuids.iterator(); iter.hasNext();) {
String sectionUuid = iter.next();
if(log.isDebugEnabled()) log.debug("Disbanding section " + sectionUuid);
Group group = findGroup(sectionUuid);
// TODO Add token in UI to intercept double clicks in action buttons
// SAK-3553 (Clicking remove button twice during section remove operation results in blank iframe.)
if(group == null) {
log.warn("Unable to find group with uuid " + sectionUuid);
return;
}
if(site == null) {
site = group.getContainingSite();
}
site.removeGroup(group);
}
try {
siteService().save(site);
clearSite(site.getId());
for(Iterator<String> iter = sectionUuids.iterator(); iter.hasNext();) {
String sectionUuid = iter.next();
postEvent("section.disband", sectionUuid);
}
} catch (IdUnusedException e) {
log.error("Cound not disband section (can't find section): ",e);
} catch (PermissionException e) {
log.error("Cound not disband section (access denied): ",e);
}
}
public boolean isExternallyManaged(String courseUuid) {
Reference ref = entityManager.newReference(courseUuid);
String siteId = ref.getId();
Site site;
try {
site = getSite(siteId);
} catch (IdUnusedException e) {
throw new RuntimeException("Can not find site " + courseUuid, e);
}
ResourceProperties props = site.getProperties();
return Boolean.toString(true).equals(props.getProperty(CourseImpl.EXTERNALLY_MAINTAINED));
}
public void setExternallyManaged(String courseUuid, boolean externallyManaged) {
// Disallow if the service is configured to be mandatory
ExternalIntegrationConfig appConfig = getConfiguration(null);
if(appConfig == ExternalIntegrationConfig.AUTOMATIC_MANDATORY ||
appConfig == ExternalIntegrationConfig.MANUAL_MANDATORY) {
throw new SecurityException("Can not change the external management of a site, since this service is configured to be " + appConfig);
}
Reference ref = entityManager.newReference(courseUuid);
String siteId = ref.getId();
Site site;
try {
site = getSite(siteId);
} catch (IdUnusedException e) {
throw new RuntimeException("Can not find site " + courseUuid, e);
}
ResourceProperties props = site.getProperties();
// Update the site
props.addProperty(CourseImpl.EXTERNALLY_MAINTAINED, Boolean.toString(externallyManaged));
if(externallyManaged) {
// Also set the self join/switch to false
props.addProperty(CourseImpl.STUDENT_REGISTRATION_ALLOWED, Boolean.toString(false));
props.addProperty(CourseImpl.STUDENT_SWITCHING_ALLOWED, Boolean.toString(false));
}
try {
siteService().save(site);
clearSite(ref.getId());
if(log.isDebugEnabled()) log.debug("Saved site " + site.getTitle());
postEvent("section.external=" + externallyManaged, site.getReference());
} catch (IdUnusedException ide) {
log.error("Error saving site... could not find site " + site, ide);
} catch (PermissionException pe) {
log.error("Error saving site... permission denied for " + site, pe);
}
}
/**
* {@inheritDoc}
*/
public boolean isSelfRegistrationAllowed(String courseUuid) {
Reference ref = entityManager.newReference(courseUuid);
String siteId = ref.getId();
Site site;
try {
site = getSite(siteId);
} catch (IdUnusedException e) {
throw new RuntimeException("Can not find site " + courseUuid, e);
}
ResourceProperties props = site.getProperties();
return Boolean.toString(true).equals(props.getProperty(CourseImpl.STUDENT_REGISTRATION_ALLOWED));
}
public void setJoinOptions(String courseUuid, boolean joinAllowed, boolean switchAllowed) {
// Disallow if we're in an externally managed site
ensureInternallyManaged(courseUuid);
Reference ref = entityManager.newReference(courseUuid);
String siteId = ref.getId();
Site site;
try {
site = getSite(siteId);
} catch (IdUnusedException e) {
throw new RuntimeException("Can not find site " + courseUuid, e);
}
ResourceProperties props = site.getProperties();
// Get the existing join and switch settings, so we know what's changed
boolean oldJoin = Boolean.valueOf(props.getProperty(CourseImpl.STUDENT_REGISTRATION_ALLOWED)).booleanValue();
boolean oldSwitch = Boolean.valueOf(props.getProperty(CourseImpl.STUDENT_SWITCHING_ALLOWED)).booleanValue();
props.addProperty(CourseImpl.STUDENT_REGISTRATION_ALLOWED, Boolean.toString(joinAllowed));
props.addProperty(CourseImpl.STUDENT_SWITCHING_ALLOWED, Boolean.toString(switchAllowed));
try {
siteService().save(site);
clearSite(site.getId());
if(joinAllowed != oldJoin) {
postEvent("section.student.reg=" + joinAllowed, site.getReference());
}
if(switchAllowed != oldSwitch) {
postEvent("section.student.switch=" + switchAllowed, site.getReference());
}
} catch (IdUnusedException ide) {
log.error("Error saving site... could not find site " + site, ide);
} catch (PermissionException pe) {
log.error("Error saving site... permission denied for " + site, pe);
}
}
/**
* {@inheritDoc}
*/
public boolean isSelfSwitchingAllowed(String courseUuid) {
Reference ref = entityManager.newReference(courseUuid);
String siteId = ref.getId();
Site site;
try {
site = getSite(siteId);
} catch (IdUnusedException e) {
throw new RuntimeException("Can not find site " + courseUuid, e);
}
ResourceProperties props = site.getProperties();
return Boolean.toString(true).equals(props.getProperty(CourseImpl.STUDENT_SWITCHING_ALLOWED));
}
/**
* {@inheritDoc}
*/
public List<EnrollmentRecord> getUnsectionedEnrollments(String courseUuid, String category) {
Reference siteRef = entityManager.newReference(courseUuid);
String siteId = siteRef.getId();
// Get all of the sections and userUids of enrolled students
List siteEnrollments = getSiteEnrollments(siteId);
// Get all userUids of students enrolled in sections of this category
List<String> sectionedStudentUids = new ArrayList<String>();
List<String> categorySections = getSectionIdsInCategory(siteId, category);
Set usersbygroup = authzGroupService.getUsersIsAllowedByGroup(SectionAwareness.STUDENT_MARKER, categorySections);
for (Iterator iterator = usersbygroup.iterator(); iterator.hasNext();) {
String[] entry = (String[]) iterator.next();
sectionedStudentUids.add(entry[0]);
}
// Now generate the list of unsectioned enrollments by subtracting the two collections
// Since the APIs return different kinds of objects, we need to iterate
List<EnrollmentRecord> unsectionedEnrollments = new ArrayList<EnrollmentRecord>();
for(Iterator iter = siteEnrollments.iterator(); iter.hasNext();) {
EnrollmentRecord record = (EnrollmentRecord)iter.next();
if(! sectionedStudentUids.contains(record.getUser().getUserUid())) {
unsectionedEnrollments.add(record);
}
}
return unsectionedEnrollments;
}
/**
* {@inheritDoc}
*/
public Map getEnrollmentCount(List sectionSet) {
ArrayList<String> siteGroupRefs = new ArrayList<String>(sectionSet.size());
for(Iterator sectionIter = sectionSet.iterator(); sectionIter.hasNext();) {
CourseSectionImpl section = (CourseSectionImpl)sectionIter.next();
siteGroupRefs.add(section.getGroup().getReference());
}
return authzGroupService.getUserCountIsAllowed(SectionAwareness.STUDENT_MARKER, siteGroupRefs);
}
/**
* {@inheritDoc}
*/
public Map<String,List<ParticipationRecord>> getSectionTeachingAssistantsMap(List sectionSet)
{
ArrayList<String> siteGroupRefs = new ArrayList<String>(sectionSet.size());
Map<String,List<ParticipationRecord>> sectionTaMap = new HashMap<String,List<ParticipationRecord>>();
// Iterate through sections and create an empty TA set for each
for(Iterator sectionIter = sectionSet.iterator(); sectionIter.hasNext();) {
CourseSectionImpl section = (CourseSectionImpl)sectionIter.next();
siteGroupRefs.add(section.getGroup().getReference());
List<ParticipationRecord> membersList = new ArrayList<ParticipationRecord>();
sectionTaMap.put(section.getGroup().getReference(), membersList);
}
Set usersbygroup = authzGroupService.getUsersIsAllowedByGroup(SectionAwareness.TA_MARKER, siteGroupRefs);
// Iterate through user/group pairs and add to the Map
for (Iterator iterator = usersbygroup.iterator(); iterator.hasNext();) {
String[] entry = (String[]) iterator.next();
String useruid = entry[0];
String sectionUuid = entry[1];
List<ParticipationRecord> membersList = sectionTaMap.get(sectionUuid);
try {
org.sakaiproject.user.api.User sakaiUser = userDirectoryService.getUser(useruid);
User user = SakaiUtil.convertUser(sakaiUser);
TeachingAssistantRecordImpl record = new TeachingAssistantRecordImpl(user);
membersList.add(record);
} catch (UserNotDefinedException ex) {
if (log.isDebugEnabled()) log.debug("Userid " + useruid + " found in group " + sectionUuid + " does not exist");
}
}
return sectionTaMap;
}
/**
* {@inheritDoc}
*/
public Set<EnrollmentRecord> getSectionEnrollments(String userUid, String courseUuid) {
if(log.isDebugEnabled()) log.debug("getSectionEnrollments for userUid " + userUid + " courseUuid " + courseUuid);
// Get the user
org.sakaiproject.user.api.User sakaiUser;
try {
sakaiUser = userDirectoryService.getUser(userUid);
} catch (UserNotDefinedException ide) {
log.error("Can not find user with id " + userUid);
return new HashSet<EnrollmentRecord>();
}
User sectionUser = SakaiUtil.convertUser(sakaiUser);
// Get all of the sections
Reference siteRef = entityManager.newReference(courseUuid);
String siteId = siteRef.getId();
List sections = getSections(siteId);
// Generate a set of sections for which this user is enrolled
Set<EnrollmentRecord> sectionEnrollments = new HashSet<EnrollmentRecord>();
ArrayList<String> siteGroupRefs = new ArrayList<String>(sections.size());
for(Iterator sectionIter = sections.iterator(); sectionIter.hasNext();) {
CourseSectionImpl section = (CourseSectionImpl)sectionIter.next();
siteGroupRefs.add(section.getGroup().getReference());
}
List groups = authzGroupService.getAuthzUserGroupIds(siteGroupRefs, userUid);
// Check membership of groups
for (Iterator i = groups.iterator(); i.hasNext();)
{
Group group = findGroup((String)i.next());
Member member = group.getMember(userUid);
if(member == null) {
if (log.isDebugEnabled()) log.debug("getAuthzUserGroupIds said " + userUid + " is member of " + group.getId() + " but getMember disagrees");
continue;
}
if(member.getRole().isAllowed(SectionAwareness.STUDENT_MARKER)) {
sectionEnrollments.add(new EnrollmentRecordImpl(new CourseSectionImpl(group), null, sectionUser));
}
}
return sectionEnrollments;
}
/**
* {@inheritDoc}
*/
public User getSiteEnrollment(String siteContext, String studentUid) {
return SakaiUtil.getUserFromSakai(studentUid);
}
public ExternalIntegrationConfig getConfiguration(Object obj) {
if(config == null) {
log.warn("No integration configuration property has been set. Using " + ExternalIntegrationConfig.MANUAL_DEFAULT);
config = ExternalIntegrationConfig.MANUAL_DEFAULT;
}
return config;
}
// Dependency injection
public void setAuthzGroupService(AuthzGroupService authzGroupService) {
this.authzGroupService = authzGroupService;
}
public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
this.userDirectoryService = userDirectoryService;
}
public void setEventTrackingService(EventTrackingService eventTrackingService) {
this.eventTrackingService = eventTrackingService;
}
public void setConfig(String config) {
if(ExternalIntegrationConfig.AUTOMATIC_MANDATORY.toString().equals(config)) {
this.config = ExternalIntegrationConfig.AUTOMATIC_MANDATORY;
} else if(ExternalIntegrationConfig.AUTOMATIC_DEFAULT.toString().equals(config)){
this.config = ExternalIntegrationConfig.AUTOMATIC_DEFAULT;
} else if(ExternalIntegrationConfig.MANUAL_DEFAULT.toString().equals(config)) {
this.config = ExternalIntegrationConfig.MANUAL_DEFAULT;
} else if(ExternalIntegrationConfig.MANUAL_MANDATORY.toString().equals(config)) {
this.config = ExternalIntegrationConfig.MANUAL_MANDATORY;
} else {
log.warn("Unknown section integration config specified: " + config + ". Using " + ExternalIntegrationConfig.MANUAL_DEFAULT);
}
}
public void setCourseManagementService(CourseManagementService courseManagementService) {
this.courseManagementService = courseManagementService;
}
public void setGroupProvider(GroupProvider groupProvider) {
this.groupProvider = groupProvider;
}
public void setThreadLocalManager(ThreadLocalManager threadLocalManager) {
this.threadLocalManager = threadLocalManager;
}
public Calendar getOpenDate(String siteId) {
//Reference ref = entityManager.newReference(courseUuid);
//String siteId = ref.getId();
Site site;
try {
site = siteService().getSite(siteId);
} catch (IdUnusedException e) {
throw new RuntimeException("Can not find site " + siteId, e);
}
ResourceProperties props = site.getProperties();
String open=props.getProperty(CourseImpl.STUDENT_OPEN_DATE);
Calendar c=null;
if (open!=null && open.length()>0){
c = Calendar.getInstance();
c.setTimeInMillis(Long.parseLong(open));
}
return c;
}
public void setOpenDate(String courseUuid, Calendar openDate) {
// Disallow if the service is configured to be mandatory
Reference ref = entityManager.newReference(courseUuid);
String siteId = ref.getId();
Site site;
try {
site = siteService().getSite(siteId);
} catch (IdUnusedException e) {
throw new RuntimeException("Can not find site " + courseUuid, e);
}
ResourceProperties props = site.getProperties();
// Update the site
if (openDate!=null) {
props.addProperty(CourseImpl.STUDENT_OPEN_DATE, Long.toString(openDate.getTimeInMillis()));
} else {
props.addProperty(CourseImpl.STUDENT_OPEN_DATE, null);
}
try {
siteService().save(site);
if(log.isDebugEnabled()) log.debug("Saved site " + site.getTitle());
} catch (IdUnusedException ide) {
log.error("Error saving site... could not find site " + site, ide);
} catch (PermissionException pe) {
log.error("Error saving site... permission denied for " + site, pe);
}
}
}