Index: lams_central/src/java/org/lamsfoundation/lams/web/GroupingController.java =================================================================== diff -u --- lams_central/src/java/org/lamsfoundation/lams/web/GroupingController.java (revision 0) +++ lams_central/src/java/org/lamsfoundation/lams/web/GroupingController.java (revision a8bfcfbbe983d3069b91882d551d9897b3b4a7fa) @@ -0,0 +1,632 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +package org.lamsfoundation.lams.web; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.contentrepository.exception.InvalidParameterException; +import org.lamsfoundation.lams.integration.UserInfoValidationException; +import org.lamsfoundation.lams.integration.dto.ExtGroupDTO; +import org.lamsfoundation.lams.integration.service.IIntegrationService; +import org.lamsfoundation.lams.integration.util.GroupInfoFetchException; +import org.lamsfoundation.lams.learning.service.ILearnerService; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.BranchingActivity; +import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.GroupComparator; +import org.lamsfoundation.lams.learningdesign.Grouping; +import org.lamsfoundation.lams.learningdesign.GroupingActivity; +import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.security.ISecurityService; +import org.lamsfoundation.lams.usermanagement.Organisation; +import org.lamsfoundation.lams.usermanagement.OrganisationGroup; +import org.lamsfoundation.lams.usermanagement.OrganisationGrouping; +import org.lamsfoundation.lams.usermanagement.OrganisationType; +import org.lamsfoundation.lams.usermanagement.Role; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.dto.OrganisationGroupingDTO; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.util.AlphanumComparator; +import org.lamsfoundation.lams.util.JsonUtil; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +@Controller +@RequestMapping("/organisationGroup") +public class OrganisationGroupController { + private static Logger log = Logger.getLogger(OrganisationGroupController.class); + + private static final String PARAM_USED_FOR_BRANCHING = "usedForBranching"; + + @Autowired + private IUserManagementService userManagementService; + @Autowired + private ILearnerService learnerService; + @Autowired + private ILessonService lessonService; + @Autowired + private ISecurityService securityService; + @Autowired + private IIntegrationService integrationService; + + /** + * Shows course grouping list or redirects to groups if a grouping was already chosen. + * + * @throws Exception + */ + @RequestMapping("/viewGroupings") + @SuppressWarnings("unchecked") + public String viewGroupings(HttpServletRequest request, HttpServletResponse response) + throws GroupInfoFetchException, UserInfoValidationException, IOException { + Long activityID = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID, true); + + Integer userId = getUserDTO().getUserID(); + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID, true); + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); + Organisation organisation = null; + if (organisationId == null) { + organisation = ((Lesson) userManagementService.findById(Lesson.class, lessonId)).getOrganisation(); + // read organisation ID from lesson + organisationId = organisation.getOrganisationId(); + } + if (organisation == null) { + organisation = (Organisation) userManagementService.findById(Organisation.class, organisationId); + } + // get course groupings from top-leve course + if (OrganisationType.CLASS_TYPE.equals(organisation.getOrganisationType().getOrganisationTypeId())) { + organisation = organisation.getParentOrganisation(); + organisationId = organisation.getOrganisationId(); + } + + // check if user is allowed to view and edit groupings + if (!securityService.hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_MANAGER, Role.MONITOR, Role.AUTHOR }, "view organisation groupings")) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a participant in the organisation"); + return null; + } + + List orgGroupings = userManagementService.findByProperty(OrganisationGrouping.class, + "organisationId", organisationId); + Grouping grouping = getLessonGrouping(activityID); + + // show groups page if this is a lesson mode and user have already chosen a grouping or there is no organisation + // groupings available + boolean lessonGroupsExist = (grouping != null) && (grouping.getGroups() != null) + && !grouping.getGroups().isEmpty() && !isDefaultChosenGrouping(grouping); + if (lessonGroupsExist || (activityID != null && orgGroupings.isEmpty())) { + return viewGroups(request, response, organisationId); + } + + // if this grouping is used for branching then it should use groups set in authoring. It will be possible to + // remove users from the groups, but not delete groups due to the branching relationships. + boolean isUsedForBranching = (grouping != null) && grouping.isUsedForBranching(); + request.setAttribute(PARAM_USED_FOR_BRANCHING, isUsedForBranching); + + if (log.isDebugEnabled()) { + log.debug("Displaying course groupings for user " + userId + " and organisation " + organisationId); + } + request.setAttribute(AttributeNames.PARAM_ORGANISATION_ID, organisationId); + + // if it's not a group-based branching and lesson is created using integrations - show groups received from LMS instead of actual LAMS ones + if (!isUsedForBranching && integrationService.isIntegratedServerGroupFetchingAvailable(lessonId)) { + + if (lessonId == null) { + //it's when a learner clicks back button on groups page + Activity activity = learnerService.getActivity(activityID); + lessonId = learnerService.getLessonByActivity(activity).getLessonId(); + request.setAttribute("lessonID", lessonId); + } + + List extGroups = integrationService.getExtGroups(lessonId, null); + request.setAttribute("extGroups", extGroups); + // TODO ? show only with user number >0 + return "extGroups"; + } + + boolean isGroupSuperuser = userManagementService.isUserInRole(userId, organisationId, Role.GROUP_MANAGER); + request.setAttribute("canEdit", isGroupSuperuser || (activityID != null)); + + Set orgGroupingDTOs = new TreeSet<>(); + for (OrganisationGrouping orgGrouping : orgGroupings) { + orgGroupingDTOs.add(new OrganisationGroupingDTO(orgGrouping)); + } + request.setAttribute("groupings", orgGroupingDTOs); + + return "orgGrouping"; + } + + /** + * View groups of the given grouping. + */ + @RequestMapping("/viewGroups") + @SuppressWarnings("unchecked") + public String viewGroups(HttpServletRequest request, HttpServletResponse response, + @RequestParam(value = "organisationID", required = false) Integer organisationId) + throws IOException, GroupInfoFetchException, UserInfoValidationException { + Integer userId = getUserDTO().getUserID(); + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); + Lesson lesson = null; + if (organisationId == null) { + // read organisation ID from lesson + lesson = (Lesson) userManagementService.findById(Lesson.class, lessonId); + organisationId = lesson.getOrganisation().getOrganisationId(); + } + request.setAttribute(AttributeNames.PARAM_LESSON_ID, lessonId); // Needed for the download spreadsheet call. + + // check if user is allowed to view and edit groups + if (!securityService.hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_MANAGER, Role.MONITOR, Role.AUTHOR }, "view organisation groups")) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a participant in the organisation"); + return null; + } + + boolean isGroupSuperuser = userManagementService.isUserInRole(userId, organisationId, Role.GROUP_MANAGER); + + if (log.isDebugEnabled()) { + log.debug("Displaying course groups for user " + userId + " and organisation " + organisationId); + } + Long activityId = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID, true); + + if (activityId == null) { + request.setAttribute("canEdit", isGroupSuperuser); + } else { + Activity activity = (Activity) userManagementService.findById(Activity.class, activityId); + request.setAttribute(AttributeNames.PARAM_TITLE, activity.getTitle()); + request.setAttribute("description", activity.getDescription()); + request.setAttribute("canEdit", true); + } + + ObjectNode orgGroupingJSON = JsonNodeFactory.instance.objectNode(); + orgGroupingJSON.put("organisationId", organisationId); + + Long orgGroupingId = WebUtil.readLongParam(request, "groupingId", true); + OrganisationGrouping orgGrouping = null; + // check if course grouping already exists or it is a new one + if (orgGroupingId != null) { + orgGrouping = (OrganisationGrouping) userManagementService.findById(OrganisationGrouping.class, + orgGroupingId); + if (orgGrouping != null) { + orgGroupingJSON.put("groupingId", orgGroupingId); + orgGroupingJSON.put("name", orgGrouping.getName()); + } + } + + //selected groups from integrated server + String[] extGroupIds = request.getParameterValues("extGroupIds"); + boolean isExternalGroupsSelected = extGroupIds != null && extGroupIds.length > 0; + + // check if any groups already exist in this grouping + Grouping lessonGrouping = getLessonGrouping(activityId); + Set lessonGroups = lessonGrouping == null ? null : lessonGrouping.getGroups(); + if ((activityId != null) && (lessonGrouping != null) && (isExternalGroupsSelected || (orgGroupingId != null)) + && isDefaultChosenGrouping(lessonGrouping)) { + if (log.isDebugEnabled()) { + log.debug("Removing default groups for grouping " + orgGroupingId); + } + + Set groupIDs = new HashSet<>(lessonGroups.size()); + for (Group group : lessonGroups) { + groupIDs.add(group.getGroupId()); + } + for (Long groupId : groupIDs) { + lessonService.removeGroup(lessonGrouping, groupId); + } + + lessonGroups = null; + } + + // if this grouping is used for branching then it should use groups set in authoring. It will be possible to + // remove users from the groups, but not delete groups due to the branching relationships. + boolean isUsedForBranching = (lessonGrouping != null) && lessonGrouping.isUsedForBranching(); + request.setAttribute(PARAM_USED_FOR_BRANCHING, isUsedForBranching); + + ArrayNode orgGroupsJSON = JsonNodeFactory.instance.arrayNode(); + Collection learners = null; + + // if teacher selected groups from integrated server - show them + if (isExternalGroupsSelected) { + + if (lesson == null) { + lesson = (Lesson) userManagementService.findById(Lesson.class, lessonId); + } + learners = lesson.getLessonClass().getLearners(); + + //request all users from selected groups from integrated server + List extGroups = integrationService.getExtGroups(lessonId, extGroupIds); + + // serialize database group objects into JSON + if (extGroups != null) { + + //if there are duplicate users - put them into unassigned column + List allDuplicates = new ArrayList<>(); + for (ExtGroupDTO groupA : extGroups) { + for (ExtGroupDTO groupB : extGroups) { + List usersA = groupA.getUsers(); + List usersB = groupB.getUsers(); + + //proceed for non empty and different groups + if ((usersA != null) && (usersB != null) && !groupA.getGroupId().equals(groupB.getGroupId())) { + + Collection duplicates = CollectionUtils.intersection(usersA, usersB); + allDuplicates.addAll(duplicates); + + usersA.removeAll(duplicates); + usersB.removeAll(duplicates); + } + + } + } + + // sort groups by their name + Collections.sort(extGroups); + for (ExtGroupDTO extGroup : extGroups) { + ObjectNode groupJSON = JsonNodeFactory.instance.objectNode(); + groupJSON.put("name", extGroup.getGroupName()); + groupJSON.put("groupId", extGroup.getGroupId()); + if (extGroup.getUsers() != null) { + for (User groupUser : (List) extGroup.getUsers()) { + ObjectNode groupUserJSON = WebUtil.userToJSON(groupUser); + groupJSON.withArray("users").add(groupUserJSON); + + // remove the user who is already assigned to a group + learners.remove(groupUser); + } + } + orgGroupsJSON.add(groupJSON); + } + } + + // if groups haven't been selected yet - show all available groups in organisation + } else if ((lessonGroups == null) || lessonGroups.isEmpty()) { + + learners = userManagementService.getUsersFromOrganisationByRole(organisationId, Role.LEARNER, true); + Set orgGroups = orgGrouping == null ? null : orgGrouping.getGroups(); + orgGroupsJSON = getOrgGroupsDetails(orgGroups, learners); + + // show already selected groups + } else { + + if (lesson == null) { + lesson = (Lesson) userManagementService.findById(Lesson.class, lessonId); + } + learners = lesson.getLessonClass().getLearners(); + orgGroupsJSON = getLessonGroupsDetails(lessonGroups, learners); + request.setAttribute("skipInitialAssigning", true); + } + orgGroupingJSON.set("groups", orgGroupsJSON); + request.setAttribute("grouping", orgGroupingJSON); + + // all the remaining users are unassigned to any group + ArrayNode unassignedUsersJSON = JsonNodeFactory.instance.arrayNode(); + for (User unassignedUser : learners) { + ObjectNode unassignedUserJSON = WebUtil.userToJSON(unassignedUser); + unassignedUsersJSON.add(unassignedUserJSON); + } + request.setAttribute("unassignedUsers", unassignedUsersJSON); + + return "orgGroup"; + } + + /** + * Saves a course grouping. + */ + @ResponseBody + @RequestMapping(path = "/save", method = RequestMethod.POST) + public void save(HttpServletRequest request, HttpServletResponse response) + throws InvalidParameterException, IOException { + // check if user is allowed to edit groups + Integer userId = getUserDTO().getUserID(); + int organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + // check if user is allowed to save grouping + if (!securityService.hasOrgRole(organisationId, userId, new String[] { Role.GROUP_MANAGER }, + "save organisation grouping")) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a manager or admin in the organisation"); + } + + if (log.isDebugEnabled()) { + log.debug("Saving course groups for user " + userId + " and organisation " + organisationId); + } + + // deserialize grouping + ObjectNode orgGroupingJSON = new ObjectMapper().readValue(request.getParameter("grouping"), ObjectNode.class); + // check if already exists + Long orgGroupingId = JsonUtil.optLong(orgGroupingJSON, "groupingId"); + + // iterate over groups + List orgGroups = new LinkedList<>(); + ArrayNode orgGroupsJSON = JsonUtil.optArray(orgGroupingJSON, "groups"); + if (orgGroupsJSON != null) { + for (JsonNode orgGroupNode : orgGroupsJSON) { + // just overwrite existing groups; they will be updated if already exist + Set users = new HashSet<>(); + ObjectNode orgGroupJSON = (ObjectNode) orgGroupNode; + ArrayNode usersJSON = JsonUtil.optArray(orgGroupJSON, "users"); + if (usersJSON != null) { + // find user objects based on delivered IDs + for (JsonNode learnerId : usersJSON) { + User user = (User) userManagementService.findById(User.class, learnerId.asInt()); + users.add(user); + } + } + + OrganisationGroup orgGroup = new OrganisationGroup(); + Long orgGroupId = JsonUtil.optLong(orgGroupJSON, "groupId"); + if (orgGroupId != null) { + orgGroup.setGroupId(orgGroupId); + orgGroup.setGroupingId(orgGroupingId); + } + orgGroup.setName(JsonUtil.optString(orgGroupJSON, "name")); + orgGroup.setUsers(users); + + orgGroups.add(orgGroup); + } + } + + OrganisationGrouping orgGrouping = null; + if (orgGroupingId != null) { + orgGrouping = (OrganisationGrouping) userManagementService.findById(OrganisationGrouping.class, + orgGroupingId); + } + if (orgGrouping == null) { + orgGrouping = new OrganisationGrouping(); + orgGrouping.setOrganisationId(organisationId); + } + // update grouping name + String orgGroupingName = JsonUtil.optString(orgGroupingJSON, "name"); + orgGrouping.setName(orgGroupingName); + + userManagementService.saveOrganisationGrouping(orgGrouping, orgGroups); + } + + /** + * Deletes course grouping with the given ID. + */ + @RequestMapping(path = "/removeGrouping", method = RequestMethod.POST) + public String removeGrouping(HttpServletRequest request, HttpServletResponse response) + throws IOException, GroupInfoFetchException, UserInfoValidationException { + // check if user is allowed to edit groups + Integer userId = getUserDTO().getUserID(); + int organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + if (!securityService.hasOrgRole(organisationId, userId, new String[] { Role.GROUP_MANAGER }, + "remove organisation grouping")) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a manager or admin in the organisation"); + return null; + } + + Long groupingId = WebUtil.readLongParam(request, "groupingId"); + if (log.isDebugEnabled()) { + log.debug( + "Removing grouping " + groupingId + " for user " + userId + " and organisation " + organisationId); + } + userManagementService.deleteById(OrganisationGrouping.class, groupingId); + + return viewGroupings(request, response); + } + + /** + * Fetches course and branching so they can get matched by user. + */ + @ResponseBody + @RequestMapping("/getGroupsForMapping") + public void getGroupsForMapping(HttpServletRequest request, HttpServletResponse response) throws IOException { + Long orgGroupingId = WebUtil.readLongParam(request, "groupingId"); + Long activityID = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + + OrganisationGrouping orgGrouping = (OrganisationGrouping) userManagementService + .findById(OrganisationGrouping.class, orgGroupingId); + ArrayNode groupsJSON = JsonNodeFactory.instance.arrayNode(); + SortedSet orgGroups = new TreeSet<>(orgGrouping.getGroups()); + for (OrganisationGroup group : orgGroups) { + ObjectNode groupJSON = JsonNodeFactory.instance.objectNode(); + groupJSON.put("id", group.getGroupId()); + groupJSON.put("name", group.getName()); + groupsJSON.add(groupJSON); + } + Activity activity = (Activity) userManagementService.findById(Activity.class, activityID); + Grouping grouping = activity.isGroupingActivity() ? ((GroupingActivity) activity).getCreateGrouping() + : ((BranchingActivity) activity).getGrouping(); + + ArrayNode branchesJSON = JsonNodeFactory.instance.arrayNode(); + SortedSet groups = new TreeSet<>(grouping.getGroups()); + for (Group group : groups) { + ObjectNode groupJSON = JsonNodeFactory.instance.objectNode(); + groupJSON.put("id", group.getGroupId()); + groupJSON.put("name", group.getGroupName()); + branchesJSON.add(groupJSON); + } + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.set("branches", branchesJSON); + responseJSON.set("groups", groupsJSON); + + response.setContentType("application/json;charset=utf-8"); + response.getWriter().write(responseJSON.toString()); + } + + /** + * Stores course groups to branching groups mapping. + */ + @ResponseBody + @RequestMapping(path = "/saveGroupMappings", method = RequestMethod.POST) + public void saveGroupMappings(HttpServletRequest request, HttpServletResponse response) throws IOException { + ArrayNode groupMapping = JsonUtil.readArray(request.getParameter("mapping")); + for (JsonNode entryNode : groupMapping) { + ObjectNode entry = (ObjectNode) entryNode; + Long orgGroupID = JsonUtil.optLong(entry, "groupID"); + Long branchingGroupID = JsonUtil.optLong(entry, "branchID"); + OrganisationGroup orgGroup = (OrganisationGroup) userManagementService.findById(OrganisationGroup.class, + orgGroupID); + Group branchingGroup = (Group) userManagementService.findById(Group.class, branchingGroupID); + // put all users from course group to mapped branching group + branchingGroup.getUsers().addAll(orgGroup.getUsers()); + userManagementService.save(branchingGroup); + } + response.setContentType("text/plain;charset=utf-8"); + // Javascript waits for this response + response.getWriter().write("OK"); + } + + /** + * Build JSON objects based on existing lesson-level groups. + */ + private ArrayNode getLessonGroupsDetails(Set groups, Collection learners) { + // serialize database group objects into JSON + ArrayNode groupsJSON = JsonNodeFactory.instance.arrayNode(); + if (groups != null) { + // sort groups by their name + List groupList = new LinkedList<>(groups); + Collections.sort(groupList, new GroupComparator()); + for (Group group : groupList) { + ObjectNode groupJSON = JsonNodeFactory.instance.objectNode(); + groupJSON.put("name", group.getGroupName()); + groupJSON.put("groupId", group.getGroupId()); + groupJSON.put("locked", !group.mayBeDeleted()); + if (group.getUsers() != null) { + for (User groupUser : group.getUsers()) { + ObjectNode groupUserJSON = WebUtil.userToJSON(groupUser); + groupJSON.withArray("users").add(groupUserJSON); + + // remove the user who is already assigned to a group + learners.remove(groupUser); + } + } + groupsJSON.add(groupJSON); + } + } + + return groupsJSON; + } + + /** + * Build JSON objects based on existing course-level groups. + */ + private ArrayNode getOrgGroupsDetails(Set groups, Collection learners) { + + final Comparator ORG_GROUP_COMPARATOR = new Comparator<>() { + @Override + public int compare(OrganisationGroup o1, OrganisationGroup o2) { + String grp1Name = o1 != null ? o1.getName() : ""; + String grp2Name = o2 != null ? o2.getName() : ""; + + AlphanumComparator comparator = new AlphanumComparator(); + return comparator.compare(grp1Name, grp2Name); + } + }; + + // serialize database group objects into JSON + ArrayNode groupsJSON = JsonNodeFactory.instance.arrayNode(); + if (groups != null) { + // sort groups by their name + List groupList = new LinkedList<>(groups); + Collections.sort(groupList, ORG_GROUP_COMPARATOR); + + for (OrganisationGroup group : groupList) { + ObjectNode groupJSON = JsonNodeFactory.instance.objectNode(); + groupJSON.put("name", group.getName()); + groupJSON.put("groupId", group.getGroupId()); + for (User groupUser : group.getUsers()) { + ObjectNode groupUserJSON = WebUtil.userToJSON(groupUser); + groupJSON.withArray("users").add(groupUserJSON); + + // remove the user who is already assigned to a group + learners.remove(groupUser); + } + + groupsJSON.add(groupJSON); + } + } + + return groupsJSON; + } + + /** + * Checks if lesson-level groups exist for the given activity. + */ + private Grouping getLessonGrouping(Long activityID) { + if (activityID != null) { + // we need to fetch real objects instead of stubs/proxies + Activity activity = (Activity) userManagementService.findById(Activity.class, activityID); + Grouping grouping = activity.isChosenBranchingActivity() ? activity.getGrouping() + : ((GroupingActivity) userManagementService.findById(GroupingActivity.class, activityID)) + .getCreateGrouping(); + + return grouping; + } + + return null; + } + + /** + * Check if the given groups are default for chosen grouping. There is actually no good way to detect this, but even + * if a custom grouping is mistaken for the default one, it should bring little harm. + */ + private boolean isDefaultChosenGrouping(Grouping grouping) { + Set groups = grouping.getGroups(); + for (Group group : groups) { + if (!group.getUsers().isEmpty()) { + return false; + } + } + if (groups == null || (grouping.getMaxNumberOfGroups() != null + && !grouping.getMaxNumberOfGroups().equals(groups.size()))) { + return false; + } + return true; + } + + private UserDTO getUserDTO() { + HttpSession ss = SessionManager.getSession(); + return (UserDTO) ss.getAttribute(AttributeNames.USER); + } +} \ No newline at end of file Fisheye: Tag a8bfcfbbe983d3069b91882d551d9897b3b4a7fa refers to a dead (removed) revision in file `lams_central/src/java/org/lamsfoundation/lams/web/OrganisationGroupController.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a8bfcfbbe983d3069b91882d551d9897b3b4a7fa refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingAJAXController.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingController.java (revision a8bfcfbbe983d3069b91882d551d9897b3b4a7fa) @@ -0,0 +1,398 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +package org.lamsfoundation.lams.monitoring.web; + +import java.io.IOException; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.GroupComparator; +import org.lamsfoundation.lams.learningdesign.Grouping; +import org.lamsfoundation.lams.learningdesign.GroupingActivity; +import org.lamsfoundation.lams.lesson.service.LessonServiceException; +import org.lamsfoundation.lams.monitoring.service.IMonitoringFullService; +import org.lamsfoundation.lams.security.ISecurityService; +import org.lamsfoundation.lams.usermanagement.OrganisationGroup; +import org.lamsfoundation.lams.usermanagement.OrganisationGrouping; +import org.lamsfoundation.lams.usermanagement.Role; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.usermanagement.util.FirstNameAlphabeticComparator; +import org.lamsfoundation.lams.util.Configuration; +import org.lamsfoundation.lams.util.ConfigurationKeys; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The action servlet that provides the support for the + *
    + *
  • AJAX based Chosen Grouping screen
  • + *
  • forwards to the learner's view grouping screen for Random Grouping.
  • + *
+ * + * @author Fiona Malikoff + */ +@Controller +@RequestMapping("/grouping") +public class GroupingAJAXController { + private static Logger log = Logger.getLogger(GroupingAJAXController.class); + + @Autowired + private IMonitoringFullService monitoringService; + @Autowired + private ISecurityService securityService; + @Autowired + private IUserManagementService userManagementService; + + private static final String PARAM_ACTIVITY_TITLE = "title"; + private static final String PARAM_ACTIVITY_DESCRIPTION = "description"; + public static final String PARAM_MAX_NUM_GROUPS = "maxNumberOfGroups"; + public static final String PARAM_NAME = "name"; + public static final String PARAM_MEMBERS = "members"; + public static final String PARAM_MAY_DELETE = "mayDelete"; + public static final String PARAM_USED_FOR_BRANCHING = "usedForBranching"; + public static final String PARAM_VIEW_MODE = "viewMode"; + public static final String GROUPS = "groups"; + + /** + * Start the process of doing the chosen grouping + * + * Input parameters: activityID + */ + @RequestMapping("/startGrouping") + public String startGrouping(HttpServletRequest request) throws IOException, ServletException { + return startGrouping(request, false); + } + + private String startGrouping(HttpServletRequest request, boolean forcePrintView) + throws IOException, ServletException { + + Long activityID = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Activity activity = monitoringService.getActivityById(activityID); + + Grouping grouping = null; + if (activity.isChosenBranchingActivity()) { + grouping = activity.getGrouping(); + monitoringService.createChosenBranchingGroups(activityID); + } else { + grouping = ((GroupingActivity) activity).getCreateGrouping(); + } + + if (!forcePrintView && grouping.isChosenGrouping()) { + return "redirect:" + Configuration.get(ConfigurationKeys.SERVER_URL) + + "/organisationGroup/viewGroupings.do?lessonID=" + lessonId + "&activityID=" + activityID; + } + + request.setAttribute(AttributeNames.PARAM_ACTIVITY_ID, activityID); + request.setAttribute(AttributeNames.PARAM_LESSON_ID, lessonId); + request.setAttribute(GroupingAJAXController.PARAM_ACTIVITY_TITLE, activity.getTitle()); + request.setAttribute(GroupingAJAXController.PARAM_ACTIVITY_DESCRIPTION, activity.getDescription()); + + SortedSet groups = new TreeSet<>(new GroupComparator()); + groups.addAll(grouping.getGroups()); + + // sort users with first, then last name, then login + Comparator userComparator = new FirstNameAlphabeticComparator(); + for (Group group : groups) { + Set sortedUsers = new TreeSet<>(userComparator); + sortedUsers.addAll(group.getUsers()); + group.setUsers(sortedUsers); + } + + request.setAttribute(GroupingAJAXController.GROUPS, groups); + // go to a view only screen for random grouping + return "grouping/viewGroups"; + } + + /** + * Called by the chosen grouping / course grouping screen to show a print version of the grouping. + */ + @RequestMapping("/printGrouping") + public String printGrouping(HttpServletRequest request) throws IOException, ServletException { + + Long activityID = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID, true); + if (activityID != null) { + // normal activity based processing, startGrouping can handle it as it supports the normal view screen in monitoring + return startGrouping(request, true); + } + + // Not activity? Then it must be the course grouping print view request + Long orgGroupingId = WebUtil.readLongParam(request, "groupingId", true); + OrganisationGrouping orgGrouping = null; + if (orgGroupingId != null) { + orgGrouping = (OrganisationGrouping) userManagementService.findById(OrganisationGrouping.class, + orgGroupingId); + } + + SortedSet groups = new TreeSet<>(); + if (orgGrouping != null) { + groups.addAll(orgGrouping.getGroups()); + + // sort users with first, then last name, then login + Comparator userComparator = new FirstNameAlphabeticComparator(); + for (OrganisationGroup group : groups) { + Set sortedUsers = new TreeSet<>(userComparator); + sortedUsers.addAll(group.getUsers()); + group.setUsers(sortedUsers); + } + } + + request.setAttribute(GroupingAJAXController.GROUPS, groups); + request.setAttribute("isCourseGrouping", true); // flag to page it is a course grouping so use the field names for OrganisationGroup + return "grouping/viewGroups"; + } + + /** + * Moves users between groups, removing them from previous group and creating a new one, if needed. + */ + @RequestMapping(path = "/addMembers", method = RequestMethod.POST) + @ResponseBody + public String addMembers(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("application/json;charset=utf-8"); + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + boolean result = true; + + Long activityID = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + String membersParam = WebUtil.readStrParam(request, GroupingAJAXController.PARAM_MEMBERS, true); + String[] members = StringUtils.isBlank(membersParam) ? null : membersParam.split(","); + + // remove users from current group + if (members != null) { + Activity activity = monitoringService.getActivityById(activityID); + Grouping grouping = activity.isChosenBranchingActivity() ? activity.getGrouping() + : ((GroupingActivity) activity).getCreateGrouping(); + User exampleUser = (User) userManagementService.findById(User.class, Integer.valueOf(members[0])); + Group group = grouping.getGroupBy(exampleUser); + // null group means that user is not assigned anywhere in this grouping + if (!group.isNull()) { + // check if user can be moved outside of this group + result = group.mayBeDeleted(); + + if (result) { + if (log.isDebugEnabled()) { + log.debug("Removing users " + membersParam.toString() + " from group " + group.getGroupId() + + " in activity " + activityID); + } + + try { + monitoringService.removeUsersFromGroup(activityID, group.getGroupId(), members); + } catch (LessonServiceException e) { + log.error(e); + result = false; + } + } + + if (!result) { + // let JSP page know that this group became immutable + responseJSON.put("locked", true); + } + } + } + + Long groupID = WebUtil.readLongParam(request, AttributeNames.PARAM_GROUP_ID, true); + // no group ID means that it has to be created + // group ID = -1 means that user is not being assigned to any new group, i.e. becomse unassigned + if (result && ((groupID == null) || (groupID > 0))) { + if (groupID == null) { + String name = WebUtil.readStrParam(request, GroupingAJAXController.PARAM_NAME); + if (log.isDebugEnabled()) { + log.debug("Creating group with name \"" + name + "\" in activity " + activityID); + } + Group group = monitoringService.addGroup(activityID, name, true); + if (group == null) { + // group creation failed + result = false; + } else { + groupID = group.getGroupId(); + // let JSP page know that the group was given this ID + responseJSON.put("groupId", groupID); + } + } + + if (result && (members != null)) { + if (log.isDebugEnabled()) { + log.debug("Adding users " + membersParam.toString() + " to group " + groupID + " in activity " + + activityID); + } + + // add users to the given group + try { + monitoringService.addUsersToGroup(activityID, groupID, members); + } catch (LessonServiceException e) { + log.error(e); + result = false; + } + } + } + + responseJSON.put("result", result); + return responseJSON.toString(); + } + + /** + * Stores lesson grouping as a course grouping. + */ + @RequestMapping(path = "/saveAsCourseGrouping", method = RequestMethod.POST) + @ResponseBody + public String saveAsCourseGrouping(HttpServletRequest request, HttpServletResponse response) throws IOException { + + HttpSession ss = SessionManager.getSession(); + Integer userId = ((UserDTO) ss.getAttribute(AttributeNames.USER)).getUserID(); + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + String newGroupingName = request.getParameter("name"); + + // check if user is allowed to view and edit groupings + if (!securityService.hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_MANAGER, Role.MONITOR, Role.AUTHOR }, "view organisation groupings")) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a participant in the organisation"); + return null; + } + + Long activityID = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + Activity activity = monitoringService.getActivityById(activityID); + Grouping grouping = activity.isChosenBranchingActivity() ? activity.getGrouping() + : ((GroupingActivity) activity).getCreateGrouping(); + + // iterate over groups + List orgGroups = new LinkedList<>(); + for (Group group : grouping.getGroups()) { + OrganisationGroup orgGroup = new OrganisationGroup(); + //groupId and GroupingId will be set during userManagementService.saveOrganisationGrouping() call + orgGroup.setName(group.getGroupName()); + HashSet users = new HashSet<>(); + users.addAll(group.getUsers()); + orgGroup.setUsers(users); + + orgGroups.add(orgGroup); + } + + OrganisationGrouping orgGrouping = new OrganisationGrouping(); + orgGrouping.setOrganisationId(organisationId); + orgGrouping.setName(newGroupingName); + + userManagementService.saveOrganisationGrouping(orgGrouping, orgGroups); + + response.setContentType("application/json;charset=utf-8"); + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("result", true); + return responseJSON.toString(); + } + + /** + * Renames the group. + */ + @RequestMapping(path = "/changeGroupName", method = RequestMethod.POST) + public String changeGroupName(HttpServletRequest request) { + Long groupID = WebUtil.readLongParam(request, AttributeNames.PARAM_GROUP_ID); + String name = WebUtil.readStrParam(request, GroupingAJAXController.PARAM_NAME); + if (name != null) { + if (log.isDebugEnabled()) { + log.debug("Renaming group " + groupID + " to \"" + name + "\""); + } + monitoringService.setGroupName(groupID, name); + } + return null; + } + + /** + * Checks if a course grouping name is unique inside of this organisation and thus whether the new group can be + * named using it + */ + @RequestMapping("/checkGroupingNameUnique") + @ResponseBody + public String checkGroupingNameUnique(HttpServletRequest request, HttpServletResponse response) throws IOException { + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + String newGroupingName = request.getParameter("name"); + + // Checks if a course grouping name is unique inside of this group and thus new group can have it + HashMap properties = new HashMap<>(); + properties.put("organisationId", organisationId); + properties.put("name", newGroupingName); + List orgGroupings = userManagementService.findByProperties(OrganisationGrouping.class, + properties); + boolean isGroupingNameUnique = orgGroupings.isEmpty(); + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("isGroupingNameUnique", isGroupingNameUnique); + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + /** + * Checks if a group can be removed and performs it. + */ + @RequestMapping(path = "/removeGroup", method = RequestMethod.POST) + @ResponseBody + public String removeGroup(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("application/json;charset=utf-8"); + Long activityID = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + Long groupID = WebUtil.readLongParam(request, AttributeNames.PARAM_GROUP_ID); + boolean result = true; + + // check if the group can be removed + Group group = (Group) userManagementService.findById(Group.class, groupID); + result = group.mayBeDeleted(); + + if (result) { + try { + if (log.isDebugEnabled()) { + log.debug("Removing group " + groupID + " from activity " + activityID); + } + monitoringService.removeGroup(activityID, groupID); + } catch (LessonServiceException e) { + log.error(e); + result = false; + } + } + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("result", result); + return responseJSON.toString(); + } +} Fisheye: Tag a8bfcfbbe983d3069b91882d551d9897b3b4a7fa refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingUploadAJAXController.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingUploadController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingUploadController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingUploadController.java (revision a8bfcfbbe983d3069b91882d551d9897b3b4a7fa) @@ -0,0 +1,532 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +package org.lamsfoundation.lams.monitoring.web; + +import java.io.IOException; +import java.security.InvalidParameterException; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.CellType; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.GroupComparator; +import org.lamsfoundation.lams.learningdesign.Grouping; +import org.lamsfoundation.lams.learningdesign.GroupingActivity; +import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.monitoring.service.IMonitoringFullService; +import org.lamsfoundation.lams.monitoring.web.form.FileUploadForm; +import org.lamsfoundation.lams.security.ISecurityService; +import org.lamsfoundation.lams.usermanagement.Organisation; +import org.lamsfoundation.lams.usermanagement.OrganisationGroup; +import org.lamsfoundation.lams.usermanagement.OrganisationGrouping; +import org.lamsfoundation.lams.usermanagement.Role; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.util.AlphanumComparator; +import org.lamsfoundation.lams.util.FileUtil; +import org.lamsfoundation.lams.util.MessageService; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.util.excel.ExcelRow; +import org.lamsfoundation.lams.util.excel.ExcelSheet; +import org.lamsfoundation.lams.util.excel.ExcelUtil; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The action servlet that provides the support for the AJAX based Chosen Grouping upload from File + * + * @author Fiona Malikoff + */ +@Controller +@RequestMapping("/groupingUpload") +public class GroupingUploadAJAXController { + private static Logger log = Logger.getLogger(GroupingUploadAJAXController.class); + + @Autowired + private IMonitoringFullService monitoringService; + @Autowired + private IUserManagementService userManagementService; + @Autowired + private ILessonService lessonService; + @Autowired + private ISecurityService securityService; + @Autowired + @Qualifier("centralMessageService") + private MessageService messageService; + + /** + * Get the spreadsheet file containing list of the current users, ready for uploading with groups. If lesson + * supplied, list lesson users, otherwise list organisation users (course grouping screen has just the + * organisation). + * + * @throws Exception + */ + @RequestMapping("/getGroupTemplateFile") + public void getGroupTemplateFile(HttpServletRequest request, HttpServletResponse response) throws Exception { + + Integer userId = getUserDTO().getUserID(); + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID, true); + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); + + if (lessonId == null && organisationId == null) { + log.error("Cannot create group template file as lessonId and organisationId are both null."); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid parameters"); + } + + Lesson lesson = null; + String lessonOrOrganisationName = null; + + if (lessonId != null) { + lesson = (Lesson) userManagementService.findById(Lesson.class, lessonId); + lessonOrOrganisationName = lesson.getLessonName(); + organisationId = lesson.getOrganisation().getOrganisationId(); + } else { + Organisation organisation = (Organisation) userManagementService.findById(Organisation.class, + organisationId); + lessonOrOrganisationName = organisation.getName(); + } + + // check if user is allowed to view and edit groups + if (!securityService.hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_MANAGER, Role.MONITOR, Role.AUTHOR }, "view organisation groups")) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a participant in the organisation"); + return; + } + + String fileName = new StringBuilder(messageService.getMessage("filename.create.grouping.template").trim()) + .append(" ").append(lessonOrOrganisationName).append(".xls").toString().replaceAll(" ", "-"); + fileName = FileUtil.encodeFilenameForDownload(request, fileName); + + List sheets; + if (lesson != null) { + Set learners = lesson.getLessonClass().getLearners(); + // check for any groups already exist in this grouping + Long activityId = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + Activity activity = monitoringService.getActivityById(activityId); + Grouping grouping = activity.isChosenBranchingActivity() ? activity.getGrouping() + : ((GroupingActivity) activity).getCreateGrouping(); + sheets = exportLearnersForGrouping(learners, grouping.getGroups(), null); + + } else { + Long groupingId = WebUtil.readLongParam(request, "groupingId", true); + Set groups = null; + if (groupingId != null) { + OrganisationGrouping orgGrouping = (OrganisationGrouping) userManagementService + .findById(OrganisationGrouping.class, groupingId); + if (orgGrouping != null) { + groups = orgGrouping.getGroups(); + } + } + Vector learners = userManagementService.getUsersFromOrganisationByRole(organisationId, Role.LEARNER, + true); + sheets = exportLearnersForGrouping(learners, null, groups); + } + + // set cookie that will tell JS script that export has been finished + WebUtil.setFileDownloadTokenCookie(request, response); + + response.setContentType("application/x-download"); + response.setHeader("Content-Disposition", "attachment;filename=" + fileName); + ServletOutputStream out = response.getOutputStream(); + ExcelUtil.createExcel(out, sheets, null, false, false); + } + + private List exportLearnersForGrouping(Collection learners, Set groups, + Set orgGroups) { + List sheets = new LinkedList<>(); + ExcelSheet excelSheet = new ExcelSheet(messageService.getMessage("label.course.groups.prefix")); + sheets.add(excelSheet); + + ExcelRow titleRow = excelSheet.initRow(); + titleRow.addCell(messageService.getMessage("spreadsheet.column.login")); + titleRow.addCell(messageService.getMessage("spreadsheet.column.firstname")); + titleRow.addCell(messageService.getMessage("spreadsheet.column.lastname")); + titleRow.addCell(messageService.getMessage("spreadsheet.column.groupname")); + + if (groups != null) { + List groupList = new LinkedList<>(groups); + Collections.sort(groupList, new GroupComparator()); + for (Group group : groupList) { + String groupName = group.getGroupName(); + for (User groupUser : group.getUsers()) { + generateUserRow(groupName, groupUser, excelSheet); + learners.remove(groupUser); + } + } + } + + if (orgGroups != null) { + List groupList = new LinkedList<>(orgGroups); + for (OrganisationGroup group : groupList) { + String groupName = group.getName(); + for (User groupUser : group.getUsers()) { + generateUserRow(groupName, groupUser, excelSheet); + learners.remove(groupUser); + } + } + } + + // all the remaining users are unassigned to any group + for (User unassignedUser : learners) { + generateUserRow(null, unassignedUser, excelSheet); + } + + return sheets; + } + + private void generateUserRow(String groupName, User groupUser, ExcelSheet excelSheet) { + ExcelRow userRow = excelSheet.initRow(); + userRow.addCell(groupUser.getLogin()); + userRow.addCell(groupUser.getFirstName()); + userRow.addCell(groupUser.getLastName()); + userRow.addCell(groupName); + } + + /** + * Saves a course grouping. + */ + @RequestMapping("/importLearnersForGrouping") + @ResponseBody + public String importLearnersForGrouping(@ModelAttribute("uploadForm") FileUploadForm uploadForm, + HttpServletRequest request, HttpServletResponse response) throws InvalidParameterException, IOException { + + Integer userId = getUserDTO().getUserID(); + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID, true); + boolean isLessonMode = WebUtil.readBooleanParam(request, "lessonMode", true); + + // used for lesson based grouping + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); + Long activityId = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID, true); + Lesson lesson = lessonId != null ? (Lesson) userManagementService.findById(Lesson.class, lessonId) : null; + + // used for course grouping + Long groupingId = WebUtil.readLongParam(request, "groupingId", true); + String name = WebUtil.readStrParam(request, "name", true); + if (isLessonMode && activityId == null) { + log.error("Lesson grouping to be saved but activityId is missing"); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid parameters"); + } else if (!isLessonMode) { + if ((groupingId == null && name == null) || organisationId == null) { + log.error("Course grouping to be saved but groupingId, grouping name or organisationId is missing"); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid parameters"); + } + } + + Organisation organisation; + if (organisationId == null) { + // read organisation ID from lesson + organisation = lesson.getOrganisation(); + organisationId = organisation.getOrganisationId(); + } else { + organisation = (Organisation) userManagementService.findById(Organisation.class, organisationId); + } + + // check if user is allowed to save grouping + if (!securityService.hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_MANAGER, Role.MONITOR, Role.AUTHOR }, + "save organisation grouping from spreadsheet")) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a manager or admin in the organisation"); + return null; + } + + MultipartFile file = uploadForm.getGroupUploadFile(); + if (file == null || file.getSize() == 0) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "No file provided"); + return null; + } + if (log.isDebugEnabled()) { + log.debug("Saving course groups from spreadsheet for user " + userId + " and organisation " + organisationId + + " filename " + file); + } + + ObjectNode responseJSON = isLessonMode ? saveLessonGrouping(lessonId, activityId, file) + : saveCourseGrouping(organisation, groupingId, name, file); + + return responseJSON.toString(); + } + + /** Create the new course grouping */ + private ObjectNode saveCourseGrouping(Organisation organisation, Long orgGroupingId, String name, + MultipartFile fileElements) throws IOException { + + OrganisationGrouping orgGrouping = null; + if (orgGroupingId != null) { + orgGrouping = (OrganisationGrouping) userManagementService.findById(OrganisationGrouping.class, + orgGroupingId); + } + if (orgGrouping == null) { + orgGrouping = new OrganisationGrouping(); + orgGrouping.setOrganisationId(organisation.getOrganisationId()); + orgGrouping.setName(name); + } + + Map existingGroupNameToId = new HashMap<>(); + if (orgGrouping.getGroups() != null) { + for (OrganisationGroup group : orgGrouping.getGroups()) { + existingGroupNameToId.put(group.getName(), group.getGroupId()); + } + } + + Map> groups = new HashMap<>(); + int totalUsersSkipped = parseGroupSpreadsheet(fileElements, orgGroupingId, groups); + int totalUsersAdded = 0; + + List orgGroups = new LinkedList<>(); + for (Map.Entry> groupEntry : groups.entrySet()) { + String groupName = groupEntry.getKey(); + // just overwrite existing groups; they will be updated if already exist + Set learners = new HashSet<>(); + for (String login : groupEntry.getValue()) { + User learner = userManagementService.getUserByLogin(login); + if (learner == null) { + log.warn("Unable to add learner " + login + " for group in related to grouping " + orgGroupingId + + " as learner cannot be found."); + totalUsersSkipped++; + + //Check user is a part of the organisation + } else if (!securityService.hasOrgRole(organisation.getOrganisationId(), learner.getUserId(), + new String[] { Role.GROUP_MANAGER, Role.LEARNER, Role.MONITOR, Role.AUTHOR }, + "be added to grouping", true)) { + + totalUsersSkipped++; + + } else { + totalUsersAdded++; + learners.add(learner); + } + } + OrganisationGroup orgGroup = new OrganisationGroup(); + Long orgGroupId = existingGroupNameToId.get(groupName); + if (orgGroupId != null) { + orgGroup.setGroupId(orgGroupId); + orgGroup.setGroupingId(orgGroupingId); + } + orgGroup.setName(groupName); + orgGroup.setUsers(learners); + orgGroups.add(orgGroup); + } + + userManagementService.saveOrganisationGrouping(orgGrouping, orgGroups); + return createResponseJSON(false, null, true, orgGrouping.getGroupingId(), totalUsersAdded, totalUsersSkipped); + + } + + /** Clean out and reuse any existing groups */ + private ObjectNode saveLessonGrouping(Long lessonId, Long activityId, MultipartFile fileElements) + throws IOException { + + int totalUsersSkipped = 0; + int totalUsersAdded = 0; + + // Lesson grouping case so clean out and reuse any existing groups + Activity activity = monitoringService.getActivityById(activityId); + Grouping grouping = activity.isChosenBranchingActivity() ? activity.getGrouping() + : ((GroupingActivity) activity).getCreateGrouping(); + + Set existingGroupNames = new HashSet<>(); + // check for any users starting to use the groups since the page was loaded. + for (Group group : grouping.getGroups()) { + existingGroupNames.add(group.getGroupName()); + if (!group.mayBeDeleted()) { + String error = messageService.getMessage("error.groups.upload.locked"); + return createResponseJSON(true, error, true, grouping.getGroupingId(), 0, 0); + } + } + + Map> groups = new HashMap<>(); + totalUsersSkipped = parseGroupSpreadsheet(fileElements, grouping.getGroupingId(), groups); + + // if branching must use the already specified groups or cannot match to a branch! + if (activity.isChosenBranchingActivity()) { + for (Map.Entry> groupEntry : groups.entrySet()) { + if (!existingGroupNames.contains(groupEntry.getKey())) { + StringBuilder groupNamesStrBlder = new StringBuilder(); + for (String name : existingGroupNames) { + groupNamesStrBlder.append("'").append(name).append("' "); + } + String error = messageService.getMessage("error.branching.upload.must.use.existing.groups", + new String[] { groupNamesStrBlder.toString() }); + return createResponseJSON(true, error.toString(), false, grouping.getGroupingId(), 0, 0); + } + } + } + + //check all users exist and are learners in the specified lesson + for (Map.Entry> groupEntry : groups.entrySet()) { + Set logins = groupEntry.getValue(); + + Iterator iter = logins.iterator(); + while (iter.hasNext()) { + String login = iter.next(); + User learner = userManagementService.getUserByLogin(login); + if (learner == null) { + log.warn("Unable to add learner " + login + " to lesson grouping as learner cannot be found."); + totalUsersSkipped++; + iter.remove(); + + } else if (!securityService.isLessonLearner(lessonId, learner.getUserId(), "be added to grouping", + true)) { + totalUsersSkipped++; + iter.remove(); + } + } + } + + // remove all the existing users from their groups + lessonService.removeAllLearnersFromGrouping(grouping); + + // Now put in the new users groupings + for (Map.Entry> groupEntry : groups.entrySet()) { + int added = monitoringService.addUsersToGroupByLogins(activityId, groupEntry.getKey(), + groupEntry.getValue()); + totalUsersAdded += added; + totalUsersSkipped += groupEntry.getValue().size() - added; + } + + return createResponseJSON(false, null, true, grouping.getGroupingId(), totalUsersAdded, totalUsersSkipped); + } + + private ObjectNode createResponseJSON(boolean isError, String errorMessage, boolean reload, Long groupingId, + int totalUsersAdded, int totalUsersSkipped) { + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + if (isError) { + responseJSON.put("result", "FAIL"); + responseJSON.put("reload", reload); + responseJSON.put("error", errorMessage); + } else { + responseJSON.put("result", "OK"); + responseJSON.put("added", totalUsersAdded); + responseJSON.put("skipped", totalUsersSkipped); + } + responseJSON.put("groupingId", groupingId); + return responseJSON; + } + + /* XLS Version Parse */ + private String parseStringCell(HSSFCell cell) { + if (cell != null) { + cell.setCellType(CellType.STRING); + if (cell.getStringCellValue() != null) { + return cell.getStringCellValue().trim(); + } + } + return null; + } + + public int parseGroupSpreadsheet(MultipartFile fileItem, Long groupingID, Map> groups) + throws IOException { + POIFSFileSystem fs = new POIFSFileSystem(fileItem.getInputStream()); + HSSFWorkbook wb = new HSSFWorkbook(fs); + HSSFSheet sheet = wb.getSheetAt(0); + + int startRow = sheet.getFirstRowNum(); + int endRow = sheet.getLastRowNum(); + int skipped = 0; + Set allUsers = new HashSet<>(); + + for (int i = startRow + 1; i < (endRow + 1); i++) { + HSSFRow row = sheet.getRow(i); + String login = parseStringCell(row.getCell(0)); + + if (StringUtils.isBlank(login)) { + skipped++; + GroupingUploadAJAXController.log.warn( + "Unable to add learner for group related to grouping " + groupingID + " as login is missing."); + continue; + } + boolean alreadyExists = !allUsers.add(login); + if (alreadyExists) { + skipped++; + GroupingUploadAJAXController.log.warn( + "Skipping duplicate row for learner " + login + " for group related to grouping " + groupingID); + continue; + } + String groupName = row.getLastCellNum() > 3 ? parseStringCell(row.getCell(3)) : null; + if (groupName == null || groupName.length() == 0) { + skipped++; + GroupingUploadAJAXController.log.warn("Unable to add learner " + login + + " for group in related to grouping " + groupingID + " as group name is missing."); + continue; + } + Set users = groups.get(groupName); + if (users == null) { + users = new HashSet<>(); + groups.put(groupName, users); + } + users.add(login); + } + return skipped; + } + + final Comparator ORG_GROUP_COMPARATOR = new Comparator<>() { + @Override + public int compare(OrganisationGroup o1, OrganisationGroup o2) { + String grp1Name = o1 != null ? o1.getName() : ""; + String grp2Name = o2 != null ? o2.getName() : ""; + + AlphanumComparator comparator = new AlphanumComparator(); + return comparator.compare(grp1Name, grp2Name); + } + }; + + private UserDTO getUserDTO() { + HttpSession ss = SessionManager.getSession(); + return (UserDTO) ss.getAttribute(AttributeNames.USER); + } + +}