Index: lams_central/src/java/org/lamsfoundation/lams/webservice/SPEnrolmentServlet.java =================================================================== diff -u -r286ef7c98baa533571da3d6af47ebc0aff0742f4 -r40b5ff3de75f51721a10181b830b5008f2d9f54c --- lams_central/src/java/org/lamsfoundation/lams/webservice/SPEnrolmentServlet.java (.../SPEnrolmentServlet.java) (revision 286ef7c98baa533571da3d6af47ebc0aff0742f4) +++ lams_central/src/java/org/lamsfoundation/lams/webservice/SPEnrolmentServlet.java (.../SPEnrolmentServlet.java) (revision 40b5ff3de75f51721a10181b830b5008f2d9f54c) @@ -61,9 +61,11 @@ import org.lamsfoundation.lams.logevent.LogEvent; import org.lamsfoundation.lams.logevent.service.ILogEventService; import org.lamsfoundation.lams.usermanagement.Organisation; +import org.lamsfoundation.lams.usermanagement.OrganisationType; import org.lamsfoundation.lams.usermanagement.Role; import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.UserOrganisation; +import org.lamsfoundation.lams.usermanagement.UserOrganisationRole; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; import org.lamsfoundation.lams.util.Configuration; import org.lamsfoundation.lams.util.ConfigurationKeys; @@ -93,7 +95,7 @@ } private static List getAllRoles() { - return Arrays.asList(LEARNER.getRole(), STAFF.getRole(), MANAGER.getRole()); + return Arrays.asList(MANAGER.getRole(), LEARNER.getRole(), STAFF.getRole()); } }; @@ -133,7 +135,7 @@ } // split each line into list of trimmed pieces - Map>> linesByMode = Files.readAllLines(fileInput).parallelStream().unordered() + List> allLines = Files.readAllLines(fileInput).parallelStream().unordered() .map(line -> Arrays.stream(line.split(DELIMITER)).map(elem -> elem.trim()) .collect(Collectors.toList())) // filter out malformed rows @@ -149,76 +151,128 @@ } // throw an exception when a row is malformed rather than silently discard it throw new RuntimeException("Malformed row: " + String.join(DELIMITER, row)); - }).collect(Collectors.groupingByConcurrent(row -> row.get(6))); + }).collect(Collectors.toList()); - if (linesByMode.isEmpty()) { + if (allLines.isEmpty()) { throw new ServletException("File is empty"); } + // map of user login -> user ID + Map userIDs = new HashMap<>(); + + // find sysadmin as he/she will be the creator of organisations + Organisation rootOrganisation = userManagementService.getRootOrganisation(); + Integer creatorId = rootOrganisation.getCreatedBy().getUserId(); + + // map of user login -> email, first name + // for learner email is login, for staff it is a different ID in email format + ConcurrentMap allParsedUserMapping = allLines.parallelStream().unordered() + .collect(Collectors.toConcurrentMap( + elem -> elem.get(6).equals(Mode.STAFF.getRole()) + || elem.get(6).equals(Mode.MANAGER.getRole()) ? elem.get(3) : elem.get(5), + elem -> new String[] { elem.get(5), elem.get(4) }, (elem1, elem2) -> elem1)); + logger.info("Found " + allParsedUserMapping.size() + " users in the file"); + + Integer extServerSid = extServer.getSid(); + // load all users from DB which are present in the output file + // map of user login -> user + Map allExistingParsedUsers = userManagementService + .findByPropertyValues(User.class, "login", allParsedUserMapping.keySet()).parallelStream() + .collect(Collectors.toConcurrentMap(User::getLogin, u -> u)); + + logger.info(allExistingParsedUsers.size() + " users already exist"); + + // load all ext users from DB which are present in the output file + // map of user login -> extUser + Map allExistingParsedExtUsers = userManagementService + .findByPropertyValues(ExtUserUseridMap.class, "extUsername", allExistingParsedUsers.keySet()) + .parallelStream().filter(e -> e.getExtServer().getSid().equals(extServerSid)) + .collect(Collectors.toConcurrentMap(ExtUserUseridMap::getExtUsername, e -> e)); + + // create users and ext users + Set allParsedUsers = createUsers(extServer, creatorId, allParsedUserMapping, userIDs, + allExistingParsedUsers, allExistingParsedExtUsers); + + // map of course code (ID) -> course name + // for all organisations present in the output file + ConcurrentMap allParsedCourseMapping = allLines.parallelStream().unordered().collect( + Collectors.toConcurrentMap(elem -> elem.get(0), elem -> elem.get(1), (elem1, elem2) -> elem1)); + + logger.info("Found " + allParsedCourseMapping.size() + " courses in the file"); + + // load all organisations from DB which are present in the output file, by code + // map of code -> organisation + Map allExistingParsedCourses = userManagementService + .findByPropertyValues(Organisation.class, "code", allParsedCourseMapping.keySet()) + .parallelStream().collect(Collectors.toConcurrentMap(Organisation::getCode, o -> o)); + + logger.info(allExistingParsedCourses.size() + " courses already exist"); + + // prepare codes of all suborganisations disregarding which organisation is their parent + Set allParsedSubcourseMapping = allLines.stream() + .collect(Collectors.mapping(elem -> elem.get(2), Collectors.toSet())); + + logger.info("Found " + allParsedSubcourseMapping.size() + " subcourses in the file"); + + // load all suborganisations from DB which are present in the output file + List allExistingParsedSubcourses = userManagementService + .findByPropertyValues(Organisation.class, "code", allParsedSubcourseMapping); + + logger.info(allParsedSubcourseMapping.size() + " subcourses already exist"); + + // map of course ID -> subcourse code -> subcourse + Map> allExistingParsedCoursesAndSubcourses = allExistingParsedSubcourses + .parallelStream().filter(o -> o.getParentOrganisation() != null) + .collect(Collectors.groupingByConcurrent(o -> o.getParentOrganisation().getOrganisationId(), + Collectors.toConcurrentMap(Organisation::getCode, o -> o))); + + // load all ext courses and subcourses from DB which are present in the output file + Map allExistingParsedExtCourses = userManagementService + .findByPropertyValues(ExtCourseClassMap.class, "classid", Stream + // merge IDs of organisations and suborganisations + .concat(allExistingParsedCourses.values().stream(), + allExistingParsedSubcourses.stream()) + .map(Organisation::getOrganisationId).collect(Collectors.toSet())) + .parallelStream().filter(e -> e.getExtServer().getSid().equals(extServerSid)) + .collect(Collectors.toConcurrentMap(e -> e.getOrganisation().getOrganisationId(), e -> e)); + + Set allExistingUsersFromParsedCourses = Stream + .concat(allExistingParsedCourses.values().stream(), allExistingParsedSubcourses.stream()) + .flatMap(o -> o.getUserOrganisations().stream()) + .collect(Collectors.mapping(UserOrganisation::getUser, Collectors.toSet())); + + // map lines into corresponding roles + Map>> linesByMode = allLines.stream() + .collect(Collectors.groupingByConcurrent(row -> row.get(6))); + for (String role : Mode.getAllRoles()) { - List> lines = linesByMode.get(role); - if (lines == null) { - continue; - } - logger.info("Processing \"" + role + "\" role"); + List> lines = linesByMode.get(role); // it is easier to detect whether we process managers or staff or learners just once // than for each user - they do not come together anyway final Mode mode = role.equals(Mode.STAFF.getRole()) ? Mode.STAFF : (role.equals(Mode.MANAGER.getRole()) ? Mode.MANAGER : Mode.LEARNER); - // map of course code (ID) -> course name - ConcurrentMap courses = lines.parallelStream().unordered().collect(Collectors - .toConcurrentMap(elem -> elem.get(0), elem -> elem.get(1), (elem1, elem2) -> elem1)); + if (lines == null) { + // always process manager role, even if there are no managers + if (mode == Mode.MANAGER) { + lines = List.of(); + } else { + continue; + } + } - // map of user login -> email, first name - // for learner email is login, for staff it is a different ID in email format - ConcurrentMap users = lines.parallelStream().unordered() - .collect(Collectors.toConcurrentMap( - elem -> mode == Mode.STAFF || mode == Mode.MANAGER ? elem.get(3) : elem.get(5), - elem -> new String[] { elem.get(5), elem.get(4) }, (elem1, elem2) -> elem1)); - - // map of user login -> user ID - Map userIDs = new HashMap<>(); - - // find sysadmin as he/she will be the creator of organisations - Organisation rootOrganisation = userManagementService.getRootOrganisation(); - Integer creatorId = rootOrganisation.getCreatedBy().getUserId(); - - Integer extServerSid = extServer.getSid(); - // load all users from DB which are present in the output file - // map of user login -> user - Map allExistingUsers = userManagementService - .findByPropertyValues(User.class, "login", users.keySet()).parallelStream() - .collect(Collectors.toConcurrentMap(User::getLogin, u -> u)); - // load all ext users from DB which are present in the output file - // map of user login -> extUser - Map allExistingExtUsers = userManagementService - .findByPropertyValues(ExtUserUseridMap.class, "extUsername", allExistingUsers.keySet()) - .parallelStream().filter(e -> e.getExtServer().getSid().equals(extServerSid)) - .collect(Collectors.toConcurrentMap(ExtUserUseridMap::getExtUsername, e -> e)); - // CREATE USERS and ext users - Set allUsersParsed = createUsers(extServer, creatorId, users, userIDs, allExistingUsers, - allExistingExtUsers); - // map of user login -> course ID -> role IDs - // for all users which are present in the output file - Map>> allExistingRoles = userManagementService - .findByPropertyValues(UserOrganisation.class, "user.userId", - allExistingUsers.values().parallelStream() - .collect(Collectors.mapping(User::getUserId, Collectors.toSet()))) - .stream() + // for all organisations which are present in the output file + Map>> allExistingRoles = Stream + .concat(allExistingParsedCourses.values().stream(), allExistingParsedSubcourses.stream()) + .flatMap(o -> o.getUserOrganisations().stream()) .collect(Collectors.groupingBy(uo -> uo.getUser().getLogin(), Collectors.toMap( userOrganisation -> userOrganisation.getOrganisation().getOrganisationId(), userOrganisation -> userOrganisation.getUserOrganisationRoles().stream() .map(userOrganisationRole -> userOrganisationRole.getRole().getRoleId()) .collect(Collectors.toSet())))); - // load all organisations from DB which are present in the output file, by code - // map of code -> organisation - Map allExistingOrganisations = userManagementService - .findByPropertyValues(Organisation.class, "code", courses.keySet()).parallelStream() - .collect(Collectors.toConcurrentMap(Organisation::getCode, o -> o)); // When setting group managers, just process courses, not subcourses and lessons if (mode == Mode.MANAGER) { @@ -228,19 +282,21 @@ Collectors.mapping(elem -> elem.get(3), Collectors.toList()))); AtomicInteger mappingsProcessed = new AtomicInteger(); - logger.info("Processing courses and assigments"); - for (String courseCode : courses.keySet()) { - Organisation course = allExistingOrganisations.get(courseCode); + logger.info("Processing manager courses and assigments"); - assignManagers(course, creatorId, mappings, allUsersParsed, userIDs, allExistingRoles, - allExistingUsers, mappingsProcessed); + for (Entry courseEntry : allParsedCourseMapping.entrySet()) { + Organisation course = getCourse(courseEntry.getKey(), courseEntry.getValue(), extServer, + creatorId, rootOrganisation, allExistingParsedCourses, allExistingParsedExtCourses); + assignManagers(course, creatorId, mappings, allParsedUsers, userIDs, allExistingRoles, + allExistingParsedUsers, mappingsProcessed); + logger.info("Processed " + mappingsProcessed.get() + " entries"); } - logger.info("Processing \"" + role + "\" role finished"); + logger.info("Processing manager role finished"); - // END OF GROUP MANAGER PROCESSING! - return; + // END OF GROUP MANAGER PROCESSING + continue; } // START OF LEARNER / STAFF PROCESSING @@ -252,76 +308,42 @@ Collectors.mapping(elem -> mode == Mode.STAFF ? elem.get(3) : elem.get(5), Collectors.toList())))); - // prepare codes of all suborganisations disregarding which organisation is their parent - Set allExistingSubOrganisationCodes = mappings.values().stream() - .flatMap(e -> e.keySet().stream()).collect(Collectors.toSet()); - - // load all suborganisations from DB which are present in the output file - List allExistingSubcourseObjects = userManagementService - .findByPropertyValues(Organisation.class, "code", allExistingSubOrganisationCodes); - - // map of course code -> subcourse code -> subcourse - Map> allExistingSubcourses = allExistingSubcourseObjects - .parallelStream().filter(o -> o.getParentOrganisation() != null) - .collect(Collectors.groupingByConcurrent(o -> o.getParentOrganisation().getOrganisationId(), - Collectors.toConcurrentMap(Organisation::getCode, o -> o))); - - // load all ext courses and subcourses from DB which are present in the output file - Map allExistingExtCourses = userManagementService - .findByPropertyValues(ExtCourseClassMap.class, "classid", Stream - // merge IDs of organisations and suborganisations - .concat(allExistingOrganisations.values().stream() - .map(Organisation::getOrganisationId), - allExistingSubcourseObjects.stream().map(Organisation::getOrganisationId)) - .collect(Collectors.toSet())) - .parallelStream().filter(e -> e.getExtServer().getSid().equals(extServerSid)) - .collect(Collectors.toConcurrentMap(e -> e.getOrganisation().getOrganisationId(), e -> e)); - - Set allUsersinCourses = new HashSet<>(); // go through each course AtomicInteger mappingsProcessed = new AtomicInteger(); logger.info("Processing courses and assigments"); - for (Entry courseEntry : courses.entrySet()) { + + for (Entry courseEntry : allParsedCourseMapping.entrySet()) { String courseCode = courseEntry.getKey(); // create or get existing course Organisation course = getCourse(courseCode, courseEntry.getValue(), extServer, creatorId, - rootOrganisation, allExistingOrganisations, allExistingExtCourses); - // collect learners from all subcourses of this course - Set allUsersInSubcourses = assignLearnersOrStaff(course, mappings, extServer, creatorId, - allUsersParsed, userIDs, allExistingRoles, allExistingSubcourses, allExistingExtCourses, - allExistingUsers, mode == Mode.STAFF, mappingsProcessed); - allUsersinCourses.addAll(allUsersInSubcourses); + rootOrganisation, allExistingParsedCourses, allExistingParsedExtCourses); + // assign learners and staff to subcourses of this course + assignLearnersOrStaff(course, mappings, extServer, creatorId, allParsedUsers, userIDs, + allExistingRoles, allExistingParsedCoursesAndSubcourses, allExistingParsedExtCourses, + allExistingParsedUsers, mode == Mode.STAFF, mappingsProcessed); logger.info("Processed " + mappingsProcessed.get() + " entries"); } - logger.info("Disabling users"); - // users who are part of courses but are not in the file anymore are eligible for disabling - allUsersinCourses.removeAll(allUsersParsed); - for (User user : allUsersinCourses) { - // make a flat set of roles from all subcourses - Set roles = userManagementService.getRolesForUser(user.getUserId()).values().stream() - .collect(HashSet::new, Set::addAll, Set::addAll); - if (mode == Mode.STAFF) { - // check if the user is learner in any course - roles.remove(Role.ROLE_MONITOR); - roles.remove(Role.ROLE_AUTHOR); - } else { - // check if the user is staff in any course - roles.remove(Role.ROLE_LEARNER); - } - if (roles.isEmpty()) { - // he is only a learner or this is staff mode, so disable - userManagementService.disableUser(user.getUserId()); + logger.info("Processing " + role + " role finished"); + } - String message = "User \"" + user.getLogin() + "\" disabled"; - logger.info(message); - logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, - "SPEnrolment: " + message); - } + logger.info("Disabling users"); + // users who are part of courses but are not in the file anymore are eligible for disabling + allExistingUsersFromParsedCourses.removeAll(allParsedUsers); + for (User user : allExistingUsersFromParsedCourses) { + boolean hasAnyRoles = userManagementService.hasUserAnyRoles(user.getUserId()); + if (!hasAnyRoles) { + // he is only a learner or this is staff mode, so disable + userManagementService.disableUser(user.getUserId()); + + String message = "User \"" + user.getLogin() + "\" disabled"; + logger.info(message); + logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, + "SPEnrolment: " + message); } - logger.info("Processing \"" + role + "\" role finished"); } + logger.info("SP enrolments provisioning completed successfully"); } catch (Exception e) { logger.error("Error while provisioning SP enrolments", e); @@ -398,9 +420,9 @@ } private Organisation getCourse(String courseCode, String courseName, ExtServer extServer, Integer creatorId, - Organisation rootOrganisation, Map allExistingOrganisations, - Map allExistingExtCourses) throws UserInfoValidationException { - Organisation course = allExistingOrganisations.get(courseCode); + Organisation rootOrganisation, Map allExistingParsedCourses, + Map allExistingParsedExtCourses) throws UserInfoValidationException { + Organisation course = allExistingParsedCourses.get(courseCode); // create course if (course == null) { String name = courseName; @@ -410,8 +432,8 @@ course.setCode(courseCode); userManagementService.save(course); - allExistingOrganisations.put(courseCode, course); - allExistingExtCourses.put(extOrgMap.getOrganisation().getOrganisationId(), extOrgMap); + allExistingParsedCourses.put(courseCode, course); + allExistingParsedExtCourses.put(extOrgMap.getOrganisation().getOrganisationId(), extOrgMap); String message = "Course created with code \"" + courseCode + "\" and name \"" + name + "\" and ID " + course.getOrganisationId(); @@ -421,15 +443,15 @@ } else { String name = course.getName(); - ExtCourseClassMap extOrgMap = allExistingExtCourses.get(course.getOrganisationId()); + ExtCourseClassMap extOrgMap = allExistingParsedExtCourses.get(course.getOrganisationId()); if (extOrgMap == null) { extOrgMap = new ExtCourseClassMap(); extOrgMap.setCourseid(name); extOrgMap.setExtServer(extServer); extOrgMap.setOrganisation(course); userManagementService.save(extOrgMap); - allExistingExtCourses.put(course.getOrganisationId(), extOrgMap); + allExistingParsedExtCourses.put(course.getOrganisationId(), extOrgMap); String message = "External course created for existing course with code \"" + courseCode + "\" and name \"" + name + "\" and ID " + course.getOrganisationId(); @@ -442,162 +464,235 @@ } @SuppressWarnings("unchecked") - private Set assignLearnersOrStaff(Organisation course, Map>> mappings, + private void assignLearnersOrStaff(Organisation course, Map>> mappings, ExtServer extServer, Integer creatorId, Set allUsersParsed, Map userIDs, Map>> allExistingRoles, - Map> allExistingSubcourses, - Map allExistingExtCourses, Map allExistingUsers, + Map> allExistingParsedCoursesAndSubcourses, + Map allExistingParsedExtCourses, Map allExistingParsedUsers, boolean isStaffMode, AtomicInteger mappingsProcessed) throws UserInfoValidationException { - Set allUsersInSubcourses = new HashSet<>(); String courseCode = course.getCode(); Integer courseId = course.getOrganisationId(); + ConcurrentMap existingSubcourses = allExistingParsedCoursesAndSubcourses.get(courseId); + Map nonProcessedSubcourses = existingSubcourses == null ? new HashMap<>() + : new HashMap<>(existingSubcourses); // go through each subcourse - for (Entry> subcourseEntry : mappings.get(courseCode).entrySet()) { - String subcourseCode = subcourseEntry.getKey(); - ConcurrentMap existingSubcourses = allExistingSubcourses.get(courseId); - Organisation subcourse = existingSubcourses == null ? null : existingSubcourses.get(subcourseCode); + Map> subcourseMappings = mappings.get(courseCode); + if (subcourseMappings != null) { + for (Entry> subcourseEntry : subcourseMappings.entrySet()) { + String subcourseCode = subcourseEntry.getKey(); + nonProcessedSubcourses.remove(subcourseCode); - // create subcourse - if (subcourse == null) { - ExtCourseClassMap extSubOrgMap = integrationService.createExtCourseClassMap(extServer, creatorId, - subcourseCode, subcourseCode, course.getOrganisationId().toString(), false); - subcourse = extSubOrgMap.getOrganisation(); - subcourse.setCode(subcourseCode); - userManagementService.save(subcourse); + Organisation subcourse = existingSubcourses == null ? null : existingSubcourses.get(subcourseCode); + // create subcourse + if (subcourse == null) { + ExtCourseClassMap extSubOrgMap = integrationService.createExtCourseClassMap(extServer, creatorId, + subcourseCode, subcourseCode, course.getOrganisationId().toString(), false); + subcourse = extSubOrgMap.getOrganisation(); + subcourse.setCode(subcourseCode); + userManagementService.save(subcourse); - if (existingSubcourses == null) { - existingSubcourses = new ConcurrentHashMap<>(); - allExistingSubcourses.put(courseId, existingSubcourses); - } - existingSubcourses.put(subcourse.getCode(), subcourse); - allExistingExtCourses.put(extSubOrgMap.getOrganisation().getOrganisationId(), extSubOrgMap); + if (existingSubcourses == null) { + existingSubcourses = new ConcurrentHashMap<>(); + allExistingParsedCoursesAndSubcourses.put(courseId, existingSubcourses); + } + existingSubcourses.put(subcourse.getCode(), subcourse); + allExistingParsedExtCourses.put(extSubOrgMap.getOrganisation().getOrganisationId(), extSubOrgMap); - String message = "Subcourse created with code and name \"" + courseCode + "\" and ID " - + subcourse.getOrganisationId(); - logger.info(message); - logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, - "SPEnrolment: " + message); - } else { - String name = subcourse.getName(); - - ExtCourseClassMap extOrgMap = allExistingExtCourses.get(subcourse.getOrganisationId()); - if (extOrgMap == null) { - extOrgMap = new ExtCourseClassMap(); - extOrgMap.setCourseid(name); - extOrgMap.setExtServer(extServer); - extOrgMap.setOrganisation(subcourse); - userManagementService.save(extOrgMap); - - String message = "External subcourse created for existing subcourse with code \"" + subcourseCode - + "\" and name \"" + name + "\" and ID " + subcourse.getOrganisationId(); + String message = "Subcourse created with code and name \"" + courseCode + "\" and ID " + + subcourse.getOrganisationId(); logger.info(message); logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, "SPEnrolment: " + message); - } - } + } else { + String name = subcourse.getName(); - Integer subcourseId = subcourse.getOrganisationId(); + ExtCourseClassMap extOrgMap = allExistingParsedExtCourses.get(subcourse.getOrganisationId()); + if (extOrgMap == null) { + extOrgMap = new ExtCourseClassMap(); + extOrgMap.setCourseid(name); + extOrgMap.setExtServer(extServer); + extOrgMap.setOrganisation(subcourse); + userManagementService.save(extOrgMap); - // get existing learners/staff members for given subcourse - Collection subcourseUsers = userManagementService.getUsersFromOrganisationByRole(subcourseId, - isStaffMode ? Role.MONITOR : Role.LEARNER, true); - // add users to set containing all users in any subcourse which is processed - allUsersInSubcourses.addAll(subcourseUsers); - - // go through each user - for (String login : subcourseEntry.getValue()) { - mappingsProcessed.incrementAndGet(); - - // check if the user is already a learner/staff member in the subcourse - // if so, there is nothing to do - boolean userAlreadyAssigned = false; - Integer userId = userIDs.get(login); - Iterator subcourseUserIterator = subcourseUsers.iterator(); - while (subcourseUserIterator.hasNext()) { - if (userId.equals(subcourseUserIterator.next().getUserId())) { - // IMPORTANT: if we found a matching existing learner/staff member, we get remove him from this collection - // so after this loop he does not get removed from subcourses - subcourseUserIterator.remove(); - userAlreadyAssigned = true; - break; + String message = "External subcourse created for existing subcourse with code \"" + + subcourseCode + "\" and name \"" + name + "\" and ID " + + subcourse.getOrganisationId(); + logger.info(message); + logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, + "SPEnrolment: " + message); } } - if (userAlreadyAssigned) { - continue; - } - // the user is not a learner/staff member yet, so assign him the role and add him to lessons - Map> existingSubcoursesRoles = allExistingRoles.get(login); - Set existingSubcourseRoles = existingSubcoursesRoles == null ? null - : allExistingRoles.get(login).get(subcourseId); - if (existingSubcourseRoles == null) { - existingSubcourseRoles = new HashSet<>(); - } + Integer subcourseId = subcourse.getOrganisationId(); + + // get existing learners/staff members for given subcourse + Collection subcourseMonitorsOrLearners = userManagementService + .getUsersFromOrganisationByRole(subcourseId, isStaffMode ? Role.MONITOR : Role.LEARNER, true); + Collection subcourseUsers = new HashSet<>(subcourseMonitorsOrLearners); if (isStaffMode) { - existingSubcourseRoles.add(Role.ROLE_AUTHOR); - existingSubcourseRoles.add(Role.ROLE_MONITOR); - } else { - existingSubcourseRoles.add(Role.ROLE_LEARNER); + // make sure that staff has both monitor and author roles in subcourse, + // even if they are course managers in the parent organisations + // and they already have a monitor role in subcourse + Collection authors = userManagementService.getUsersFromOrganisationByRole(subcourseId, + Role.AUTHOR, true); + subcourseUsers.retainAll(authors); } - User user = allExistingUsers.get(login); - userManagementService.setRolesForUserOrganisation(user, subcourse, - existingSubcourseRoles.stream().map(String::valueOf).collect(Collectors.toList()), false); - for (Lesson lesson : lessonService.getLessonsByGroup(subcourseId)) { - if (isStaffMode) { - lessonService.addStaffMember(lesson.getLessonId(), userId); - } else { - lessonService.addLearner(lesson.getLessonId(), userId); - } - } + // go through each user + for (String login : subcourseEntry.getValue()) { - String message = (isStaffMode ? "Teacher" : "Learner") + " \"" + login + "\" added to subcourse " - + subcourseId + " and its lessons"; - logger.info(message); - logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, - "SPEnrolment: " + message); - } + logger.info("Processing \"" + login + "\""); - // user is a learner/staff member, but he should not; remove him role from subcourse and lessons - for (User user : subcourseUsers) { - Map> existingSubcoursesRoles = allExistingRoles.get(user.getLogin()); - Set existingSubcourseRoles = existingSubcoursesRoles == null ? null - : existingSubcoursesRoles.get(subcourseId); - if (existingSubcourseRoles != null) { + mappingsProcessed.incrementAndGet(); + + // check if the user is already a learner/staff member in the subcourse + // if so, there is nothing to do + boolean userAlreadyAssigned = false; + Integer userId = userIDs.get(login); + Iterator subcourseUserIterator = subcourseUsers.iterator(); + while (subcourseUserIterator.hasNext()) { + User user = subcourseUserIterator.next(); + if (userId.equals(user.getUserId())) { + // IMPORTANT: if we found a matching existing learner/staff member, we get remove him from this collection + // so after this loop he does not get removed from subcourses + subcourseUserIterator.remove(); + subcourseMonitorsOrLearners.remove(user); + userAlreadyAssigned = true; + break; + } + } + if (userAlreadyAssigned) { + continue; + } + + // the user is not a learner/staff member yet, so assign him the role and add him to lessons + Map> existingSubcoursesRoles = allExistingRoles.get(login); + Set existingSubcourseRoles = existingSubcoursesRoles == null ? null + : allExistingRoles.get(login).get(subcourseId); + if (existingSubcourseRoles == null) { + existingSubcourseRoles = new HashSet<>(); + } if (isStaffMode) { - existingSubcourseRoles.remove(Role.ROLE_AUTHOR); - existingSubcourseRoles.remove(Role.ROLE_MONITOR); + existingSubcourseRoles.add(Role.ROLE_AUTHOR); + existingSubcourseRoles.add(Role.ROLE_MONITOR); } else { - existingSubcourseRoles.remove(Role.ROLE_LEARNER); + existingSubcourseRoles.add(Role.ROLE_LEARNER); } + User user = allExistingParsedUsers.get(login); userManagementService.setRolesForUserOrganisation(user, subcourse, existingSubcourseRoles.stream().map(String::valueOf).collect(Collectors.toList()), false); for (Lesson lesson : lessonService.getLessonsByGroup(subcourseId)) { if (isStaffMode) { - lessonService.removeStaffMember(lesson.getLessonId(), user.getUserId()); + lessonService.addStaffMember(lesson.getLessonId(), userId); } else { - lessonService.removeLearner(lesson.getLessonId(), user.getUserId()); + lessonService.addLearner(lesson.getLessonId(), userId); } + } + String message = (isStaffMode ? "Teacher" : "Learner") + " \"" + login + "\" added to subcourse " + + subcourseId + " and its lessons"; + logger.info(message); + logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, + "SPEnrolment: " + message); + } + + // user is a learner/staff member, but he should not; remove him role from subcourse, course and lessons + for (User user : subcourseMonitorsOrLearners) { + + boolean removedFromSubcourse = removeFromCourse(subcourse, user, + isStaffMode ? Mode.STAFF : Mode.LEARNER, allExistingRoles); + if (removedFromSubcourse) { + String message = (isStaffMode ? "Teacher" : "Learner") + " \"" + user.getLogin() + + "\" removed from subcourse " + subcourseId + " and its lessons"; + logger.info(message); + logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, + "SPEnrolment: " + message); } + } + } + } + for (Organisation subcourse : nonProcessedSubcourses.values()) { + // get all learners/staff members for given subcourse and remove them + Collection subcourseMonitorsOrLearners = userManagementService.getUsersFromOrganisationByRole( + subcourse.getOrganisationId(), isStaffMode ? Role.MONITOR : Role.LEARNER, true); + for (User user : subcourseMonitorsOrLearners) { + + boolean removedFromSubcourse = removeFromCourse(subcourse, user, + isStaffMode ? Mode.STAFF : Mode.LEARNER, allExistingRoles); + if (removedFromSubcourse) { String message = (isStaffMode ? "Teacher" : "Learner") + " \"" + user.getLogin() - + "\" removed from subcourse " + subcourseId + " and its lessons"; + + "\" removed from subcourse " + subcourse.getOrganisationId() + " and its lessons"; logger.info(message); logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, "SPEnrolment: " + message); } } } + } - return allUsersInSubcourses; + private boolean removeFromCourse(Organisation course, User user, Mode mode, + Map>> allExistingRoles) { + // no existing roles and the user should be removed - nothing to do + Map> existingCoursesRoles = allExistingRoles.get(user.getLogin()); + Set existingCourseRoles = existingCoursesRoles == null ? null + : existingCoursesRoles.get(course.getOrganisationId()); + if (existingCourseRoles == null || existingCourseRoles.isEmpty()) { + return false; + } + + Organisation parentCourse = course.getOrganisationType().getOrganisationTypeId() + .equals(OrganisationType.CLASS_TYPE) ? course.getParentOrganisation() : null; + + if (mode == Mode.MANAGER) { + existingCourseRoles.remove(Role.ROLE_GROUP_MANAGER); + } else if (mode == Mode.STAFF) { + // managers are always monitors in subcourses + if (parentCourse != null + && userManagementService.hasRoleInOrganisation(user, Role.ROLE_GROUP_MANAGER, parentCourse)) { + return false; + } + + existingCourseRoles.remove(Role.ROLE_MONITOR); + existingCourseRoles.remove(Role.ROLE_AUTHOR); + } else { + existingCourseRoles.remove(Role.ROLE_LEARNER); + } + + userManagementService.setRolesForUserOrganisation(user, course, + existingCourseRoles.stream().map(String::valueOf).collect(Collectors.toList()), false); + + // add learners and staff to lessons + if (mode != Mode.MANAGER) { + for (Lesson lesson : lessonService.getLessonsByGroup(course.getOrganisationId())) { + if (mode == Mode.STAFF) { + lessonService.removeStaffMember(lesson.getLessonId(), user.getUserId()); + } else { + lessonService.removeLearner(lesson.getLessonId(), user.getUserId()); + } + } + + // if learner or staff is not in any subcourse, remove him also from parent course + if (course.getOrganisationType().getOrganisationTypeId().equals(OrganisationType.CLASS_TYPE)) { + for (Organisation subcourse : parentCourse.getChildOrganisations()) { + List rolesInSubcourse = userManagementService + .getUserOrganisationRoles(subcourse.getOrganisationId(), user.getLogin()); + if (!rolesInSubcourse.isEmpty()) { + return true; + } + } + removeFromCourse(parentCourse, user, mode, allExistingRoles); + } + } + + return true; } @SuppressWarnings("unchecked") private void assignManagers(Organisation course, Integer creatorId, Map> mappings, Set allUsersParsed, Map userIDs, - Map>> allExistingRoles, Map allExistingUsers, + Map>> allExistingRoles, Map allExistingParsedUsers, AtomicInteger mappingsProcessed) throws UserInfoValidationException { String courseCode = course.getCode(); Integer courseId = course.getOrganisationId(); @@ -607,54 +702,53 @@ Role.GROUP_MANAGER, true); // go through each user - for (String login : mappings.get(courseCode)) { - mappingsProcessed.incrementAndGet(); + List courseMappings = mappings.get(courseCode); + if (courseMappings != null) { + for (String login : courseMappings) { + logger.info("Processing manager \"" + login + "\""); + mappingsProcessed.incrementAndGet(); - // check if the user is already a manager is the course - // if so, there is nothing to do - boolean userAlreadyAssigned = false; - Integer userId = userIDs.get(login); - Iterator courseUserIterator = courseUsers.iterator(); - while (courseUserIterator.hasNext()) { - if (userId.equals(courseUserIterator.next().getUserId())) { - courseUserIterator.remove(); - userAlreadyAssigned = true; - break; + // check if the user is already a manager is the course + // if so, there is nothing to do + boolean userAlreadyAssigned = false; + Integer userId = userIDs.get(login); + Iterator courseUserIterator = courseUsers.iterator(); + while (courseUserIterator.hasNext()) { + if (userId.equals(courseUserIterator.next().getUserId())) { + courseUserIterator.remove(); + userAlreadyAssigned = true; + break; + } } - } - if (userAlreadyAssigned) { - continue; - } - // the user is not a manager yet, so assign him the role - Map> existingCoursesRoles = allExistingRoles.get(login); - Set existingCourseRoles = existingCoursesRoles == null ? null - : allExistingRoles.get(login).get(courseId); - if (existingCourseRoles == null) { - existingCourseRoles = new HashSet<>(); - } - existingCourseRoles.add(Role.ROLE_GROUP_MANAGER); + // always set roles for course managers as subcourses could have been added - User user = allExistingUsers.get(login); - userManagementService.setRolesForUserOrganisation(user, course, - existingCourseRoles.stream().map(String::valueOf).collect(Collectors.toList()), false); + Map> existingCoursesRoles = allExistingRoles.get(login); + Set existingCourseRoles = existingCoursesRoles == null ? null + : allExistingRoles.get(login).get(courseId); + if (existingCourseRoles == null) { + existingCourseRoles = new HashSet<>(); + } + existingCourseRoles.add(Role.ROLE_GROUP_MANAGER); - String message = "Group manager \"" + login + "\" added to course"; - logger.info(message); - logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, - "SPEnrolment: " + message); + User user = allExistingParsedUsers.get(login); + userManagementService.setRolesForUserOrganisation(user, course, + existingCourseRoles.stream().map(String::valueOf).collect(Collectors.toList()), true); + + if (!userAlreadyAssigned) { + String message = "Manager \"" + login + "\" added to course"; + logger.info(message); + logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, + "SPEnrolment: " + message); + } + } } // user is a group manager, but he should not; remove him role from course for (User user : courseUsers) { - Map> existingCoursesRoles = allExistingRoles.get(user.getLogin()); - Set existingSubcourseRoles = existingCoursesRoles == null ? null - : existingCoursesRoles.get(courseId); - if (existingSubcourseRoles != null) { - existingSubcourseRoles.remove(Role.ROLE_GROUP_MANAGER); - userManagementService.setRolesForUserOrganisation(user.getUserId(), courseId, existingSubcourseRoles); - - String message = "Group manager \"" + user.getLogin() + "\" removed from course " + courseId; + boolean removedFromCourse = removeFromCourse(course, user, Mode.MANAGER, allExistingRoles); + if (removedFromCourse) { + String message = "Manager \"" + user.getLogin() + "\" removed from course " + courseId; logger.info(message); logEventService.logEvent(LogEvent.TYPE_USER_ORG_ADMIN, creatorId, null, null, null, "SPEnrolment: " + message); Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java =================================================================== diff -u -r2188972474f8d186d6811e3dea2e4136be669335 -r40b5ff3de75f51721a10181b830b5008f2d9f54c --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java (.../IUserManagementService.java) (revision 2188972474f8d186d6811e3dea2e4136be669335) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java (.../IUserManagementService.java) (revision 40b5ff3de75f51721a10181b830b5008f2d9f54c) @@ -246,6 +246,11 @@ Map> getRolesForUser(Integer userId); /** + * Checks if given user has got any roles in any organisation. + */ + boolean hasUserAnyRoles(Integer userId); + + /** * @param login * @param typeId * @param stateId Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java =================================================================== diff -u -r96033f32ff880d045b4c087f52628d233dc98095 -r40b5ff3de75f51721a10181b830b5008f2d9f54c --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision 96033f32ff880d045b4c087f52628d233dc98095) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision 40b5ff3de75f51721a10181b830b5008f2d9f54c) @@ -401,7 +401,17 @@ return baseDAO.findByProperties(UserOrganisation.class, properties); } + /** + * Checks if given user has got any roles in any organisation. + */ @Override + public boolean hasUserAnyRoles(Integer userId) { + Map properties = new HashMap<>(); + properties.put("userOrganisation.user.userId", userId); + return baseDAO.countByProperties(UserOrganisationRole.class, properties) > 0; + } + + @Override public List getUserOrganisationsForUserByTypeAndStatusAndParent(String login, Integer typeId, Integer stateId, Integer parentOrgId) { Map properties = new HashMap<>();