Fisheye: Tag 6e7ab7890111cde84e6557c3507a1e9d3682e318 refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/BranchingAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/BranchingController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/BranchingController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/BranchingController.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,190 @@ +/**************************************************************** + * 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.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +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 org.apache.log4j.Logger; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.BranchActivityEntry; +import org.lamsfoundation.lams.learningdesign.BranchingActivity; +import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.SequenceActivity; +import org.lamsfoundation.lams.monitoring.dto.BranchDTO; +import org.lamsfoundation.lams.monitoring.dto.BranchingDTO; +import org.lamsfoundation.lams.monitoring.service.IMonitoringService; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.util.MessageService; +import org.lamsfoundation.lams.util.WebUtil; +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; + +/** + * The action servlet that provides the support for the + * + * + * + * Due to how XDoclet works, we have to have a separate subclass of BranchingAction for each branching type, and include + * + * + * @author Fiona Malikoff + */ +@Controller +public class BranchingController { + + //--------------------------------------------------------------------- + + @Autowired + @Qualifier("monitoringService") + private IMonitoringService monitoringService; + + public static Logger log = Logger.getLogger(BranchingController.class); + + protected static final String VIEW_BRANCHES_SCREEN = "viewBranches"; + protected static final String CHOSEN_SELECTION_SCREEN = "chosenSelection"; + public static final String PARAM_BRANCHING_DTO = "branching"; + public static final String PARAM_SHOW_GROUP_NAME = "showGroupName"; + public static final String PARAM_LOCAL_FILES = "localFiles"; + public static final String PARAM_MAY_DELETE = "mayDelete"; + public static final String PARAM_MODULE_LANGUAGE_XML = "languageXML"; + protected static final String PARAM_VIEW_MODE = "viewMode"; + + /** + * Display the view screen, irrespective of the branching type. + */ + public String viewBranching(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + long activityId = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + + BranchingActivity activity = (BranchingActivity) monitoringService.getActivityById(activityId, + BranchingActivity.class); + return viewBranching(activity, lessonId, false, request, monitoringService); + } + + protected String viewBranching(BranchingActivity activity, Long lessonId, boolean useLocalFiles, + HttpServletRequest request, IMonitoringService monitoringService) throws IOException, ServletException { + + // in general the progress engine expects the activity and lesson id to be in the request, + // so follow that standard. + request.setAttribute(AttributeNames.PARAM_ACTIVITY_ID, activity.getActivityId()); + request.setAttribute(AttributeNames.PARAM_LESSON_ID, lessonId); + request.setAttribute(AttributeNames.PARAM_TITLE, activity.getTitle()); + request.setAttribute(PARAM_LOCAL_FILES, useLocalFiles); + request.setAttribute(PARAM_MODULE_LANGUAGE_XML, getLanguageXML()); + request.setAttribute(PARAM_MAY_DELETE, Boolean.FALSE); + request.setAttribute(PARAM_VIEW_MODE, Boolean.TRUE); + + // only show the group names if this is a group based branching activity - the names + // are meaningless for chosen and tool based branching + BranchingDTO dto = getBranchingDTO(activity, monitoringService); + request.setAttribute(PARAM_BRANCHING_DTO, dto); + request.setAttribute(PARAM_SHOW_GROUP_NAME, activity.isGroupBranchingActivity()); + if (log.isDebugEnabled()) { + log.debug("viewBranching: Branching activity " + dto); + } + return "chosenSelection"; + } + + // Can't do this in BranchingDTO (although that's where it should be) as we have + // to get the SequenceActivities via the getActivityById to get around Hibernate + // not allowing us to cast the cglib classes. + private BranchingDTO getBranchingDTO(BranchingActivity activity, IMonitoringService monitoringService) { + BranchingDTO dto = new BranchingDTO(); + + dto.setBranchActivityId(activity.getActivityId()); + dto.setBranchActivityName(activity.getTitle()); + + TreeSet branches = new TreeSet<>(); + Iterator iter = activity.getActivities().iterator(); + while (iter.hasNext()) { + Activity childActivity = iter.next(); + SequenceActivity branch = (SequenceActivity) monitoringService + .getActivityById(childActivity.getActivityId(), SequenceActivity.class); + Set mappingEntries = branch.getBranchEntries(); + + // If it is a grouped based or teacher chosen branching, the users will be in groups. + // If not get the user based on the progress engine and create a dummy group. + // Can't use tool session as sequence activities don't have a tool session! + SortedSet groups = new TreeSet<>(); + if (activity.isChosenBranchingActivity() || activity.isGroupBranchingActivity()) { + for (BranchActivityEntry entry : mappingEntries) { + Group group = entry.getGroup(); + groups.add(group); + } + } else { + Group group = new Group(); + if (group.getUsers() == null) { + group.setUsers(new HashSet()); + } + List learners = monitoringService.getLearnersAttemptedOrCompletedActivity(branch); + group.getUsers().addAll(learners); + groups.add(group); + } + branches.add(new BranchDTO(branch, groups)); + } + dto.setBranches(branches); + return dto; + } + + /** + * @return String of xml with all needed language elements + */ + protected String getLanguageXML() { + MessageService messageService = monitoringService.getMessageService(); + ArrayList languageCollection = new ArrayList<>(); + languageCollection.add(new String("button.finished")); + languageCollection.add(new String("label.branching.non.allocated.users.heading")); + languageCollection.add(new String("label.grouping.status")); + languageCollection.add(new String("label.grouping.learners")); + languageCollection.add(new String("error.title")); + languageCollection.add(new String("label.branching.popup.drag.selection.message")); + + String languageOutput = ""; + + for (int i = 0; i < languageCollection.size(); i++) { + languageOutput += "" + + messageService.getMessage(languageCollection.get(i)) + ""; + } + + languageOutput += ""; + + return languageOutput; + } +} \ No newline at end of file Fisheye: Tag 6e7ab7890111cde84e6557c3507a1e9d3682e318 refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/ComplexLearnerProgressAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/ComplexLearnerProgressController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/ComplexLearnerProgressController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/ComplexLearnerProgressController.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,219 @@ +/**************************************************************** + * 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.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.Vector; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.ComplexActivity; +import org.lamsfoundation.lams.learningdesign.ParallelActivity; +import org.lamsfoundation.lams.learningdesign.SequenceActivity; +import org.lamsfoundation.lams.lesson.LearnerProgress; +import org.lamsfoundation.lams.monitoring.dto.ContributeActivityDTO; +import org.lamsfoundation.lams.monitoring.service.IMonitoringService; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +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.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * @author jliew + * + * + * + * + */ +@Controller +public class ComplexLearnerProgressController { + + private static Logger log = Logger.getLogger(ComplexLearnerProgressController.class); + + @Autowired + @Qualifier("monitoringService") + private IMonitoringService monitoringService; + + @RequestMapping("/complexProgress") + public String execute(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + Long activityID = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID, false); + Long lessonID = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, false); + Integer userID = WebUtil.readIntParam(request, AttributeNames.PARAM_USER_ID, false); + + Activity activity = monitoringService.getActivityById(activityID); + + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + + if (activity.isParallelActivity()) { + + ArrayList urls = new ArrayList<>(); + ParallelActivity parallelActivity = (ParallelActivity) activity; + Set parallels = parallelActivity.getActivities(); + Iterator i = parallels.iterator(); + try { + while (i.hasNext()) { + Activity a = (Activity) i.next(); + // get learner progress url for this parallel activity + urls.add(monitoringService.getLearnerActivityURL(lessonID, a.getActivityId(), userID, + user.getUserID())); + } + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + request.setAttribute("parallelUrls", urls); + return "parallelProgress"; + } + + else { + + HashMap statusMap = new HashMap<>(); + HashMap urlMap = new HashMap<>(); + LearnerProgress learnerProgress = monitoringService.getLearnerProgress(userID, lessonID); + request.setAttribute("hasSequenceActivity", false); + List subActivities = new ArrayList<>(); + + if (activity.isOptionsActivity() || activity.isBranchingActivity()) { + + ComplexActivity complexActivity = (ComplexActivity) activity; + + Iterator i = complexActivity.getActivities().iterator(); + + // iterate through each optional or branching activity + while (i.hasNext()) { + Activity aNext = (Activity) i.next(); + + // make sure have castable object, not a CGLIB class + Activity a = monitoringService.getActivityById(aNext.getActivityId()); + ContributeActivityDTO dto = new ContributeActivityDTO(a); + subActivities.add(dto); + + Byte status = learnerProgress.getProgressState(a); + statusMap.put(a.getActivityId(), status); + + if (a.isSequenceActivity()) { + request.setAttribute("hasSequenceActivity", true); + // map learner progress urls of each activity in the sequence + SequenceActivity sequenceActivity = (SequenceActivity) a; + dto.setChildActivities(new Vector()); + processSequenceChildren(lessonID, userID, monitoringService, user, statusMap, urlMap, + learnerProgress, sequenceActivity, dto, null); + } else { + if (status.equals(LearnerProgress.ACTIVITY_ATTEMPTED) + || status.equals(LearnerProgress.ACTIVITY_COMPLETED)) { + urlMap.put(a.getActivityId(), monitoringService.getLearnerActivityURL(lessonID, + a.getActivityId(), userID, user.getUserID())); + } + } + } + + } else if (activity.isSequenceActivity()) { + SequenceActivity sequenceActivity = (SequenceActivity) activity; + try { + processSequenceChildren(lessonID, userID, monitoringService, user, statusMap, urlMap, + learnerProgress, sequenceActivity, null, subActivities); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + } else { + ComplexLearnerProgressController.log.error( + "ComplexLearnerProgress trying to deal with a activity type it doesn't expect. Activity is " + + activity); + return null; + } + + // learner progress urls for children of the sequence activities + request.setAttribute("urlMap", urlMap); + // boolean flags for whether an activity is started + request.setAttribute("statusMap", statusMap); + // set of child activities + request.setAttribute("subActivities", subActivities); + // main activity title + request.setAttribute("activityTitle", activity.getTitle()); + + return "complexProgress"; + } + + } + + /** + * Process the children of the sequence. Best done by traversing the transitions, with the first activity being the + * default activity for the sequence. + * + * If the page is for a SequenceActivity the subActivities list should be included as a parameter and + * parentContributeActivityDTO will be null. + * + * If the page is for a Branching or Optional Sequence activity then subActivities will be null (as the sequence + * activities go in the subactivities list) but parentContributeActivityDTO should not be null. + */ + private void processSequenceChildren(Long lessonID, Integer userID, IMonitoringService monitoringService, + UserDTO user, HashMap statusMap, HashMap urlMap, LearnerProgress learnerProgress, + SequenceActivity sequenceActivity, ContributeActivityDTO parentContributeActivityDTO, + List subActivities) throws IOException { + Activity child = sequenceActivity.getDefaultActivity(); + while (child != null) { + Byte status = learnerProgress.getProgressState(child); + statusMap.put(child.getActivityId(), status); + if (status.equals(LearnerProgress.ACTIVITY_ATTEMPTED) + || status.equals(LearnerProgress.ACTIVITY_COMPLETED)) { + // learner progress url + urlMap.put(child.getActivityId(), monitoringService.getLearnerActivityURL(lessonID, + child.getActivityId(), userID, user.getUserID())); + } + + ContributeActivityDTO dto = new ContributeActivityDTO(child); + if (subActivities != null) { + subActivities.add(dto); + } + if (parentContributeActivityDTO != null) { + parentContributeActivityDTO.getChildActivities().add(dto); + } + + if (child.getTransitionFrom() != null) { + child = child.getTransitionFrom().getToActivity(); + } else { + child = null; + } + } + } +} Fisheye: Tag 6e7ab7890111cde84e6557c3507a1e9d3682e318 refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/EmailNotificationsAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/EmailNotificationsController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/EmailNotificationsController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/EmailNotificationsController.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,761 @@ +/**************************************************************** + * 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.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +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.events.EmailNotificationArchive; +import org.lamsfoundation.lams.events.IEventNotificationService; +import org.lamsfoundation.lams.gradebook.util.GradebookConstants; +import org.lamsfoundation.lams.index.IndexLessonBean; +import org.lamsfoundation.lams.learning.service.ICoreLearnerService; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.logevent.LogEvent; +import org.lamsfoundation.lams.logevent.service.ILogEventService; +import org.lamsfoundation.lams.monitoring.MonitoringConstants; +import org.lamsfoundation.lams.monitoring.dto.EmailScheduleMessageJobDTO; +import org.lamsfoundation.lams.monitoring.quartz.job.EmailScheduleMessageJob; +import org.lamsfoundation.lams.monitoring.service.IMonitoringService; +import org.lamsfoundation.lams.monitoring.service.MonitoringServiceProxy; +import org.lamsfoundation.lams.security.ISecurityService; +import org.lamsfoundation.lams.usermanagement.Organisation; +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.DateUtil; +import org.lamsfoundation.lams.util.ExcelCell; +import org.lamsfoundation.lams.util.ExcelUtil; +import org.lamsfoundation.lams.util.FileUtil; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.quartz.JobBuilder; +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import org.quartz.impl.matchers.GroupMatcher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + *

+ * Responsible for "Email notification" functionality. + *

+ * + * @author Andrey Balan + */ +@Controller +@RequestMapping("/emailNotifications") +public class EmailNotificationsController { + + @Autowired + private WebApplicationContext applicationContext; + + @Autowired + @Qualifier("monitoringService") + private IMonitoringService monitoringService; + + private static Logger log = Logger.getLogger(EmailNotificationsController.class); + + // --------------------------------------------------------------------- + // Class level constants + // --------------------------------------------------------------------- + + private static final String TRIGGER_PREFIX_NAME = "emailMessageOnScheduleTrigger:"; + private static final String JOB_PREFIX_NAME = "emailScheduleMessageJob:"; + + private static IEventNotificationService eventNotificationService; + private static IUserManagementService userManagementService; + private static ILogEventService logEventService; + private static ISecurityService securityService; + private static ILessonService lessonService; + + // --------------------------------------------------------------------- + // Struts Dispatch Method + // --------------------------------------------------------------------- + + /** + * Shows "Email notification" page for particular lesson. + */ + @RequestMapping("/getLessonView") + public String getLessonView(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getCurrentUser().getUserID(), + "show lesson email notifications", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + ICoreLearnerService learnerService = MonitoringServiceProxy + .getLearnerService(applicationContext.getServletContext()); + Lesson lesson = learnerService.getLesson(lessonId); + if (!lesson.getEnableLessonNotifications()) { + getLogEventService().logEvent(LogEvent.TYPE_NOTIFICATION, getCurrentUser().getUserID(), null, lessonId, + null, "Attempted to send notification when notifications are disabled in lesson " + lessonId); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Notifications are disabled in the lesson"); + return null; + } + + Set activities = lesson.getLearningDesign().getActivities(); + request.setAttribute("lesson", lesson); + request.setAttribute("activities", activities); + + return "emailnotifications/lessonNotifications"; + } + + /** + * Shows "Email notification" page for particular course. + */ + @RequestMapping("/getCourseView") + public String getCourseView(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + int orgId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + if (!getSecurityService().isGroupMonitor(orgId, getCurrentUser().getUserID(), "show course email notifications", + false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the organisation"); + return null; + } + + ICoreLearnerService learnerService = MonitoringServiceProxy + .getLearnerService(applicationContext.getServletContext()); + + // getting the organisation + Organisation org = (Organisation) learnerService.getUserManagementService().findById(Organisation.class, orgId); + + boolean isGroupMonitor = getSecurityService().hasOrgRole(orgId, getCurrentUser().getUserID(), + new String[] { Role.GROUP_MANAGER }, "show course email notifications", false); + Integer userRole = isGroupMonitor ? Role.ROLE_GROUP_MANAGER : Role.ROLE_MONITOR; + Map staffMap = getLessonService() + .getLessonsByOrgAndUserWithCompletedFlag(getCurrentUser().getUserID(), orgId, userRole); + + // Already sorted, just double check that it does not contain finished or removed lessons + // This call should not be returning REMOVED lessons anyway so test for finished ones. + ArrayList lessons = new ArrayList<>(staffMap.size()); + for (IndexLessonBean lesson : staffMap.values()) { + if (!Lesson.FINISHED_STATE.equals(lesson.getState())) { + lessons.add(lesson); + } + } + + IndexLessonBean firstLesson = lessons.size() > 0 ? lessons.get(0) : null; + + request.setAttribute("org", org); + request.setAttribute("lessons", lessons); + request.setAttribute("firstLesson", firstLesson); + + return "emailnotifications/courseNotifications"; + } + + /** + * Renders a page listing all scheduled emails. + */ + @RequestMapping("/showScheduledEmails") + public String showScheduledEmails(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException, SchedulerException { + + getUserManagementService(); + Scheduler scheduler = getScheduler(); + TreeSet scheduleList = new TreeSet<>(); + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); + boolean isLessonNotifications = (lessonId != null); + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID, true); + if (isLessonNotifications) { + if (!getSecurityService().isLessonMonitor(lessonId, getCurrentUser().getUserID(), + "show scheduled lesson email notifications", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the lesson"); + return null; + } + } else { + if (!getSecurityService().isGroupMonitor(organisationId, getCurrentUser().getUserID(), + "show scheduled course email notifications", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the organisation"); + return null; + } + } + + Set triggerKeys = scheduler + .getTriggerKeys(GroupMatcher.triggerGroupEquals(Scheduler.DEFAULT_GROUP)); + for (TriggerKey triggerKey : triggerKeys) { + String triggerName = triggerKey.getName(); + if (triggerName.startsWith(EmailNotificationsController.TRIGGER_PREFIX_NAME)) { + Trigger trigger = scheduler.getTrigger(triggerKey); + JobDetail jobDetail = scheduler.getJobDetail(trigger.getJobKey()); + JobDataMap jobDataMap = jobDetail.getJobDataMap(); + + // filter triggers + if (isLessonNotifications) { + Object jobLessonId = jobDataMap.get(AttributeNames.PARAM_LESSON_ID); + if ((jobLessonId == null) || (!lessonId.equals(jobLessonId))) { + continue; + } + } else { + Object jobOrganisationId = jobDataMap.get(AttributeNames.PARAM_ORGANISATION_ID); + if ((jobOrganisationId == null) || (!organisationId.equals(jobOrganisationId))) { + continue; + } + } + + Date triggerDate = trigger.getNextFireTime(); + String emailBody = WebUtil.convertNewlines((String) jobDataMap.get("emailBody")); + int searchType = (Integer) jobDataMap.get("searchType"); + EmailScheduleMessageJobDTO emailScheduleJobDTO = new EmailScheduleMessageJobDTO(); + emailScheduleJobDTO.setTriggerName(triggerName); + emailScheduleJobDTO.setTriggerDate(triggerDate); + emailScheduleJobDTO.setEmailBody(emailBody); + emailScheduleJobDTO.setSearchType(searchType); + scheduleList.add(emailScheduleJobDTO); + } + } + + request.setAttribute("scheduleList", scheduleList); + request.setAttribute(AttributeNames.PARAM_LESSON_ID, lessonId); + request.setAttribute(AttributeNames.PARAM_ORGANISATION_ID, organisationId); + + return "emailnotifications/scheduledEmailList"; + } + + /** + * Renders a page listing all archived emails. + */ + @RequestMapping("/showArchivedEmails") + public String showArchivedEmails(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException, SchedulerException { + + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); + boolean isLessonNotifications = (lessonId != null); + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID, true); + if (isLessonNotifications) { + if (!getSecurityService().isLessonMonitor(lessonId, getCurrentUser().getUserID(), + "show archived lesson email notifications", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the lesson"); + return null; + } + + List notifications = monitoringService.getArchivedEmailNotifications(lessonId); + request.setAttribute("notifications", notifications); + } else { + if (!getSecurityService().isGroupMonitor(organisationId, getCurrentUser().getUserID(), + "show archived course email notifications", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the organisation"); + return null; + } + List notifications = monitoringService + .getArchivedEmailNotifications(organisationId); + request.setAttribute("notifications", notifications); + } + + request.setAttribute(AttributeNames.PARAM_LESSON_ID, lessonId); + request.setAttribute(AttributeNames.PARAM_ORGANISATION_ID, organisationId); + + return "emailnotifications/archivedEmailList"; + } + + @RequestMapping("getArchivedRecipients") + @ResponseBody + public String getArchivedRecipients(HttpServletRequest request, HttpServletResponse response) throws IOException { + IMonitoringService monitoringService = MonitoringServiceProxy + .getMonitoringService(applicationContext.getServletContext()); + + Long emailNotificationUid = WebUtil.readLongParam(request, "emailNotificationUid"); + EmailNotificationArchive notification = (EmailNotificationArchive) getUserManagementService() + .findById(EmailNotificationArchive.class, emailNotificationUid); + + Long lessonId = notification.getLessonId(); + Integer organisationId = notification.getOrganisationId(); + boolean isLessonNotifications = (lessonId != null); + // check if the user is allowed to fetch this data + if (isLessonNotifications) { + if (!getSecurityService().isLessonMonitor(lessonId, getCurrentUser().getUserID(), + "show archived lesson email notification participants", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the lesson"); + return null; + } + } else { + if (!getSecurityService().isGroupMonitor(organisationId, getCurrentUser().getUserID(), + "show archived course email notification participants", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the organisation"); + return null; + } + } + + int page = WebUtil.readIntParam(request, GradebookConstants.PARAM_PAGE); + int rowLimit = WebUtil.readIntParam(request, GradebookConstants.PARAM_ROWS); + + // get only recipients we want on the page + List recipients = monitoringService.getArchivedEmailNotificationRecipients(emailNotificationUid, rowLimit, + (page - 1) * rowLimit); + + // build JSON which is understood by jqGrid + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put(GradebookConstants.ELEMENT_PAGE, page); + responseJSON.put(GradebookConstants.ELEMENT_TOTAL, ((notification.getRecipients().size() - 1) / rowLimit) + 1); + responseJSON.put(GradebookConstants.ELEMENT_RECORDS, recipients.size()); + + ArrayNode rowsJSON = JsonNodeFactory.instance.arrayNode(); + for (User recipient : recipients) { + ObjectNode rowJSON = JsonNodeFactory.instance.objectNode(); + rowJSON.put(GradebookConstants.ELEMENT_ID, recipient.getUserId()); + + ArrayNode cellJSON = JsonNodeFactory.instance.arrayNode(); + cellJSON.add(new StringBuilder(recipient.getLastName()).append(", ").append(recipient.getFirstName()) + .append(" [").append(recipient.getLogin()).append("]").toString()); + + rowJSON.set(GradebookConstants.ELEMENT_CELL, cellJSON); + rowsJSON.add(rowJSON); + } + + responseJSON.set(GradebookConstants.ELEMENT_ROWS, rowsJSON); + response.setContentType("application/json;charset=UTF-8"); + + return responseJSON.toString(); + } + + /** + * Delete a scheduled emails. + * + * @throws JSONException + */ + @RequestMapping("/deleteNotification") + @ResponseBody + public String deleteNotification(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException, SchedulerException { + + String inputTriggerName = WebUtil.readStrParam(request, "triggerName"); + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); + Integer userId = getCurrentUser().getUserID(); + boolean isLessonNotifications = (lessonId != null); + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID, true); + + getUserManagementService(); + Scheduler scheduler = getScheduler(); + + ObjectNode jsonObject = JsonNodeFactory.instance.objectNode(); + String error = null; + + try { + // if this method throws an Exception, there will be no deleteNotification=true in the JSON reply + if (isLessonNotifications) { + if (!getSecurityService().isLessonMonitor(lessonId, userId, "show scheduled lesson email notifications", + false)) { + error = "Unable to delete notification: the user is not a monitor in the lesson"; + } + } else { + if (!getSecurityService().isGroupMonitor(organisationId, userId, + "show scheduled course course email notifications", false)) { + error = "Unable to delete notification: the user is not a monitor in the organisation"; + } + } + + if (error == null) { + Set triggerKeys = scheduler + .getTriggerKeys(GroupMatcher.triggerGroupEquals(Scheduler.DEFAULT_GROUP)); + for (TriggerKey triggerKey : triggerKeys) { + String triggerName = triggerKey.getName(); + if (triggerName.equals(inputTriggerName)) { + Trigger trigger = scheduler.getTrigger(triggerKey); + + JobKey jobKey = trigger.getJobKey(); + + JobDetail jobDetail = scheduler.getJobDetail(trigger.getJobKey()); + JobDataMap jobDataMap = jobDetail.getJobDataMap(); + getLogEventService().logEvent(LogEvent.TYPE_NOTIFICATION, userId, null, lessonId, null, + "Deleting unsent scheduled notification " + jobKey + " " + + jobDataMap.getString("emailBody")); + + scheduler.deleteJob(jobKey); + + } + } + + } + + } catch (Exception e) { + String[] msg = new String[1]; + msg[0] = e.getMessage(); + error = monitoringService.getMessageService().getMessage("error.system.error", msg); + } + + jsonObject.put("deleteNotification", error == null ? "true" : error); + response.setContentType("application/json;charset=utf-8"); + return jsonObject.toString(); + + } + + /** + * Exports the given archived email notification to excel. + */ + @RequestMapping("exportArchivedNotification") + public String exportArchivedNotification(HttpServletRequest request, HttpServletResponse response) + throws IOException { + + Long emailNotificationUid = WebUtil.readLongParam(request, "emailNotificationUid"); + EmailNotificationArchive notification = (EmailNotificationArchive) getUserManagementService() + .findById(EmailNotificationArchive.class, emailNotificationUid); + + Long lessonId = notification.getLessonId(); + Integer organisationId = notification.getOrganisationId(); + boolean isLessonNotifications = (lessonId != null); + // check if the user is allowed to fetch this data + if (isLessonNotifications) { + if (!getSecurityService().isLessonMonitor(lessonId, getCurrentUser().getUserID(), + "export archived lesson email notification", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the lesson"); + return null; + } + } else { + if (!getSecurityService().isGroupMonitor(organisationId, getCurrentUser().getUserID(), + "export archived course email notification", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the organisation"); + return null; + } + } + + LinkedHashMap dataToExport = monitoringService + .exportArchivedEmailNotification(emailNotificationUid); + String fileName = "email_notification_" + + FileUtil.EXPORT_TO_SPREADSHEET_TITLE_DATE_FORMAT.format(notification.getSentOn()) + ".xlsx"; + fileName = FileUtil.encodeFilenameForDownload(request, fileName); + + response.setContentType("application/x-download"); + response.setHeader("Content-Disposition", "attachment;filename=" + fileName); + + ExcelUtil.createExcel(response.getOutputStream(), dataToExport, + monitoringService.getMessageService().getMessage("export.dateheader"), false); + return null; + + } + + /** + * Method called via Ajax. It either emails selected users or schedules these emails to be sent on specified date. + */ + @RequestMapping("/emailUsers") + @ResponseBody + public String emailUsers(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + ObjectNode ObjectNode = JsonNodeFactory.instance.objectNode(); + + String emailBody = WebUtil.readStrParam(request, "emailBody"); + Long scheduleDateParameter = WebUtil.readLongParam(request, "scheduleDate", true); + + String scheduleDateStr = ""; + String emailClauseStr = ""; + // check if we need to send email instantly + if (scheduleDateParameter == null) { + boolean isSuccessfullySent = true; + String[] userIdStrs = request.getParameterValues("userId"); + Set userIdInts = new HashSet<>(); + for (String userIdStr : userIdStrs) { + int userId = Integer.parseInt(userIdStr); + userIdInts.add(userId); + boolean isHtmlFormat = false; + isSuccessfullySent &= getEventNotificationService().sendMessage(null, userId, + IEventNotificationService.DELIVERY_METHOD_MAIL, monitoringService.getMessageService() + .getMessage("event.emailnotifications.email.subject", new Object[] {}), + emailBody, isHtmlFormat); + } + monitoringService.archiveEmailNotification( + WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID, true), + WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true), + WebUtil.readIntParam(request, "searchType", true), emailBody, userIdInts); + + ObjectNode.put("isSuccessfullySent", isSuccessfullySent); + + //prepare data for audit log + scheduleDateStr = "now"; + emailClauseStr = "for users (userIds: " + StringUtils.join(userIdStrs, ",") + ")"; + + } else { + try { + Calendar now = Calendar.getInstance(); + + // calculate scheduleDate + Date scheduleDateTeacherTimezone = new Date(scheduleDateParameter); + TimeZone teacherTimeZone = getCurrentUser().getTimeZone(); + Date scheduleDate = DateUtil.convertFromTimeZoneToDefault(teacherTimeZone, scheduleDateTeacherTimezone); + + // build job detail based on the bean class + JobDetail emailScheduleMessageJob = JobBuilder.newJob(EmailScheduleMessageJob.class) + .withIdentity(EmailNotificationsController.JOB_PREFIX_NAME + now.getTimeInMillis()) + .withDescription("schedule email message to user(s)").usingJobData("emailBody", emailBody) + .build(); + + Map searchParameters = new HashMap<>(); + copySearchParametersFromRequestToMap(request, searchParameters); + searchParameters.forEach(emailScheduleMessageJob.getJobDataMap()::putIfAbsent); + + // create customized triggers + Trigger startLessonTrigger = TriggerBuilder.newTrigger() + .withIdentity(EmailNotificationsController.TRIGGER_PREFIX_NAME + now.getTimeInMillis()) + .startAt(scheduleDate).build(); + // start the scheduling job + Scheduler scheduler = getScheduler(); + scheduler.scheduleJob(emailScheduleMessageJob, startLessonTrigger); + ObjectNode.put("isSuccessfullyScheduled", true); + + //prepare data for audit log + scheduleDateStr = "on " + scheduleDate; + Object lessonIdObj = searchParameters.get(AttributeNames.PARAM_LESSON_ID); + Object lessonIDsObj = searchParameters.get("lessonIDs"); + Object organisationIdObj = searchParameters.get(AttributeNames.PARAM_ORGANISATION_ID); + if (lessonIdObj != null) { + emailClauseStr = "for lesson (lessonId: " + lessonIdObj + ")"; + } else if (lessonIDsObj != null) { + emailClauseStr = "for lessons (lessonIDs: " + StringUtils.join((String[]) lessonIDsObj, ",") + ")"; + } else if (organisationIdObj != null) { + emailClauseStr = "for organisation (organisationId: " + organisationIdObj + ")"; + } + + } catch (SchedulerException e) { + EmailNotificationsController.log + .error("Error occurred at " + "[emailScheduleMessage]- fail to email scheduling", e); + } + } + + //audit log + getLogEventService().logEvent(LogEvent.TYPE_NOTIFICATION, getCurrentUser().getUserID(), null, null, null, + "User " + getCurrentUser().getLogin() + " set a notification " + emailClauseStr + " " + scheduleDateStr + + " with the following notice: " + emailBody); + + response.setContentType("application/json;charset=utf-8"); + return ObjectNode.toString(); + } + + /** + * Refreshes user list. + */ + @RequestMapping("/getUsers") + @ResponseBody + public String getUsers(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + Map map = new HashMap<>(); + copySearchParametersFromRequestToMap(request, map); + Long lessonId = (Long) map.get(AttributeNames.PARAM_LESSON_ID); + Integer orgId = (Integer) map.get(AttributeNames.PARAM_ORGANISATION_ID); + + if (lessonId != null) { + if (!getSecurityService().isLessonMonitor(lessonId, getCurrentUser().getUserID(), + "get users for lesson email notifications", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the lesson"); + return null; + } + } else if (orgId != null) { + if (!getSecurityService().isGroupMonitor(orgId, getCurrentUser().getUserID(), + "get users for course email notifications", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the organisation"); + return null; + } + } + + int searchType = (Integer) map.get("searchType"); + Long activityId = (Long) map.get(AttributeNames.PARAM_ACTIVITY_ID); + Integer xDaystoFinish = (Integer) map.get("daysToDeadline"); + String[] lessonIds = (String[]) map.get("lessonIDs"); + Collection users = monitoringService.getUsersByEmailNotificationSearchType(searchType, lessonId, + lessonIds, activityId, xDaystoFinish, orgId); + + ArrayNode cellarray = JsonNodeFactory.instance.arrayNode(); + + ObjectNode responseDate = JsonNodeFactory.instance.objectNode(); + responseDate.put("total", "" + users.size()); + responseDate.put("page", "" + 1); + responseDate.put("records", "" + users.size()); + + for (User user : users) { + ArrayNode cell = JsonNodeFactory.instance.arrayNode(); + cell.add(new StringBuilder(user.getLastName()).append(", ").append(user.getFirstName()).append(" (") + .append(user.getLogin()).append(")").toString()); + + ObjectNode cellobj = JsonNodeFactory.instance.objectNode(); + cellobj.put("id", "" + user.getUserId()); + cellobj.set("cell", cell); + cellarray.add(cellobj); + } + responseDate.set("rows", cellarray); + response.setContentType("application/json;charset=utf-8"); + return responseDate.toString(); + } + + /** + * Copies search parameters from request to specified map. Validates parameters along the way. + * + * @param request + * @param map + * specified map + */ + private void copySearchParametersFromRequestToMap(HttpServletRequest request, Map map) { + int searchType = WebUtil.readIntParam(request, "searchType"); + map.put("searchType", searchType); + + switch (searchType) { + case MonitoringConstants.LESSON_TYPE_ASSIGNED_TO_LESSON: + case MonitoringConstants.LESSON_TYPE_HAVENT_FINISHED_LESSON: + case MonitoringConstants.LESSON_TYPE_HAVE_FINISHED_LESSON: + case MonitoringConstants.LESSON_TYPE_HAVENT_STARTED_LESSON: + case MonitoringConstants.LESSON_TYPE_HAVE_STARTED_LESSON: + case MonitoringConstants.LESSON_TYPE_HAVENT_REACHED_PARTICULAR_ACTIVITY: + case MonitoringConstants.LESSON_TYPE_LESS_THAN_X_DAYS_TO_DEADLINE: + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Assert.notNull(lessonId); + map.put(AttributeNames.PARAM_LESSON_ID, lessonId); + break; + + case MonitoringConstants.COURSE_TYPE_HAVENT_STARTED_ANY_LESSONS: + case MonitoringConstants.COURSE_TYPE_HAVE_FINISHED_PARTICULAR_LESSON: + case MonitoringConstants.COURSE_TYPE_HAVENT_STARTED_PARTICULAR_LESSON: + case MonitoringConstants.COURSE_TYPE_HAVE_FINISHED_THESE_LESSONS: + case MonitoringConstants.COURSE_TYPE_HAVENT_FINISHED_THESE_LESSONS: + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + Assert.notNull(organisationId); + map.put(AttributeNames.PARAM_ORGANISATION_ID, organisationId); + break; + } + + switch (searchType) { + case MonitoringConstants.LESSON_TYPE_HAVENT_REACHED_PARTICULAR_ACTIVITY: + Long activityId = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + Assert.notNull(activityId); + map.put(AttributeNames.PARAM_ACTIVITY_ID, activityId); + break; + + case MonitoringConstants.LESSON_TYPE_LESS_THAN_X_DAYS_TO_DEADLINE: + Integer xDaystoFinish = WebUtil.readIntParam(request, "daysToDeadline"); + Assert.notNull(xDaystoFinish); + map.put("daysToDeadline", xDaystoFinish); + break; + + case MonitoringConstants.COURSE_TYPE_HAVE_FINISHED_PARTICULAR_LESSON: + case MonitoringConstants.COURSE_TYPE_HAVENT_STARTED_PARTICULAR_LESSON: + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Assert.notNull(lessonId); + map.put(AttributeNames.PARAM_LESSON_ID, lessonId); + break; + case MonitoringConstants.COURSE_TYPE_HAVE_FINISHED_THESE_LESSONS: + case MonitoringConstants.COURSE_TYPE_HAVENT_FINISHED_THESE_LESSONS: + String[] lessonIds = request.getParameterValues(AttributeNames.PARAM_LESSON_ID); + Assert.notNull(lessonIds); + map.put("lessonIDs", lessonIds); + break; + } + + } + + private UserDTO getCurrentUser() { + HttpSession ss = SessionManager.getSession(); + return (UserDTO) ss.getAttribute(AttributeNames.USER); + } + + private IEventNotificationService getEventNotificationService() { + if (eventNotificationService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + eventNotificationService = (IEventNotificationService) ctx.getBean("eventNotificationService"); + } + return eventNotificationService; + } + + private IUserManagementService getUserManagementService() { + if (userManagementService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + userManagementService = (IUserManagementService) ctx.getBean("userManagementService"); + } + return userManagementService; + } + + private ILogEventService getLogEventService() { + if (logEventService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + logEventService = (ILogEventService) ctx.getBean("logEventService"); + } + return logEventService; + } + + private ILessonService getLessonService() { + if (lessonService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + lessonService = (ILessonService) ctx.getBean("lessonService"); + } + return lessonService; + } + + private ISecurityService getSecurityService() { + if (securityService == null) { + WebApplicationContext webContext = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + securityService = (ISecurityService) webContext.getBean("securityService"); + } + + return securityService; + } + + /** + * + * @return the bean that defines Scheduler. + */ + private Scheduler getScheduler() { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + return (Scheduler) ctx.getBean("scheduler"); + } +} Fisheye: Tag 6e7ab7890111cde84e6557c3507a1e9d3682e318 refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/EmailProgressAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/EmailProgressController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/EmailProgressController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/EmailProgressController.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,330 @@ +/**************************************************************** + * 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.Date; +import java.util.Locale; +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.log4j.Logger; +import org.lamsfoundation.lams.events.IEventNotificationService; +import org.lamsfoundation.lams.monitoring.quartz.job.EmailProgressMessageJob; +import org.lamsfoundation.lams.monitoring.service.IMonitoringService; +import org.lamsfoundation.lams.security.ISecurityService; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.quartz.JobBuilder; +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import org.quartz.impl.matchers.GroupMatcher; +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.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Responsible for "Email Progress" functionality. + */ +@Controller +@RequestMapping("/emailProgress") +public class EmailProgressController { + + @Autowired + private WebApplicationContext applicationContext; + + @Autowired + @Qualifier("monitoringService") + private IMonitoringService monitoringService; + + private static Logger log = Logger.getLogger(EmailNotificationsController.class); + + // --------------------------------------------------------------------- + // Class level constants + // --------------------------------------------------------------------- + + private static final String TRIGGER_PREFIX_NAME = "emailProgressMessageTrigger:"; + private static final String JOB_PREFIX_NAME = "emailProgressMessageJob:"; + + private static IEventNotificationService eventNotificationService; + private static ISecurityService securityService; + + // --------------------------------------------------------------------- + // Struts Dispatch Method + // --------------------------------------------------------------------- + + /** + * Gets learners or monitors of the lesson and organisation containing it. + * + * @throws SchedulerException + */ + @RequestMapping("/getEmailProgressDates") + @ResponseBody + public String getEmailProgressDates(HttpServletRequest request, HttpServletResponse response) + throws IOException, SchedulerException { + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getCurrentUser().getUserID(), "get class members", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + ArrayNode datesJSON = JsonNodeFactory.instance.arrayNode(); + + // find all the current dates set up to send the emails + Scheduler scheduler = getScheduler(); + String triggerPrefix = getTriggerPrefix(lessonId); + SortedSet currentDatesSet = new TreeSet<>(); + Set triggerKeys = scheduler + .getTriggerKeys(GroupMatcher.triggerGroupEquals(Scheduler.DEFAULT_GROUP)); + for (TriggerKey triggerKey : triggerKeys) { + String triggerName = triggerKey.getName(); + if (triggerName.startsWith(triggerPrefix)) { + Trigger trigger = scheduler.getTrigger(triggerKey); + JobDetail jobDetail = scheduler.getJobDetail(trigger.getJobKey()); + JobDataMap jobDataMap = jobDetail.getJobDataMap(); + + // get only the trigger for the current lesson + + Object jobLessonId = jobDataMap.get(AttributeNames.PARAM_LESSON_ID); + if (lessonId.equals(jobLessonId)) { + + Date triggerDate = trigger.getNextFireTime(); + currentDatesSet.add(triggerDate); + } + } + } + + for (Date date : currentDatesSet) { + datesJSON.add(createDateJSON(request.getLocale(), user, date, null)); + } + responseJSON.set("dates", datesJSON); + + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + private ObjectNode createDateJSON(Locale locale, UserDTO user, Date date, String result) { + ObjectNode dateJSON = JsonNodeFactory.instance.objectNode(); + if (result != null) { + dateJSON.put("result", result); + } + if (date != null) { + dateJSON.put("id", date.getTime()); + dateJSON.put("ms", date.getTime()); + dateJSON.put("date", DateUtil.convertToStringForJSON(date, locale)); + } + return dateJSON; + } + + private String getTriggerPrefix(Long lessonId) { + return new StringBuilder(EmailProgressController.TRIGGER_PREFIX_NAME).append(lessonId).append(":").toString(); + } + + private String getTriggerName(Long lessonId, Date date) { + return new StringBuilder(EmailProgressController.TRIGGER_PREFIX_NAME).append(lessonId).append(":") + .append(date.getTime()).toString(); + } + + private String getJobName(Long lessonId, Date date) { + return new StringBuilder(EmailProgressController.JOB_PREFIX_NAME).append(lessonId).append(":") + .append(date.getTime()).toString(); + } + + /** + * Add or remove a date for the email progress + */ + @RequestMapping("/updateEmailProgressDate") + @ResponseBody + public String updateEmailProgressDate(HttpServletRequest request, HttpServletResponse response) throws IOException { + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getCurrentUser().getUserID(), "get class members", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + + // as we are using ms since UTC 0 calculated on the client, this will be correctly set up as server date + // and does not need changing (assuming the user's LAMS timezone matches the user's computer timezone). + long dateId = WebUtil.readLongParam(request, "id"); + Date newDate = new Date(dateId); + boolean add = WebUtil.readBooleanParam(request, "add"); + + // calculate scheduleDate + String scheduledTriggerName = getTriggerName(lessonId, newDate); + ObjectNode dateJSON = null; + + try { + Scheduler scheduler = getScheduler(); + Set triggerKeys = scheduler + .getTriggerKeys(GroupMatcher.triggerGroupEquals(Scheduler.DEFAULT_GROUP)); + Trigger trigger = null; + + for (TriggerKey triggerKey : triggerKeys) { + if (scheduledTriggerName.equals(triggerKey.getName())) { + trigger = scheduler.getTrigger(triggerKey); + break; + } + } + + if (add) { + if (trigger == null) { + String desc = new StringBuilder("Send progress email. Lesson ").append(lessonId).append(" on ") + .append(newDate).toString(); + // build job detail based on the bean class + JobDetail EmailProgressMessageJob = JobBuilder.newJob(EmailProgressMessageJob.class) + .withIdentity(getJobName(lessonId, newDate)).withDescription(desc) + .usingJobData(AttributeNames.PARAM_LESSON_ID, lessonId).build(); + + // create customized triggers + Trigger startLessonTrigger = TriggerBuilder.newTrigger().withIdentity(scheduledTriggerName) + .startAt(newDate).build(); + // start the scheduling job + scheduler.scheduleJob(EmailProgressMessageJob, startLessonTrigger); + + dateJSON = createDateJSON(request.getLocale(), user, newDate, "added"); + + } else { + dateJSON = createDateJSON(request.getLocale(), user, newDate, "none"); + } + } else if (!add) { + if (trigger != null) { + // remove trigger + scheduler.deleteJob(trigger.getJobKey()); + + dateJSON = createDateJSON(request.getLocale(), user, null, "deleted"); + } else { + dateJSON = createDateJSON(request.getLocale(), user, null, "none"); + } + } + } catch (SchedulerException e) { + EmailProgressController.log.error("Error occurred at " + "[EmailProgressAction]- fail to email scheduling", + e); + } + + if (dateJSON != null) { + response.setContentType("application/json;charset=utf-8"); + } + return dateJSON.toString(); + } + + @RequestMapping("/sendLessonProgressEmail") + @ResponseBody + public String sendLessonProgressEmail(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Integer monitorUserId = getCurrentUser().getUserID(); + if (!getSecurityService().isLessonMonitor(lessonId, monitorUserId, "get lesson progress", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + String parts[] = monitoringService.generateLessonProgressEmail(lessonId, monitorUserId); + String error = null; + int sent = 0; + + try { + if (getEventNotificationService().sendMessage(null, monitorUserId, + IEventNotificationService.DELIVERY_METHOD_MAIL, parts[0], parts[1], true)) { + sent = 1; + } + + } catch (InvalidParameterException ipe) { + error = ipe.getMessage(); + } catch (Exception e) { + error = e.getMessage(); + } + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("sent", sent); + if (error != null) { + responseJSON.put("error", error); + } + + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + private UserDTO getCurrentUser() { + HttpSession ss = SessionManager.getSession(); + return (UserDTO) ss.getAttribute(AttributeNames.USER); + } + + private IEventNotificationService getEventNotificationService() { + if (eventNotificationService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + eventNotificationService = (IEventNotificationService) ctx.getBean("eventNotificationService"); + } + return eventNotificationService; + } + + private ISecurityService getSecurityService() { + if (securityService == null) { + WebApplicationContext webContext = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + securityService = (ISecurityService) webContext.getBean("securityService"); + } + + return securityService; + } + + /** + * + * @return the bean that defines Scheduler. + */ + private Scheduler getScheduler() { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + return (Scheduler) ctx.getBean("scheduler"); + } +} Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/FileUploadForm.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r6e7ab7890111cde84e6557c3507a1e9d3682e318 --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/FileUploadForm.java (.../FileUploadForm.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/FileUploadForm.java (.../FileUploadForm.java) (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -21,45 +21,49 @@ * **************************************************************** */ - package org.lamsfoundation.lams.monitoring.web; -import org.apache.struts.action.ActionForm; -import org.apache.struts.upload.FormFile; +import org.springframework.web.multipart.MultipartFile; -public class FileUploadForm extends ActionForm { +public class FileUploadForm { private static final long serialVersionUID = -2841551690414943725L; - + private Integer organisationID; private Long lessonID; private Long activityID; - private FormFile attachmentFile; + private MultipartFile attachmentFile; + public Integer getOrganisationID() { - return organisationID; + return organisationID; } + public void setOrganisationID(Integer organisationID) { - this.organisationID = organisationID; + this.organisationID = organisationID; } + public Long getLessonID() { - return lessonID; + return lessonID; } + public void setLessonID(Long lessonID) { - this.lessonID = lessonID; + this.lessonID = lessonID; } + public Long getActivityID() { - return activityID; + return activityID; } + public void setActivityID(Long activityID) { - this.activityID = activityID; + this.activityID = activityID; } - public FormFile getAttachmentFile() { - return attachmentFile; + + public MultipartFile getAttachmentFile() { + return attachmentFile; } - public void setAttachmentFile(FormFile attachmentFile) { - this.attachmentFile = attachmentFile; + + public void setAttachmentFile(MultipartFile attachmentFile) { + this.attachmentFile = attachmentFile; } - - } Fisheye: Tag 6e7ab7890111cde84e6557c3507a1e9d3682e318 refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GateAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GateController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GateController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GateController.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,295 @@ +/**************************************************************** + * 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.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collection; +import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; + +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.lamsfoundation.lams.learning.service.ICoreLearnerService; +import org.lamsfoundation.lams.learningdesign.GateActivity; +import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.ScheduleGateActivity; +import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.monitoring.service.IMonitoringService; +import org.lamsfoundation.lams.monitoring.service.MonitoringServiceException; +import org.lamsfoundation.lams.monitoring.service.MonitoringServiceProxy; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +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.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.context.WebApplicationContext; + +/** + *

+ * The action servlet that allows the teacher to view the status of sync gate, scheduling gate and permission gate. The + * teacher can also force the gate to open through this servlet. + *

+ * + *

+ * Regarding view gate status, followings contents should be shown by calling this action servlet: + *

  • 1.View the status of an sync gate, the lams should show how many learners are waiting and the size of the total + * class.
  • + *
  • 2.View the status of the permission gate, the lams shows the number of the learners waiting in front of the + * gates.
  • + *
  • 3.View the status of the schedule gate, the lams shows the gate status. If the schedule has been triggerred. The + * teacher should be able to change the trigger.
  • + *

    + * + * @author Jacky Fang + * @since 2005-4-15 + * @version 1.1 + * + * + * + * + * + * + * + * + */ +@Controller +@RequestMapping("/gate") +public class GateController { + + @Autowired + @Qualifier("monitoringService") + private IMonitoringService monitoringService; + + @Autowired + private WebApplicationContext applicationContext; + + // --------------------------------------------------------------------- + // Instance variables + // --------------------------------------------------------------------- + // private static Logger log = Logger.getLogger(GateAction.class); + + private ICoreLearnerService learnerService; + private ILessonService lessonService; + + private static final DateFormat SCHEDULING_DATETIME_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm"); + + // --------------------------------------------------------------------- + // Method + // --------------------------------------------------------------------- + /** + *

    + * The dispatch method that allows the teacher to view the status of the gate. It is expecting the caller passed in + * lesson id and gate activity id as http parameter. Otherwise, the utility method will generate some exception. + *

    + * + *

    + * Based on the lesson id and gate activity id, it sets up the gate form to show the waiting learners and the total + * waiting learners. Regarding schedule gate, it also shows the estimated gate opening time and gate closing time. + *

    + * + * Note: gate form attribute waitingLearners got setup after the view is dispatch to ensure + * there won't be casting exception occur if the activity id is not a gate by chance. + * + */ + @RequestMapping("/viewGate") + public String viewGate(@ModelAttribute GateForm gateForm, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + // if this is the initial call then activity id will be in the request, otherwise + // get it from the form (if being called from openGate.jsp + Long gateIdLong = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID, true); + if (gateIdLong == null) { + gateIdLong = gateForm.getActivityId(); + } + long gateId = gateIdLong != null ? gateIdLong.longValue() : -1; + + learnerService = MonitoringServiceProxy.getLearnerService(applicationContext.getServletContext()); + + GateActivity gate = (GateActivity) monitoringService.getActivityById(gateId); + + if (gate == null) { + throw new MonitoringServiceException("Gate activity missing. Activity id" + gateId); + } + + gateForm.setActivityId(gateIdLong); + + return findViewByGateType(gateForm, gate); + } + + /** + * Open the gate if is closed. + */ + @RequestMapping("/openGate") + public String openGate(@ModelAttribute GateForm gateForm, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + GateActivity gate = monitoringService.openGate(gateForm.getActivityId(), getUserId()); + + return findViewByGateType(gateForm, gate); + } + + /** + * Allows a single learner to pass the gate. + */ + @RequestMapping("/openGateForSingleUser") + public String openGateForSingleUser(@ModelAttribute GateForm gateForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + Long gateIdLong = gateForm.getActivityId(); + String userId = gateForm.getUserId(); + String[] userIdsString = userId.split(","); + List userIds = new LinkedList<>(); + for (String userIdString : userIdsString) { + if (StringUtils.isNotBlank(userIdString)) { + userIds.add(Integer.valueOf(userIdString)); + } + } + GateActivity gate = monitoringService.openGateForSingleUser(gateIdLong, userIds.toArray(new Integer[] {})); + return findViewByGateType(gateForm, gate); + } + + @RequestMapping("/scheduleGate") + public String scheduleGate(@ModelAttribute GateForm gateForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException, ParseException { + + Long gateId = gateForm.getActivityId(); + String dateAsString = gateForm.getScheduleDate(); + GateActivity gate = null; + if (dateAsString != null && dateAsString.trim().length() > 0) { + gate = monitoringService.scheduleGate(gateId, SCHEDULING_DATETIME_FORMAT.parse(dateAsString), getUserId()); + } else { + gate = (GateActivity) monitoringService.getActivityById(gateId); + } + return findViewByGateType(gateForm, gate); + } + + // --------------------------------------------------------------------- + // Helper Methods + // --------------------------------------------------------------------- + private Integer getUserId() { + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + return user != null ? user.getUserID() : null; + } + + /** + * Dispatch view the according to the gate type. + * + */ + @RequestMapping("/findViewByGateType") + private String findViewByGateType(@ModelAttribute GateForm gateForm, GateActivity gate) { + + lessonService = MonitoringServiceProxy.getLessonService(applicationContext.getServletContext()); + + // reset all the other fields, so that the following code only has to set up its own values (LDEV-1237) + gateForm.setGate(null); + gateForm.setWaitingLearnerList(null); + gateForm.setAllowedToPassLearnerList(null); + gateForm.setForbiddenLearnerList(null); + gateForm.setStartingTime(null); + gateForm.setEndingTime(null); + gateForm.setScheduleDate(null); + + gateForm.setGate(gate); + + // setup the total learners + int totalLearners = 0; + for (Group group : learnerService.getGroupsForGate(gate)) { + // users collection is extra-lazy, so checking its size will not trigger full load + totalLearners += group.getUsers().size(); + } + gateForm.setTotalLearners(totalLearners); + + // dispatch the view according to the type of the gate. + if (gate.isSynchGate()) { + Integer waitingLearnerCount = lessonService.getCountLearnersInCurrentActivity(gate); + gateForm.setWaitingLearners(waitingLearnerCount); + return "gate/sychGateContent"; + } else if (gate.isScheduleGate()) { + Integer waitingLearnerCount = lessonService.getCountLearnersInCurrentActivity(gate); + gateForm.setWaitingLearners(waitingLearnerCount); + return viewScheduleGate(gateForm, (ScheduleGateActivity) gate); + } else if (gate.isPermissionGate() || gate.isSystemGate() || gate.isConditionGate()) { + List waitingLearnersList = monitoringService.getLearnersAttemptedActivity(gate); + gateForm.setWaitingLearners(waitingLearnersList.size()); + gateForm.setWaitingLearnerList(waitingLearnersList); + gateForm.setAllowedToPassLearnerList(gate.getAllowedToPassLearners()); + Set learnerGroups = learnerService.getGroupsForGate(gate); + Collection forbiddenUsers = new HashSet<>(); + for (Group learnerGroup : learnerGroups) { + // only here users are fetched from DB as it is an extra-lazy collection + forbiddenUsers.addAll(learnerGroup.getUsers()); + } + forbiddenUsers.removeAll(gate.getAllowedToPassLearners()); + gateForm.setForbiddenLearnerList(forbiddenUsers); + if (gate.isConditionGate()) { + return "gate/conditionGateContent"; + } + + return "gate/permissionGateContent"; + } else { + throw new MonitoringServiceException("Invalid gate activity. " + "gate id [" + gate.getActivityId() + + "] - the type [" + gate.getActivityTypeId() + "] is not a gate type"); + } + } + + /** + * Set up the form attributes specific to the schedule gate and navigate to the schedule gate view. + */ + @RequestMapping("/viewScheduleGate") + public String viewScheduleGate(@ModelAttribute GateForm gateForm, ScheduleGateActivity scheduleGate) { + + if (Boolean.TRUE.equals(scheduleGate.getGateActivityCompletionBased())) { + gateForm.setActivityCompletionBased(true); + } else { + gateForm.setActivityCompletionBased(false); + learnerService = MonitoringServiceProxy.getLearnerService(applicationContext.getServletContext()); + Lesson lesson = learnerService.getLessonByActivity(scheduleGate); + Calendar startingTime = new GregorianCalendar(TimeZone.getDefault()); + startingTime.setTime(lesson.getStartDateTime()); + startingTime.add(Calendar.MINUTE, scheduleGate.getGateStartTimeOffset().intValue()); + gateForm.setStartingTime(startingTime.getTime()); + } + + return "gate/scheduleGateContent"; + } +} \ No newline at end of file Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GateForm.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GateForm.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GateForm.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,130 @@ +package org.lamsfoundation.lams.monitoring.web; + +import java.util.Collection; +import java.util.Date; + +import org.lamsfoundation.lams.learningdesign.GateActivity; + +public class GateForm { + + private GateActivity gate; + + private Long activityId; + + private Integer waitingLearners; + + private Integer totalLearners; + + private Date startingTime; + + private Date endingTime; + + private boolean activityCompletionBased; + + private Collection waitingLearnerList; + + private Collection forbiddenLearnerList; + + private Collection allowedToPassLearnerList; + + private String userId; + + private String scheduleDate; + + public GateActivity getGate() { + return gate; + } + + public void setGate(GateActivity gate) { + this.gate = gate; + } + + public Long getActivityId() { + return activityId; + } + + public void setActivityId(Long activityId) { + this.activityId = activityId; + } + + public Integer getWaitingLearners() { + return waitingLearners; + } + + public void setWaitingLearners(Integer waitingLearners) { + this.waitingLearners = waitingLearners; + } + + public Integer getTotalLearners() { + return totalLearners; + } + + public void setTotalLearners(Integer totalLearners) { + this.totalLearners = totalLearners; + } + + public Date getStartingTime() { + return startingTime; + } + + public void setStartingTime(Date startingTime) { + this.startingTime = startingTime; + } + + public Date getEndingTime() { + return endingTime; + } + + public void setEndingTime(Date endingTime) { + this.endingTime = endingTime; + } + + public boolean isActivityCompletionBased() { + return activityCompletionBased; + } + + public void setActivityCompletionBased(boolean activityCompletionBased) { + this.activityCompletionBased = activityCompletionBased; + } + + public Collection getWaitingLearnerList() { + return waitingLearnerList; + } + + public void setWaitingLearnerList(Collection waitingLearnerList) { + this.waitingLearnerList = waitingLearnerList; + } + + public Collection getForbiddenLearnerList() { + return forbiddenLearnerList; + } + + public void setForbiddenLearnerList(Collection forbiddenLearnerList) { + this.forbiddenLearnerList = forbiddenLearnerList; + } + + public Collection getAllowedToPassLearnerList() { + return allowedToPassLearnerList; + } + + public void setAllowedToPassLearnerList(Collection allowedToPassLearnerList) { + this.allowedToPassLearnerList = allowedToPassLearnerList; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getScheduleDate() { + return scheduleDate; + } + + public void setScheduleDate(String scheduleDate) { + this.scheduleDate = scheduleDate; + } + +} Fisheye: Tag 6e7ab7890111cde84e6557c3507a1e9d3682e318 refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingAJAXAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingAJAXController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingAJAXController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingAJAXController.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,442 @@ +/**************************************************************** + * 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.Iterator; +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.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.lamsfoundation.lams.learning.web.action.GroupingAction; +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.IMonitoringService; +import org.lamsfoundation.lams.monitoring.service.MonitoringServiceProxy; +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.WebUtil; +import org.lamsfoundation.lams.web.action.LamsDispatchAction; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +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 + */ +public class GroupingAJAXAction extends LamsDispatchAction { + + // --------------------------------------------------------------------- + + private static final String CHOSEN_GROUPING_SCREEN = "chosenGrouping"; + private static final String VIEW_GROUPS_SCREEN = "viewGroups"; + 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"; + + private static ISecurityService securityService; + + /** + * Start the process of doing the chosen grouping + * + * Input parameters: activityID + */ + public ActionForward startGrouping(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + return startGrouping(mapping, form, request, response, false); + } + + private ActionForward startGrouping(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response, boolean forcePrintView) throws IOException, ServletException { + + Long activityID = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + IMonitoringService monitoringService = MonitoringServiceProxy + .getMonitoringService(getServlet().getServletContext()); + Activity activity = monitoringService.getActivityById(activityID); + + Grouping grouping = null; + if (activity.isChosenBranchingActivity()) { + grouping = activity.getGrouping(); + monitoringService.createChosenBranchingGroups(activityID); + } else { + grouping = ((GroupingActivity) activity).getCreateGrouping(); + } + + request.setAttribute(AttributeNames.PARAM_ACTIVITY_ID, activityID); + request.setAttribute(AttributeNames.PARAM_LESSON_ID, lessonId); + request.setAttribute(GroupingAJAXAction.PARAM_ACTIVITY_TITLE, activity.getTitle()); + request.setAttribute(GroupingAJAXAction.PARAM_ACTIVITY_DESCRIPTION, activity.getDescription()); + + if ( !forcePrintView && grouping.isChosenGrouping()) { + // can I remove groups/users - can't if tool sessions have been created + Set groups = grouping.getGroups(); + Iterator iter = groups.iterator(); + boolean mayDelete = true; + while (mayDelete && iter.hasNext()) { + Group group = iter.next(); + mayDelete = group.mayBeDeleted(); + } + + // is this grouping used for branching. If it is, must honour the groups + // set in authoring or some groups won't have a branch. mayDelete can still + // be true or false as you can remove users from groups, you just can't remove + // groups due to the branching relationship. + boolean usedForBranching = grouping.isUsedForBranching(); + + request.setAttribute(GroupingAJAXAction.PARAM_MAY_DELETE, mayDelete); + request.setAttribute(GroupingAJAXAction.PARAM_USED_FOR_BRANCHING, usedForBranching); + request.setAttribute(GroupingAJAXAction.PARAM_MAX_NUM_GROUPS, grouping.getMaxNumberOfGroups()); + request.setAttribute(GroupingAJAXAction.PARAM_VIEW_MODE, Boolean.FALSE); + + return mapping.findForward(GroupingAJAXAction.CHOSEN_GROUPING_SCREEN); + } + + 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(GroupingAction.GROUPS, groups); + // go to a view only screen for random grouping + return mapping.findForward(GroupingAJAXAction.VIEW_GROUPS_SCREEN); + } + + /** + * Called by the chosen grouping / course grouping screen to show a print version of the grouping. + */ + public ActionForward printGrouping(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) 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(mapping, form, request, response, 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) { + IUserManagementService userManagementService = MonitoringServiceProxy + .getUserManagementService(getServlet().getServletContext()); + 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(GroupingAction.GROUPS, groups); + request.setAttribute("isCourseGrouping", true); // flag to page it is a course grouping so use the field names for OrganisationGroup + return mapping.findForward(GroupingAJAXAction.VIEW_GROUPS_SCREEN); + } + /** + * Moves users between groups, removing them from previous group and creating a new one, if needed. + */ + public ActionForward addMembers(ActionMapping mapping, ActionForm form, 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, GroupingAJAXAction.PARAM_MEMBERS, true); + String[] members = StringUtils.isBlank(membersParam) ? null : membersParam.split(","); + + // remove users from current group + IMonitoringService monitoringService = MonitoringServiceProxy + .getMonitoringService(getServlet().getServletContext()); + if (members != null) { + Activity activity = monitoringService.getActivityById(activityID); + Grouping grouping = activity.isChosenBranchingActivity() ? activity.getGrouping() + : ((GroupingActivity) activity).getCreateGrouping(); + User exampleUser = (User) MonitoringServiceProxy.getUserManagementService(getServlet().getServletContext()) + .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 (LamsDispatchAction.log.isDebugEnabled()) { + LamsDispatchAction.log.debug("Removing users " + membersParam.toString() + " from group " + + group.getGroupId() + " in activity " + activityID); + } + + try { + monitoringService.removeUsersFromGroup(activityID, group.getGroupId(), members); + } catch (LessonServiceException e) { + LamsDispatchAction.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, GroupingAJAXAction.PARAM_NAME); + if (LamsDispatchAction.log.isDebugEnabled()) { + LamsDispatchAction.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 (LamsDispatchAction.log.isDebugEnabled()) { + LamsDispatchAction.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) { + LamsDispatchAction.log.error(e); + result = false; + } + } + } + + responseJSON.put("result", result); + response.getWriter().write(responseJSON.toString()); + return null; + } + + /** + * Stores lesson grouping as a course grouping. + */ + public ActionForward saveAsCourseGrouping(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException { + IMonitoringService monitoringService = MonitoringServiceProxy + .getMonitoringService(getServlet().getServletContext()); + IUserManagementService userManagementService = MonitoringServiceProxy + .getUserManagementService(getServlet().getServletContext()); + 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 (!getSecurityService().hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_ADMIN, Role.GROUP_MANAGER, Role.MONITOR, Role.AUTHOR }, + "view organisation groupings", false)) { + 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); + response.getWriter().write(responseJSON.toString()); + return null; + } + + /** + * Renames the group. + */ + public ActionForward changeGroupName(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) { + Long groupID = WebUtil.readLongParam(request, AttributeNames.PARAM_GROUP_ID); + String name = WebUtil.readStrParam(request, GroupingAJAXAction.PARAM_NAME); + if (name != null) { + if (LamsDispatchAction.log.isDebugEnabled()) { + LamsDispatchAction.log.debug("Renaming group " + groupID + " to \"" + name + "\""); + } + IMonitoringService monitoringService = MonitoringServiceProxy + .getMonitoringService(getServlet().getServletContext()); + 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 + */ + @SuppressWarnings("unchecked") + public ActionForward checkGroupingNameUnique(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException { + IUserManagementService userManagementService = MonitoringServiceProxy + .getUserManagementService(getServlet().getServletContext()); + + 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"); + response.getWriter().write(responseJSON.toString()); + return null; + } + + /** + * Checks if a group can be removed and performs it. + */ + public ActionForward removeGroup(ActionMapping mapping, ActionForm form, 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); + IMonitoringService monitoringService = MonitoringServiceProxy + .getMonitoringService(getServlet().getServletContext()); + boolean result = true; + + // check if the group can be removed + Group group = (Group) MonitoringServiceProxy.getUserManagementService(getServlet().getServletContext()) + .findById(Group.class, groupID); + result = group.mayBeDeleted(); + + if (result) { + try { + if (LamsDispatchAction.log.isDebugEnabled()) { + LamsDispatchAction.log.debug("Removing group " + groupID + " from activity " + activityID); + } + monitoringService.removeGroup(activityID, groupID); + } catch (LessonServiceException e) { + LamsDispatchAction.log.error(e); + result = false; + } + } + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("result", result); + response.getWriter().write(responseJSON.toString()); + return null; + } + + private ISecurityService getSecurityService() { + if (securityService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(getServlet().getServletContext()); + securityService = (ISecurityService) ctx.getBean("securityService"); + } + return securityService; + } +} Fisheye: Tag 6e7ab7890111cde84e6557c3507a1e9d3682e318 refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,1794 @@ +/**************************************************************** + * 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.io.PrintWriter; +import java.security.InvalidParameterException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionMapping; +import org.lamsfoundation.lams.authoring.ObjectExtractor; +import org.lamsfoundation.lams.authoring.service.IAuthoringService; +import org.lamsfoundation.lams.learning.service.ILearnerService; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.BranchingActivity; +import org.lamsfoundation.lams.learningdesign.ChosenBranchingActivity; +import org.lamsfoundation.lams.learningdesign.ComplexActivity; +import org.lamsfoundation.lams.learningdesign.ContributionTypes; +import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.GroupingActivity; +import org.lamsfoundation.lams.learningdesign.LearningDesign; +import org.lamsfoundation.lams.learningdesign.OptionsWithSequencesActivity; +import org.lamsfoundation.lams.learningdesign.SequenceActivity; +import org.lamsfoundation.lams.learningdesign.ToolActivity; +import org.lamsfoundation.lams.learningdesign.Transition; +import org.lamsfoundation.lams.learningdesign.exception.LearningDesignException; +import org.lamsfoundation.lams.lesson.LearnerProgress; +import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.dto.LessonDetailsDTO; +import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.logevent.LogEvent; +import org.lamsfoundation.lams.logevent.service.ILogEventService; +import org.lamsfoundation.lams.monitoring.MonitoringConstants; +import org.lamsfoundation.lams.monitoring.dto.ContributeActivityDTO; +import org.lamsfoundation.lams.monitoring.service.IMonitoringService; +import org.lamsfoundation.lams.monitoring.service.MonitoringServiceProxy; +import org.lamsfoundation.lams.security.ISecurityService; +import org.lamsfoundation.lams.tool.exception.LamsToolServiceException; +import org.lamsfoundation.lams.tool.service.ILamsToolService; +import org.lamsfoundation.lams.usermanagement.Organisation; +import org.lamsfoundation.lams.usermanagement.Role; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.exception.UserException; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.util.CentralConstants; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.JsonUtil; +import org.lamsfoundation.lams.util.MessageService; +import org.lamsfoundation.lams.util.ValidationUtil; +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.ResponseBody; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.util.HtmlUtils; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The action servlet that provide all the monitoring functionalities. It interact with the teacher via JSP monitoring + * interface. + * + * @author Jacky Fang + */ +@Controller +@RequestMapping("/monitoring") +public class MonitoringController { + + @Autowired + private WebApplicationContext applicationContext; + + private static Logger log = Logger.getLogger(MonitoringController.class); + + // --------------------------------------------------------------------- + // Instance variables + // --------------------------------------------------------------------- + + // --------------------------------------------------------------------- + // Class level constants - Struts forward + // --------------------------------------------------------------------- + private static final String NOT_SUPPORTED_SCREEN = "notsupported"; + private static final String TIME_CHART_SCREEN = "timeChart"; + private static final String ERROR = "error"; + private static final DateFormat LESSON_SCHEDULING_DATETIME_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm"); + + private static final int LATEST_LEARNER_PROGRESS_LESSON_DISPLAY_LIMIT = 53; + private static final int LATEST_LEARNER_PROGRESS_ACTIVITY_DISPLAY_LIMIT = 7; + private static final int USER_PAGE_SIZE = 10; + + private static ILogEventService logEventService; + + private static ILessonService lessonService; + + private static ISecurityService securityService; + + private static IMonitoringService monitoringService; + + private static IUserManagementService userManagementService; + + private static ILearnerService learnerService; + + private static ILamsToolService toolService; + + private static MessageService messageService; + + private Integer getUserId() { + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + return user != null ? user.getUserID() : null; + } + + private String redirectToURL(HttpServletResponse response, String url) throws IOException { + if (url == null) { + return "notsupported"; + } + + String fullURL = WebUtil.convertToFullURL(url); + response.sendRedirect(response.encodeRedirectURL(fullURL)); + return null; + } + + /** + * Initializes a lesson for specific learning design with the given lesson title and lesson description. If + * initialization is successful, this method will the ID of new lesson. + * + * Currently used only in TestHarness and Authoring Preview. + */ + @RequestMapping("/initializeLesson") + public String initializeLesson(HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + String title = WebUtil.readStrParam(request, "lessonName"); + if (title == null) { + title = "lesson"; + } + String desc = WebUtil.readStrParam(request, "lessonDescription", true); + if (desc == null) { + desc = "description"; + } + Integer organisationId = WebUtil.readIntParam(request, "organisationID", true); + long ldId = WebUtil.readLongParam(request, AttributeNames.PARAM_LEARNINGDESIGN_ID); + Integer copyType = WebUtil.readIntParam(request, "copyType", true); + String customCSV = request.getParameter("customCSV"); + Boolean learnerPresenceAvailable = WebUtil.readBooleanParam(request, "learnerPresenceAvailable", false); + Boolean learnerImAvailable = WebUtil.readBooleanParam(request, "learnerImAvailable", false); + Boolean liveEditEnabled = WebUtil.readBooleanParam(request, "liveEditEnabled", false); + Boolean forceRestart = WebUtil.readBooleanParam(request, "forceRestart", false); + Boolean allowRestart = WebUtil.readBooleanParam(request, "allowRestart", false); + boolean gradebookOnComplete = WebUtil.readBooleanParam(request, "gradebookOnComplete", false); + + Lesson newLesson = null; + if ((copyType != null) && copyType.equals(LearningDesign.COPY_TYPE_PREVIEW)) { + newLesson = getMonitoringService().initializeLessonForPreview(title, desc, ldId, getUserId(), customCSV, + learnerPresenceAvailable, learnerImAvailable, liveEditEnabled); + } else { + try { + newLesson = getMonitoringService().initializeLesson(title, desc, ldId, organisationId, getUserId(), + customCSV, false, false, learnerPresenceAvailable, learnerImAvailable, liveEditEnabled, false, + forceRestart, allowRestart, gradebookOnComplete, null, null); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the organisation"); + return null; + } + } + + return (newLesson.getLessonId()).toString(); + } + + /** + * The Struts dispatch method that starts a lesson that has been created beforehand. Most likely, the request to + * start lesson should be triggered by the Monitoring This method will delegate to the Spring service bean to + * complete all the steps for starting a lesson. + * + * @throws IOException + * @throws ServletException + */ + @RequestMapping("/startLesson") + public String startLesson(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + try { + getMonitoringService().startLesson(lessonId, getUserId()); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + response.setContentType("text/plain;charset=utf-8"); + response.getWriter().write("true"); + return null; + } + + @RequestMapping("/") + public String createLessonClass(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + int organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + Integer userID = WebUtil.readIntParam(request, AttributeNames.PARAM_USER_ID); + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + + IUserManagementService userManagementService = MonitoringServiceProxy + .getUserManagementService(applicationContext.getServletContext()); + + Organisation organisation = (Organisation) userManagementService.findById(Organisation.class, organisationId); + List allUsers = userManagementService.getUsersFromOrganisation(organisationId); + String learnerGroupName = organisation.getName() + " learners"; + String staffGroupName = organisation.getName() + " staff"; + List learners = parseUserList(request, "learners", allUsers); + List staff = parseUserList(request, "monitors", allUsers); + + try { + getMonitoringService().createLessonClassForLesson(lessonId, organisation, learnerGroupName, learners, + staffGroupName, staff, userID); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the lesson"); + return null; + } + + return null; + } + + @RequestMapping("/") + public String addLesson(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException, ParseException { + String lessonName = request.getParameter("lessonName"); + if (!ValidationUtil.isOrgNameValid(lessonName)) { + throw new IOException("Lesson name contains invalid characters"); + } + + int organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + long ldId = WebUtil.readLongParam(request, AttributeNames.PARAM_LEARNINGDESIGN_ID); + + boolean introEnable = WebUtil.readBooleanParam(request, "introEnable", false); + String introDescription = introEnable ? request.getParameter("introDescription") : null; + boolean introImage = introEnable && WebUtil.readBooleanParam(request, "introImage", false); + boolean startMonitor = WebUtil.readBooleanParam(request, "startMonitor", false); + boolean enableLiveEdit = WebUtil.readBooleanParam(request, "liveEditEnable", false); + boolean notificationsEnable = WebUtil.readBooleanParam(request, "notificationsEnable", false); + boolean presenceEnable = WebUtil.readBooleanParam(request, "presenceEnable", false); + boolean imEnable = WebUtil.readBooleanParam(request, "imEnable", false); + Integer splitNumberLessons = WebUtil.readIntParam(request, "splitNumberLessons", true); + boolean schedulingEnable = WebUtil.readBooleanParam(request, "schedulingEnable", false); + Date schedulingDatetime = null; + Date schedulingEndDatetime = null; + if (schedulingEnable) { + String dateString = request.getParameter("schedulingDatetime"); + if (dateString != null && dateString.length() > 0) { + schedulingDatetime = MonitoringController.LESSON_SCHEDULING_DATETIME_FORMAT.parse(dateString); + } + dateString = request.getParameter("schedulingEndDatetime"); + if (dateString != null && dateString.length() > 0) { + schedulingEndDatetime = MonitoringController.LESSON_SCHEDULING_DATETIME_FORMAT.parse(dateString); + } + } + boolean forceRestart = WebUtil.readBooleanParam(request, "forceRestart", false); + boolean allowRestart = WebUtil.readBooleanParam(request, "allowRestart", false); + + boolean precedingLessonEnable = WebUtil.readBooleanParam(request, "precedingLessonEnable", false); + Long precedingLessonId = precedingLessonEnable ? WebUtil.readLongParam(request, "precedingLessonId", true) + : null; + boolean timeLimitEnable = WebUtil.readBooleanParam(request, "timeLimitEnable", false); + Integer timeLimitDays = WebUtil.readIntParam(request, "timeLimitDays", true); + boolean timeLimitIndividualField = WebUtil.readBooleanParam(request, "timeLimitIndividual", false); + Integer timeLimitIndividual = timeLimitEnable && timeLimitIndividualField ? timeLimitDays : null; + Integer timeLimitLesson = timeLimitEnable && !timeLimitIndividualField ? timeLimitDays : null; + boolean gradebookOnComplete = WebUtil.readBooleanParam(request, "gradebookOnComplete", false); + + IUserManagementService userManagementService = MonitoringServiceProxy + .getUserManagementService(applicationContext.getServletContext()); + + Organisation organisation = (Organisation) userManagementService.findById(Organisation.class, organisationId); + Integer userId = getUserId(); + User creator = (User) userManagementService.findById(User.class, userId); + + List allUsers = userManagementService.getUsersFromOrganisation(organisationId); + List learners = parseUserList(request, "learners", allUsers); + String learnerGroupName = organisation.getName() + " learners"; + + List staff = parseUserList(request, "monitors", allUsers); + // add the creator as staff, if not already done + if (!staff.contains(creator)) { + staff.add(creator); + } + String staffGroupName = organisation.getName() + " staff"; + + // either all users participate in a lesson, or we split them among instances + List lessonInstanceLearners = splitNumberLessons == null ? learners + : new ArrayList<>((learners.size() / splitNumberLessons) + 1); + for (int lessonIndex = 1; lessonIndex <= (splitNumberLessons == null ? 1 : splitNumberLessons); lessonIndex++) { + String lessonInstanceName = lessonName; + String learnerGroupInstanceName = learnerGroupName; + String staffGroupInstanceName = staffGroupName; + + if (splitNumberLessons != null) { + // prepare data for lesson split + lessonInstanceName += " " + lessonIndex; + learnerGroupInstanceName += " " + lessonIndex; + staffGroupInstanceName += " " + lessonIndex; + lessonInstanceLearners.clear(); + for (int learnerIndex = lessonIndex - 1; learnerIndex < learners + .size(); learnerIndex += splitNumberLessons) { + lessonInstanceLearners.add(learners.get(learnerIndex)); + } + } + + if (MonitoringController.log.isDebugEnabled()) { + MonitoringController.log.debug("Creating lesson " + + (splitNumberLessons == null ? "" : "(" + lessonIndex + "/" + splitNumberLessons + ") ") + "\"" + + lessonInstanceName + "\""); + } + + Lesson lesson = null; + try { + lesson = getMonitoringService().initializeLesson(lessonInstanceName, introDescription, ldId, + organisationId, userId, null, introEnable, introImage, presenceEnable, imEnable, enableLiveEdit, + notificationsEnable, forceRestart, allowRestart, gradebookOnComplete, timeLimitIndividual, + precedingLessonId); + + getMonitoringService().createLessonClassForLesson(lesson.getLessonId(), organisation, + learnerGroupInstanceName, lessonInstanceLearners, staffGroupInstanceName, staff, userId); + } catch (SecurityException e) { + try { + response.sendError(HttpServletResponse.SC_FORBIDDEN, + "User is not a monitor in the organisation or lesson"); + } catch (IllegalStateException e1) { + MonitoringController.log + .warn("Tried to tell user that \"User is not a monitor in the organisation or lesson\"," + + "but the HTTP response was already written, probably by some other error"); + } + return null; + } + + if (!startMonitor) { + try { + if (schedulingDatetime == null) { + getMonitoringService().startLesson(lesson.getLessonId(), userId); + } else { + // if lesson should start in few days, set it here + getMonitoringService().startLessonOnSchedule(lesson.getLessonId(), schedulingDatetime, userId); + } + + // monitor has given an end date/time for the lesson + if (schedulingEndDatetime != null) { + getMonitoringService().finishLessonOnSchedule(lesson.getLessonId(), schedulingEndDatetime, + userId); + // if lesson should finish in few days, set it here + } else if (timeLimitLesson != null) { + getMonitoringService().finishLessonOnSchedule(lesson.getLessonId(), timeLimitLesson, userId); + } + + } catch (SecurityException e) { + try { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + } catch (IllegalStateException e1) { + MonitoringController.log.warn("Tried to tell user that \"User is not a monitor in the lesson\"," + + "but the HTTP response was already written, probably by some other error"); + } + return null; + } + } + } + + return null; + } + + /** + * Adds all course learners to the given lesson. + */ + @RequestMapping("/") + public String addAllOrganisationLearnersToLesson(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException { + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getUserId(), "add all lesson learners to lesson", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + Lesson lesson = getLessonService().getLesson(lessonId); + Vector learners = getUserManagementService() + .getUsersFromOrganisationByRole(lesson.getOrganisation().getOrganisationId(), Role.LEARNER, true); + getLessonService().addLearners(lesson, learners); + return null; + } + + @RequestMapping("/") + public String startOnScheduleLesson(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws ParseException, IOException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + String dateStr = WebUtil.readStrParam(request, MonitoringConstants.PARAM_LESSON_START_DATE); + Date startDate = MonitoringController.LESSON_SCHEDULING_DATETIME_FORMAT.parse(dateStr); + try { + getMonitoringService().startLessonOnSchedule(lessonId, startDate, getUserId()); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + } + + return null; + } + + /** + * The Struts dispatch method to archive a lesson. + * + * @throws IOException + * @throws ServletException + */ + @RequestMapping("/") + public String archiveLesson(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + try { + getMonitoringService().archiveLesson(lessonId, getUserId()); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + } + + return null; + } + + /** + * The Struts dispatch method to "unarchive" a lesson. Returns it back to its previous state. + * + * @throws IOException + * @throws ServletException + */ + @RequestMapping("/") + public String unarchiveLesson(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + try { + getMonitoringService().unarchiveLesson(lessonId, getUserId()); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + } + return null; + } + + /** + * The purpose of suspending is to hide the lesson from learners temporarily. It doesn't make any sense to suspend a + * created or a not started (ie scheduled) lesson as they will not be shown on the learner interface anyway! If the + * teacher tries to suspend a lesson that is not in the STARTED_STATE, then an error should be returned to UI. + */ + @RequestMapping("/") + public String suspendLesson(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException, ParseException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + String dateStr = WebUtil.readStrParam(request, MonitoringConstants.PARAM_LESSON_END_DATE, true); + try { + if (dateStr == null || dateStr.length() == 0) { + getMonitoringService().suspendLesson(lessonId, getUserId(), true); + } else { + getMonitoringService().finishLessonOnSchedule(lessonId, + MonitoringController.LESSON_SCHEDULING_DATETIME_FORMAT.parse(dateStr), getUserId()); + } + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + } + return null; + } + + /** + * Unsuspend a lesson which state must be Lesson.SUPSENDED_STATE. Otherwise a error message will return to UI + * client. + * + * @param mapping + * @param form + * @param request + * @param response + * @return + * @throws IOException + * @throws ServletException + */ + @RequestMapping("/") + public String unsuspendLesson(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + try { + getMonitoringService().unsuspendLesson(lessonId, getUserId()); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + } + return null; + } + + /** + *

    + * The STRUTS action will send back a JSON message after marking the lesson by the given lesson ID as + * Lesson.REMOVED_STATE status. + *

    + *

    + * This action need a lession ID as input. + *

    + * + * @throws IOException + * @throws ServletException + */ + @RequestMapping("/") + public String removeLesson(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Integer userId = getUserId(); + boolean permanently = WebUtil.readBooleanParam(request, "permanently", false); + + if (permanently) { + getMonitoringService().removeLessonPermanently(lessonId, userId); + response.setContentType("text/plain;charset=utf-8"); + response.getWriter().print("OK"); + return null; + } + + ObjectNode jsonObject = JsonNodeFactory.instance.objectNode(); + + try { + // if this method throws an Exception, there will be no removeLesson=true in the JSON reply + getMonitoringService().removeLesson(lessonId, userId); + jsonObject.put("removeLesson", true); + + } catch (Exception e) { + String[] msg = new String[1]; + msg[0] = e.getMessage(); + jsonObject.put("removeLesson", + getMonitoringService().getMessageService().getMessage("error.system.error", msg)); + } + + response.setContentType("application/json;charset=utf-8"); + response.getWriter().print(jsonObject); + return null; + } + + /** + *

    + * This action need a lession ID, Learner ID and Activity ID as input. Activity ID is optional, if it is null, all + * activities for this learner will complete to as end as possible. + *

    + * + * @param form + * @param request + * @param response + * @return An ActionForward + * @throws IOException + * @throws ServletException + * @throws JSONException + */ + @RequestMapping("/") + public String forceComplete(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + // get parameters + Long activityId = null; + String actId = request.getParameter(AttributeNames.PARAM_ACTIVITY_ID); + if (actId != null) { + try { + activityId = new Long(Long.parseLong(actId)); + } catch (Exception e) { + activityId = null; + } + } + + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + String learnerIDs = request.getParameter(MonitoringConstants.PARAM_LEARNER_ID); + Integer requesterId = getUserId(); + boolean removeLearnerContent = WebUtil.readBooleanParam(request, + MonitoringConstants.PARAM_REMOVE_LEARNER_CONTENT, false); + + ObjectNode jsonCommand = JsonNodeFactory.instance.objectNode(); + jsonCommand.put("message", getMessageService().getMessage("force.complete.learner.command.message")); + jsonCommand.put("redirectURL", "/lams/learning/learner.do?method=joinLesson&lessonID=" + lessonId); + String command = jsonCommand.toString(); + + String activityDescription = null; + if (activityId != null) { + Activity activity = getMonitoringService().getActivityById(activityId); + activityDescription = new StringBuffer(activity.getTitle()).append(" (").append(activityId).append(")") + .toString(); + } + + StringBuffer learnerIdNameBuf = new StringBuffer(); + String message = null; + User learner = null; + boolean oneOrMoreProcessed = false; + try { + for (String learnerIDString : learnerIDs.split(",")) { + if (oneOrMoreProcessed) { + learnerIdNameBuf.append(", "); + } else { + oneOrMoreProcessed = true; + } + Integer learnerID = Integer.valueOf(learnerIDString); + message = getMonitoringService().forceCompleteActivitiesByUser(learnerID, requesterId, lessonId, + activityId, removeLearnerContent); + + learner = (User) getUserManagementService().findById(User.class, learnerID); + getLearnerService().createCommandForLearner(lessonId, learner.getLogin(), command); + learnerIdNameBuf.append(learner.getLogin()).append(" (").append(learnerID).append(")"); + } + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + if (MonitoringController.log.isDebugEnabled()) { + MonitoringController.log.debug("Force complete for learners " + learnerIdNameBuf.toString() + " lesson " + + lessonId + ". " + message); + } + + // audit log force completion attempt + String messageKey = (activityId == null) ? "audit.force.complete.end.lesson" : "audit.force.complete"; + + Object[] args = new Object[] { learnerIdNameBuf.toString(), activityDescription, lessonId }; + String auditMessage = getMonitoringService().getMessageService().getMessage(messageKey, args); + getLogEventService().logEvent(LogEvent.TYPE_FORCE_COMPLETE, requesterId, null, lessonId, activityId, + auditMessage + " " + message); + + PrintWriter writer = response.getWriter(); + writer.println(message); + return null; + } + + /** + * Get learners who are part of the lesson class. + */ + @RequestMapping("/getLessonLearners") + public String getLessonLearners(HttpServletRequest request, HttpServletResponse response) throws IOException { + + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getUserId(), "get lesson learners", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + String searchPhrase = request.getParameter("searchPhrase"); + Integer pageNumber = WebUtil.readIntParam(request, "pageNumber", true); + if (pageNumber == null) { + pageNumber = 1; + } + boolean orderAscending = WebUtil.readBooleanParam(request, "orderAscending", true); + + List learners = getLessonService().getLessonLearners(lessonId, searchPhrase, + MonitoringController.USER_PAGE_SIZE, (pageNumber - 1) * MonitoringController.USER_PAGE_SIZE, + orderAscending); + ArrayNode learnersJSON = JsonNodeFactory.instance.arrayNode(); + for (User learner : learners) { + learnersJSON.add(WebUtil.userToJSON(learner)); + } + + Integer learnerCount = getLessonService().getCountLessonLearners(lessonId, searchPhrase); + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.set("learners", learnersJSON); + responseJSON.put("learnerCount", learnerCount); + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + /** + * Gets learners or monitors of the lesson and organisation containing it. + */ + @RequestMapping("/getClassMembers") + @ResponseBody + public String getClassMembers(HttpServletRequest request, HttpServletResponse response) throws IOException { + + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getUserId(), "get class members", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + Lesson lesson = getLessonService().getLesson(lessonId); + String role = WebUtil.readStrParam(request, AttributeNames.PARAM_ROLE); + boolean isMonitor = role.equals(Role.MONITOR); + User creator = isMonitor ? lesson.getUser() : null; + Integer currentUserId = isMonitor ? getUserId() : null; + String searchPhrase = request.getParameter("searchPhrase"); + Integer pageNumber = WebUtil.readIntParam(request, "pageNumber", true); + if (pageNumber == null) { + pageNumber = 1; + } + boolean orderAscending = WebUtil.readBooleanParam(request, "orderAscending", true); + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + + // find organisation users and whether they participate in the current lesson + Map users = getLessonService().getUsersWithLessonParticipation(lessonId, role, searchPhrase, + MonitoringController.USER_PAGE_SIZE, (pageNumber - 1) * MonitoringController.USER_PAGE_SIZE, + orderAscending); + + // if the result is less then page size, then no need for full check of user count + Integer userCount = users.size() < MonitoringController.USER_PAGE_SIZE ? users.size() + : getUserManagementService().getCountRoleForOrg(lesson.getOrganisation().getOrganisationId(), + isMonitor ? Role.ROLE_MONITOR : Role.ROLE_LEARNER, searchPhrase); + + responseJSON.put("userCount", userCount); + + ArrayNode usersJSON = JsonNodeFactory.instance.arrayNode(); + for (Entry userEntry : users.entrySet()) { + User user = userEntry.getKey(); + ObjectNode userJSON = WebUtil.userToJSON(user); + userJSON.put("classMember", userEntry.getValue()); + // teacher can't remove lesson creator and himself from the lesson staff + if (isMonitor && (creator.getUserId().equals(user.getUserId()) || currentUserId.equals(user.getUserId()))) { + userJSON.put("readonly", true); + } + usersJSON.add(userJSON); + } + responseJSON.set("users", usersJSON); + + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + /** + * Gets users in JSON format who are at the given activity at the moment or finished the given lesson. + */ + @RequestMapping("/getCurrentLearners") + @ResponseBody + public String getCurrentLearners(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException { + ArrayNode learnersJSON = JsonNodeFactory.instance.arrayNode(); + Integer learnerCount = null; + + Integer pageNumber = WebUtil.readIntParam(request, "pageNumber", true); + if (pageNumber == null) { + pageNumber = 1; + } + boolean orderAscending = WebUtil.readBooleanParam(request, "orderAscending", true); + // if activity ID is provided, lesson ID is ignored + Long activityId = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID, true); + if (activityId == null) { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getUserId(), "get lesson completed learners", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + List learners = getMonitoringService().getUsersCompletedLesson(lessonId, + MonitoringController.USER_PAGE_SIZE, (pageNumber - 1) * MonitoringController.USER_PAGE_SIZE, + orderAscending); + for (User learner : learners) { + learnersJSON.add(WebUtil.userToJSON(learner)); + } + + learnerCount = getMonitoringService().getCountLearnersCompletedLesson(lessonId); + } else { + Activity activity = getMonitoringService().getActivityById(activityId); + Lesson lesson = (Lesson) activity.getLearningDesign().getLessons().iterator().next(); + if (!getSecurityService().isLessonMonitor(lesson.getLessonId(), getUserId(), "get activity learners", + false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + Set activities = new TreeSet<>(); + activities.add(activityId); + + List learners = getMonitoringService().getLearnersByActivities(activities.toArray(new Long[] {}), + MonitoringController.USER_PAGE_SIZE, (pageNumber - 1) * MonitoringController.USER_PAGE_SIZE, + orderAscending); + for (User learner : learners) { + learnersJSON.add(WebUtil.userToJSON(learner)); + } + learnerCount = getMonitoringService().getCountLearnersCurrentActivities(new Long[] { activityId }) + .get(activityId); + } + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.set("learners", learnersJSON); + responseJSON.put("learnerCount", learnerCount); + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + /** + * Adds/removes learners and monitors to/from lesson class. + */ + @RequestMapping("/updateLessonClass") + public String updateLessonClass(HttpServletRequest request, HttpServletResponse response) throws IOException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getUserId(), "update lesson class", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + int userId = WebUtil.readIntParam(request, AttributeNames.PARAM_USER_ID); + String role = WebUtil.readStrParam(request, AttributeNames.PARAM_ROLE); + boolean add = WebUtil.readBooleanParam(request, "add"); + boolean result = false; + + if (role.equals(Role.MONITOR)) { + if (add) { + result = getLessonService().addStaffMember(lessonId, userId); + } else { + result = getLessonService().removeStaffMember(lessonId, userId); + } + } else if (role.equals(Role.LEARNER)) { + if (add) { + result = getLessonService().addLearner(lessonId, userId); + } else { + getLessonService().removeLearnerProgress(lessonId, userId); + result = getLessonService().removeLearner(lessonId, userId); + } + } + + if (result) { + MonitoringController.log.info((add ? "Added a " : "Removed a ") + role + " with ID " + userId + + (add ? " to" : " from") + " lesson " + lessonId); + } else { + MonitoringController.log.warn("Failed when trying to " + (add ? "add a " : "remove a ") + role + " with ID " + + userId + (add ? " to" : " from") + " lesson " + lessonId); + } + + return null; + } + + @RequestMapping("/getDictionaryXML") + public String getDictionaryXML(HttpServletRequest request, HttpServletResponse response) throws IOException { + + MessageService messageService = getMonitoringService().getMessageService(); + + String module = WebUtil.readStrParam(request, "module", false); + + ArrayList languageCollection = new ArrayList<>(); + if (module.equals("timechart")) { + + languageCollection.add(new String("sys.error")); + languageCollection.add(new String("chart.btn.activity.split")); + languageCollection.add(new String("chart.btn_completion.rate")); + languageCollection.add(new String("chart.series.completed.time")); + languageCollection.add(new String("chart.series.average.time")); + languageCollection.add(new String("chart.series.duration")); + languageCollection.add(new String("chart.legend.average")); + languageCollection.add(new String("show.average.checkbox")); + languageCollection.add(new String("search.learner.textbox")); + languageCollection.add(new String("chart.learner.linear.axis.title")); + languageCollection.add(new String("chart.learner.category.axis.title")); + languageCollection.add(new String("chart.learner.datatip.average")); + + languageCollection.add(new String("label.learner")); + languageCollection.add(new String("time.chart.panel.title")); + languageCollection.add(new String("chart.time.format.hours")); + languageCollection.add(new String("chart.time.format.minutes")); + languageCollection.add(new String("chart.time.format.seconds")); + languageCollection.add(new String("label.completed")); + languageCollection.add(new String("advanced.tab.form.validation.schedule.date.error")); + languageCollection.add(new String("alert.no.learner.data")); + } + + String languageOutput = ""; + + for (int i = 0; i < languageCollection.size(); i++) { + languageOutput += "" + + messageService.getMessage(languageCollection.get(i)) + ""; + } + + languageOutput += ""; + + response.setContentType("text/xml"); + response.setCharacterEncoding("UTF-8"); + + return languageOutput; + } + + /** + * Calls the server to bring up the learner progress page. Assumes destination is a new window. The userid that + * comes from UI is the user id of the learner for which we are calculating the url. This is different to all the + * other calls. + */ + @RequestMapping("/") + public String getLearnerActivityURL(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, LamsToolServiceException { + + Integer learnerUserID = new Integer(WebUtil.readIntParam(request, AttributeNames.PARAM_USER_ID)); + Long activityID = new Long(WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID)); + Long lessonID = new Long(WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID)); + try { + String url = getMonitoringService().getLearnerActivityURL(lessonID, activityID, learnerUserID, getUserId()); + return redirectToURL(response, url); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + } + + /** Calls the server to bring up the activity's monitoring page. Assumes destination is a new window */ + @RequestMapping("/getActivityMonitorURL") + @ResponseBody + public String getActivityMonitorURL(HttpServletRequest request, HttpServletResponse response) + throws IOException, LamsToolServiceException { + Long activityID = new Long(WebUtil.readLongParam(request, "activityID")); + Long lessonID = new Long(WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID)); + String contentFolderID = WebUtil.readStrParam(request, "contentFolderID"); + try { + String url = getMonitoringService().getActivityMonitorURL(lessonID, activityID, contentFolderID, + getUserId()); + return redirectToURL(response, url); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + } + + /** + * Displays Monitor Lesson page. + */ + @RequestMapping("/monitorLesson") + public String monitorLesson(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + LessonDetailsDTO lessonDTO = getLessonService().getLessonDetails(lessonId); + + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + if (lessonDTO.getCreateDateTime() != null) { + DateFormat sfm = new SimpleDateFormat("yyyyMMdd_HHmmss"); + lessonDTO.setCreateDateTimeStr(sfm.format(lessonDTO.getCreateDateTime())); + } + // prepare encoded lessonId for shortened learner URL + lessonDTO.setEncodedLessonID(WebUtil.encodeLessonId(lessonId)); + + if (!getSecurityService().isLessonMonitor(lessonId, user.getUserID(), "monitor lesson", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + // should info box on Sequence tab be displayed? + Short sequenceTabInfoShowCount = (Short) ss.getAttribute("sequenceTabInfoShowCount"); + if (sequenceTabInfoShowCount == null) { + sequenceTabInfoShowCount = 0; + } + // only few times per session + if (sequenceTabInfoShowCount < MonitoringConstants.SEQUENCE_TAB_SHOW_INFO_MAX_COUNT) { + sequenceTabInfoShowCount++; + ss.setAttribute("sequenceTabInfoShowCount", sequenceTabInfoShowCount); + request.setAttribute("sequenceTabShowInfo", true); + } + + IUserManagementService userManagementService = MonitoringServiceProxy + .getUserManagementService(applicationContext.getServletContext()); + Organisation organisation = (Organisation) userManagementService.findById(Organisation.class, + lessonDTO.getOrganisationID()); + request.setAttribute("notificationsAvailable", organisation.getEnableCourseNotifications()); + boolean enableLiveEdit = organisation.getEnableLiveEdit() && getUserManagementService() + .isUserInRole(user.getUserID(), organisation.getOrganisationId(), Role.AUTHOR); + request.setAttribute("enableLiveEdit", enableLiveEdit); + request.setAttribute("lesson", lessonDTO); + request.setAttribute("isTBLSequence", isTBLSequence(lessonId)); + + return "monitor"; + } + + /** + * If learning design contains the following activities Grouping->(MCQ or Assessment)->Leader Selection->Scratchie + * (potentially with some other gates or activities in the middle), there is a good chance this is a TBL sequence + * and all activities must be grouped. + */ + private boolean isTBLSequence(Long lessonId) { + + Lesson lesson = getLessonService().getLesson(lessonId); + Long firstActivityId = lesson.getLearningDesign().getFirstActivity().getActivityId(); + //Hibernate CGLIB is failing to load the first activity in the sequence as a ToolActivity + Activity firstActivity = getMonitoringService().getActivityById(firstActivityId); + + return verifyNextActivityFitsTbl(firstActivity, "Grouping"); + } + + /** + * Traverses the learning design verifying it follows typical TBL structure + * + * @param activity + * @param anticipatedActivity + * could be either "Grouping", "MCQ or Assessment", "Leaderselection" or "Scratchie" + */ + private boolean verifyNextActivityFitsTbl(Activity activity, String anticipatedActivity) { + + Transition transitionFromActivity = activity.getTransitionFrom(); + //TBL can finish with the Scratchie + if (transitionFromActivity == null && !"Scratchie".equals(anticipatedActivity)) { + return false; + } + // query activity from DB as transition holds only proxied activity object + Long nextActivityId = transitionFromActivity == null ? null + : transitionFromActivity.getToActivity().getActivityId(); + Activity nextActivity = nextActivityId == null ? null : monitoringService.getActivityById(nextActivityId); + + switch (anticipatedActivity) { + case "Grouping": + //the first activity should be a grouping + if (activity instanceof GroupingActivity) { + return verifyNextActivityFitsTbl(nextActivity, "MCQ or Assessment"); + + } else { + return verifyNextActivityFitsTbl(nextActivity, "Grouping"); + } + + case "MCQ or Assessment": + //the second activity shall be a MCQ or Assessment + if (activity.isToolActivity() && (CentralConstants.TOOL_SIGNATURE_ASSESSMENT + .equals(((ToolActivity) activity).getTool().getToolSignature()) + || CentralConstants.TOOL_SIGNATURE_MCQ + .equals(((ToolActivity) activity).getTool().getToolSignature()))) { + return verifyNextActivityFitsTbl(nextActivity, "Leaderselection"); + + } else { + return verifyNextActivityFitsTbl(nextActivity, "MCQ or Assessment"); + } + + case "Leaderselection": + //the third activity shall be a Leader Selection + if (activity.isToolActivity() && CentralConstants.TOOL_SIGNATURE_LEADERSELECTION + .equals(((ToolActivity) activity).getTool().getToolSignature())) { + return verifyNextActivityFitsTbl(nextActivity, "Scratchie"); + + } else { + return verifyNextActivityFitsTbl(nextActivity, "Leaderselection"); + } + + case "Scratchie": + //the fourth activity shall be Scratchie + if (activity.isToolActivity() && CentralConstants.TOOL_SIGNATURE_SCRATCHIE + .equals(((ToolActivity) activity).getTool().getToolSignature())) { + return true; + + } else if (nextActivity == null) { + return false; + + } else { + return verifyNextActivityFitsTbl(nextActivity, "Scratchie"); + } + + default: + return false; + } + } + + /** + * Gets users whose progress bars will be displayed in Learner tab in Monitor. + */ + @RequestMapping("/getLearnerProgressPage") + @ResponseBody + public String getLearnerProgressPage(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getUserId(), "get learner progress page", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + String searchPhrase = request.getParameter("searchPhrase"); + Integer pageNumber = WebUtil.readIntParam(request, "pageNumber", true); + if (pageNumber == null) { + pageNumber = 1; + } + // are the learners sorted by the most completed first? + boolean isProgressSorted = WebUtil.readBooleanParam(request, "isProgressSorted", false); + + // either sort by name or how much a learner progressed into the lesson + List learners = isProgressSorted + ? getMonitoringService().getLearnersByMostProgress(lessonId, searchPhrase, 10, (pageNumber - 1) * 10) + : getLessonService().getLessonLearners(lessonId, searchPhrase, 10, (pageNumber - 1) * 10, true); + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + for (User learner : learners) { + responseJSON.withArray("learners").add(WebUtil.userToJSON(learner)); + } + + // get all possible learners matching the given phrase, if any; used for max page number + responseJSON.put("learnerPossibleNumber", getLessonService().getCountLessonLearners(lessonId, searchPhrase)); + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + /** + * Produces data to update Lesson tab in Monitor. + */ + @RequestMapping("/getLessonDetails") + @ResponseBody + public String getLessonDetails(HttpServletRequest request, HttpServletResponse response) throws IOException { + + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + + if (!getSecurityService().isLessonMonitor(lessonId, user.getUserID(), "get lesson details", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + Lesson lesson = getLessonService().getLesson(lessonId); + LearningDesign learningDesign = lesson.getLearningDesign(); + + Locale userLocale = new Locale(user.getLocaleLanguage(), user.getLocaleCountry()); + + responseJSON.put(AttributeNames.PARAM_LEARNINGDESIGN_ID, learningDesign.getLearningDesignId()); + responseJSON.put("numberPossibleLearners", getLessonService().getCountLessonLearners(lessonId, null)); + responseJSON.put("lessonStateID", lesson.getLessonStateId()); + + responseJSON.put("lessonName", HtmlUtils.htmlEscape(lesson.getLessonName())); + responseJSON.put("lessonDescription", lesson.getLessonDescription()); + + Date startOrScheduleDate = lesson.getStartDateTime() == null ? lesson.getScheduleStartDate() + : lesson.getStartDateTime(); + Date finishDate = lesson.getScheduleEndDate(); + DateFormat indfm = null; + + if (startOrScheduleDate != null || finishDate != null) { + indfm = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss", userLocale); + } + + if (startOrScheduleDate != null) { + Date tzStartDate = DateUtil.convertToTimeZoneFromDefault(user.getTimeZone(), startOrScheduleDate); + responseJSON.put("startDate", + indfm.format(tzStartDate) + " " + user.getTimeZone().getDisplayName(userLocale)); + } + + if (finishDate != null) { + Date tzFinishDate = DateUtil.convertToTimeZoneFromDefault(user.getTimeZone(), finishDate); + responseJSON.put("finishDate", + indfm.format(tzFinishDate) + " " + user.getTimeZone().getDisplayName(userLocale)); + } + + List contributeActivities = getContributeActivities(lessonId, false); + if (contributeActivities != null) { + responseJSON.set("contributeActivities", JsonUtil.readArray(contributeActivities)); + } + + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + @RequestMapping("/getLessonChartData") + @ResponseBody + public String getLessonChartData(HttpServletRequest request, HttpServletResponse response) throws IOException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + + Integer possibleLearnersCount = getLessonService().getCountLessonLearners(lessonId, null); + Integer completedLearnersCount = getMonitoringService().getCountLearnersCompletedLesson(lessonId); + Integer startedLearnersCount = getLessonService().getCountActiveLessonLearners(lessonId) + - completedLearnersCount; + Integer notCompletedLearnersCount = possibleLearnersCount - completedLearnersCount - startedLearnersCount; + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + ObjectNode notStartedJSON = JsonNodeFactory.instance.objectNode(); + notStartedJSON.put("name", getMessageService().getMessage("lesson.chart.not.completed")); + notStartedJSON.put("value", Math.round(notCompletedLearnersCount.doubleValue() / possibleLearnersCount * 100)); + responseJSON.withArray("data").add(notStartedJSON); + + ObjectNode startedJSON = JsonNodeFactory.instance.objectNode(); + startedJSON.put("name", getMessageService().getMessage("lesson.chart.started")); + startedJSON.put("value", Math.round((startedLearnersCount.doubleValue()) / possibleLearnersCount * 100)); + responseJSON.withArray("data").add(startedJSON); + + ObjectNode completedJSON = JsonNodeFactory.instance.objectNode(); + completedJSON.put("name", getMessageService().getMessage("lesson.chart.completed")); + completedJSON.put("value", Math.round(completedLearnersCount.doubleValue() / possibleLearnersCount * 100)); + responseJSON.withArray("data").add(completedJSON); + + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + /** + * Produces data for Sequence tab in Monitor. + */ + @RequestMapping("/getLessonProgress") + @ResponseBody + public String getLessonProgress(HttpServletRequest request, HttpServletResponse response) throws IOException { + + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Integer monitorUserId = getUserId(); + if (!getSecurityService().isLessonMonitor(lessonId, monitorUserId, "get lesson progress", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + Integer searchedLearnerId = WebUtil.readIntParam(request, "searchedLearnerId", true); + + Lesson lesson = getLessonService().getLesson(lessonId); + LearningDesign learningDesign = lesson.getLearningDesign(); + String contentFolderId = learningDesign.getContentFolderID(); + + Set activities = new HashSet<>(); + // filter activities that are interesting for further processing + for (Activity activity : (Set) learningDesign.getActivities()) { + if (activity.isSequenceActivity()) { + // skip sequence activities as they are just for grouping + continue; + } + // get real activity object, not proxy + activities.add(getMonitoringService().getActivityById(activity.getActivityId())); + } + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + List contributeActivities = getContributeActivities(lessonId, true); + if (contributeActivities != null) { + responseJSON.set("contributeActivities", JsonUtil.readArray(contributeActivities)); + } + + // check if the searched learner has started the lesson + LearnerProgress searchedLearnerProgress = null; + if (searchedLearnerId != null) { + searchedLearnerProgress = getLessonService().getUserProgressForLesson(searchedLearnerId, lessonId); + responseJSON.put("searchedLearnerFound", searchedLearnerProgress != null); + } + + // Fetch number of learners at each activity + ArrayList activityIds = new ArrayList<>(); + Set leaders = new TreeSet<>(); + for (Activity activity : activities) { + activityIds.add(activity.getActivityId()); + // find leaders from Leader Selection Tool + if (activity.isToolActivity()) { + ToolActivity toolActivity = (ToolActivity) activity; + if (ILamsToolService.LEADER_SELECTION_TOOL_SIGNATURE + .equals(toolActivity.getTool().getToolSignature())) { + leaders.addAll(getToolService().getLeaderUserId(activity.getActivityId())); + } + } + } + + Map learnerCounts = getMonitoringService() + .getCountLearnersCurrentActivities(activityIds.toArray(new Long[activityIds.size()])); + + ArrayNode activitiesJSON = JsonNodeFactory.instance.arrayNode(); + for (Activity activity : activities) { + Long activityId = activity.getActivityId(); + ObjectNode activityJSON = JsonNodeFactory.instance.objectNode(); + activityJSON.put("id", activityId); + activityJSON.put("uiid", activity.getActivityUIID()); + activityJSON.put("title", activity.getTitle()); + activityJSON.put("type", activity.getActivityTypeId()); + + Activity parentActivity = activity.getParentActivity(); + if (activity.isBranchingActivity()) { + BranchingActivity ba = (BranchingActivity) monitoringService.getActivityById(activity.getActivityId()); + activityJSON.put("x", MonitoringController.getActivityCoordinate(ba.getStartXcoord())); + activityJSON.put("y", MonitoringController.getActivityCoordinate(ba.getStartYcoord())); + } else if (activity.isOptionsWithSequencesActivity()) { + activityJSON.put("x", MonitoringController + .getActivityCoordinate(((OptionsWithSequencesActivity) activity).getStartXcoord())); + activityJSON.put("y", MonitoringController + .getActivityCoordinate(((OptionsWithSequencesActivity) activity).getStartYcoord())); + } else if ((parentActivity != null) && (parentActivity.isOptionsActivity() + || parentActivity.isParallelActivity() || parentActivity.isFloatingActivity())) { + // Optional Activity children had coordinates relative to parent + activityJSON.put("x", MonitoringController.getActivityCoordinate(parentActivity.getXcoord()) + + MonitoringController.getActivityCoordinate(activity.getXcoord())); + activityJSON.put("y", MonitoringController.getActivityCoordinate(parentActivity.getYcoord()) + + MonitoringController.getActivityCoordinate(activity.getYcoord())); + } else { + activityJSON.put("x", MonitoringController.getActivityCoordinate(activity.getXcoord())); + activityJSON.put("y", MonitoringController.getActivityCoordinate(activity.getYcoord())); + } + + String monitorUrl = getMonitoringService().getActivityMonitorURL(lessonId, activityId, contentFolderId, + monitorUserId); + if (monitorUrl != null && !activity.isBranchingActivity()) { + // whole activity monitor URL + activityJSON.put("url", monitorUrl); + } + + // find few latest users and count of all users for each activity + int learnerCount = learnerCounts.get(activityId); + if (!activity.isBranchingActivity() && !activity.isOptionsWithSequencesActivity()) { + List latestLearners = getMonitoringService().getLearnersLatestByActivity(activity.getActivityId(), + MonitoringController.LATEST_LEARNER_PROGRESS_ACTIVITY_DISPLAY_LIMIT, null); + + // insert leaders as first of learners + for (Long leaderId : leaders) { + for (User learner : latestLearners) { + if (learner.getUserId().equals(leaderId.intValue())) { + latestLearners = MonitoringController.insertHighlightedLearner(learner, latestLearners, + MonitoringController.LATEST_LEARNER_PROGRESS_ACTIVITY_DISPLAY_LIMIT); + break; + } + } + } + + // insert the searched learner as the first one + if ((searchedLearnerProgress != null) && (searchedLearnerProgress.getCurrentActivity() != null) + && activity.getActivityId() + .equals(searchedLearnerProgress.getCurrentActivity().getActivityId())) { + // put the searched learner in front + latestLearners = MonitoringController.insertHighlightedLearner(searchedLearnerProgress.getUser(), + latestLearners, MonitoringController.LATEST_LEARNER_PROGRESS_ACTIVITY_DISPLAY_LIMIT); + } + + // parse learners into JSON format + if (!latestLearners.isEmpty()) { + ArrayNode learnersJSON = JsonNodeFactory.instance.arrayNode(); + for (User learner : latestLearners) { + ObjectNode userJSON = WebUtil.userToJSON(learner); + if (leaders.contains(learner.getUserId().longValue())) { + userJSON.put("leader", true); + } + learnersJSON.add(userJSON); + } + + activityJSON.set("learners", learnersJSON); + } + } + activityJSON.put("learnerCount", learnerCount); + + activitiesJSON.add(activityJSON); + } + responseJSON.set("activities", activitiesJSON); + + // find learners who completed the lesson + List completedLearners = getMonitoringService().getLearnersLatestCompleted(lessonId, + MonitoringController.LATEST_LEARNER_PROGRESS_LESSON_DISPLAY_LIMIT, null); + Integer completedLearnerCount = null; + if (completedLearners.size() < MonitoringController.LATEST_LEARNER_PROGRESS_LESSON_DISPLAY_LIMIT) { + completedLearnerCount = completedLearners.size(); + } else { + completedLearnerCount = getMonitoringService().getCountLearnersCompletedLesson(lessonId); + } + responseJSON.put("completedLearnerCount", completedLearnerCount); + + if ((searchedLearnerProgress != null) && searchedLearnerProgress.isComplete()) { + // put the searched learner in front + completedLearners = MonitoringController.insertHighlightedLearner(searchedLearnerProgress.getUser(), + completedLearners, MonitoringController.LATEST_LEARNER_PROGRESS_LESSON_DISPLAY_LIMIT); + } + for (User learner : completedLearners) { + ObjectNode learnerJSON = WebUtil.userToJSON(learner); + // no more details are needed for learners who completed the lesson + responseJSON.withArray("completedLearners").add(learnerJSON); + } + + responseJSON.put("numberPossibleLearners", getLessonService().getCountLessonLearners(lessonId, null)); + + // on first fetch get transitions metadata so Monitoring can set their SVG elems IDs + if (WebUtil.readBooleanParam(request, "getTransitions", false)) { + ArrayNode transitions = JsonNodeFactory.instance.arrayNode(); + for (Transition transition : (Set) learningDesign.getTransitions()) { + ObjectNode transitionJSON = JsonNodeFactory.instance.objectNode(); + transitionJSON.put("uiid", transition.getTransitionUIID()); + transitionJSON.put("fromID", transition.getFromActivity().getActivityId()); + transitionJSON.put("toID", transition.getToActivity().getActivityId()); + + transitions.add(transitionJSON); + } + responseJSON.set("transitions", transitions); + } + + response.setContentType("application/json;charset=utf-8"); + + return responseJSON.toString(); + } + + /** + * Gives suggestions when a Monitor searches for Learners and staff members. + */ + @RequestMapping("/autocomplete") + @ResponseBody + public String autocomplete(HttpServletRequest request, HttpServletResponse response) throws Exception { + + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + if (!getSecurityService().isLessonMonitor(lessonId, getUserId(), "autocomplete in monitoring", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + String searchPhrase = request.getParameter("term"); + boolean isOrganisationSearch = WebUtil.readStrParam(request, "scope").equalsIgnoreCase("organisation"); + + Collection users = null; + if (isOrganisationSearch) { + // search for Learners in the organisation + Map result = getLessonService().getUsersWithLessonParticipation(lessonId, Role.LEARNER, + searchPhrase, MonitoringController.USER_PAGE_SIZE, null, true); + users = result.keySet(); + } else { + // search for Learners in the lesson + users = getLessonService().getLessonLearners(lessonId, searchPhrase, MonitoringController.USER_PAGE_SIZE, + null, true); + } + + ArrayNode responseJSON = JsonNodeFactory.instance.arrayNode(); + for (User user : users) { + ObjectNode userJSON = JsonNodeFactory.instance.objectNode(); + userJSON.put("label", user.getFirstName() + " " + user.getLastName() + " " + user.getLogin()); + userJSON.put("value", user.getUserId()); + + responseJSON.add(userJSON); + } + + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + /** + * Checks if activity A is before activity B in a sequence. + */ + @RequestMapping("/isActivityPreceding") + @ResponseBody + public String isActivityPreceding(HttpServletRequest request, HttpServletResponse response) throws Exception { + long activityAid = WebUtil.readLongParam(request, "activityA"); + long activityBid = WebUtil.readLongParam(request, "activityB"); + boolean result = false; + + IMonitoringService monitoringService = MonitoringServiceProxy + .getMonitoringService(applicationContext.getServletContext()); + Activity precedingActivity = monitoringService.getActivityById(activityBid); + + // move back an look for activity A + while (!result && (precedingActivity != null)) { + if (precedingActivity.getTransitionTo() != null) { + precedingActivity = precedingActivity.getTransitionTo().getFromActivity(); + } else if (precedingActivity.getParentActivity() != null) { + // this is a nested activity; move up + precedingActivity = precedingActivity.getParentActivity(); + continue; + } else { + precedingActivity = null; + } + + if ((precedingActivity != null) && !precedingActivity.isSequenceActivity()) { + if (precedingActivity.getActivityId().equals(activityAid)) { + // found it + result = true; + } else if (precedingActivity.isComplexActivity()) { + // check descendants of a complex activity + ComplexActivity complexActivity = (ComplexActivity) monitoringService + .getActivityById(precedingActivity.getActivityId()); + if (containsActivity(complexActivity, activityAid, monitoringService)) { + result = true; + } + } + } + } + + response.setContentType("text/plain;charset=utf-8"); + response.getWriter().write(Boolean.toString(result)); + return null; + } + + /** + * Checks if a complex activity or its descendats contain an activity with the given ID. + */ + @SuppressWarnings("unchecked") + private boolean containsActivity(ComplexActivity complexActivity, long targetActivityId, + IMonitoringService monitoringService) { + for (Activity childActivity : (Set) complexActivity.getActivities()) { + if (childActivity.getActivityId().equals(targetActivityId)) { + return true; + } + if (childActivity.isComplexActivity()) { + ComplexActivity childComplexActivity = (ComplexActivity) monitoringService + .getActivityById(childActivity.getActivityId()); + if (containsActivity(childComplexActivity, targetActivityId, monitoringService)) { + return true; + } + } + } + return false; + } + + @RequestMapping("/startPreviewLesson") + public String startPreviewLesson(HttpServletRequest request, HttpServletResponse response) throws IOException { + Integer userID = getUserId(); + long lessonID = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + + try { + getMonitoringService().createPreviewClassForLesson(userID, lessonID); + getMonitoringService().startLesson(lessonID, getUserId()); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user is not a monitor in the lesson"); + } + return null; + } + + @RequestMapping("/startLiveEdit") + @ResponseBody + public String startLiveEdit(HttpServletRequest request, HttpServletResponse response) + throws LearningDesignException, UserException, IOException { + + long learningDesignId = WebUtil.readLongParam(request, CentralConstants.PARAM_LEARNING_DESIGN_ID); + LearningDesign learningDesign = (LearningDesign) getUserManagementService().findById(LearningDesign.class, + learningDesignId); + if (learningDesign.getLessons().isEmpty()) { + throw new InvalidParameterException( + "There are no lessons associated with learning design: " + learningDesignId); + } + Integer organisationID = ((Lesson) learningDesign.getLessons().iterator().next()).getOrganisation() + .getOrganisationId(); + Integer userID = getUserId(); + if (!getSecurityService().hasOrgRole(organisationID, userID, new String[] { Role.AUTHOR }, "start live edit", + false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not an author in the organisation"); + return null; + } + + IAuthoringService authoringService = MonitoringServiceProxy + .getAuthoringService(applicationContext.getServletContext()); + + if (authoringService.setupEditOnFlyLock(learningDesignId, userID)) { + authoringService.setupEditOnFlyGate(learningDesignId, userID); + } else { + response.getWriter().write("Someone else is editing the design at the moment."); + } + + return null; + } + + private ILogEventService getLogEventService() { + if (MonitoringController.logEventService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + MonitoringController.logEventService = (ILogEventService) ctx.getBean("logEventService"); + } + return MonitoringController.logEventService; + } + + private ILessonService getLessonService() { + if (MonitoringController.lessonService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + MonitoringController.lessonService = (ILessonService) ctx.getBean("lessonService"); + } + return MonitoringController.lessonService; + } + + private IMonitoringService getMonitoringService() { + if (MonitoringController.monitoringService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + MonitoringController.monitoringService = (IMonitoringService) ctx.getBean("monitoringService"); + } + return MonitoringController.monitoringService; + } + + private ISecurityService getSecurityService() { + if (MonitoringController.securityService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + MonitoringController.securityService = (ISecurityService) ctx.getBean("securityService"); + } + return MonitoringController.securityService; + } + + private IUserManagementService getUserManagementService() { + if (MonitoringController.userManagementService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + MonitoringController.userManagementService = (IUserManagementService) ctx.getBean("userManagementService"); + } + return MonitoringController.userManagementService; + } + + private ILearnerService getLearnerService() { + if (MonitoringController.learnerService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + MonitoringController.learnerService = (ILearnerService) ctx.getBean("learnerService"); + } + return MonitoringController.learnerService; + } + + private MessageService getMessageService() { + if (MonitoringController.messageService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + MonitoringController.messageService = (MessageService) ctx.getBean("monitoringMessageService"); + } + return MonitoringController.messageService; + } + + private ILamsToolService getToolService() { + if (MonitoringController.toolService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(applicationContext.getServletContext()); + MonitoringController.toolService = (ILamsToolService) ctx.getBean("lamsToolService"); + } + return MonitoringController.toolService; + } + + /** + * Set whether or not the presence available button is available in learner. Expects parameters lessonID and + * presenceAvailable. + */ + @RequestMapping("/presenceAvailable") + public String presenceAvailable(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + Long lessonID = new Long(WebUtil.readLongParam(request, "lessonID")); + Integer userID = getUserId(); + Boolean presenceAvailable = WebUtil.readBooleanParam(request, "presenceAvailable", false); + + try { + getMonitoringService().togglePresenceAvailable(lessonID, userID, presenceAvailable); + + if (!presenceAvailable) { + getMonitoringService().togglePresenceImAvailable(lessonID, userID, false); + } + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + } + return null; + } + + /** + * Set whether or not the presence available button is available in learner. Expects parameters lessonID and + * presenceImAvailable. + */ + @RequestMapping("/presenceImAvailable") + public String presenceImAvailable(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + Long lessonID = new Long(WebUtil.readLongParam(request, "lessonID")); + Integer userID = getUserId(); + Boolean presenceImAvailable = WebUtil.readBooleanParam(request, "presenceImAvailable", false); + + try { + getMonitoringService().togglePresenceImAvailable(lessonID, userID, presenceImAvailable); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + } + return null; + } + + /** + * Set whether or not the activity scores / gradebook values are shown to the learner at the end of the lesson. + * Expects parameters lessonID and presenceAvailable. + */ + @RequestMapping("/gradebookOnComplete") + public String gradebookOnComplete(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + Long lessonID = new Long(WebUtil.readLongParam(request, "lessonID")); + Integer userID = getUserId(); + Boolean gradebookOnComplete = WebUtil.readBooleanParam(request, "gradebookOnComplete", false); + + try { + getMonitoringService().toggleGradebookOnComplete(lessonID, userID, gradebookOnComplete); + } catch (SecurityException e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + } + return null; + } + + /** Open Time Chart display */ + @RequestMapping("/viewTimeChart") + public String viewTimeChart(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + try { + + long lessonID = WebUtil.readLongParam(request, "lessonID"); + + // check monitor privledges + if (!getSecurityService().isLessonMonitor(lessonID, getUserId(), "open time chart", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); + return null; + } + + request.setAttribute("lessonID", lessonID); + request.setAttribute("learnerID", WebUtil.readLongParam(request, "learnerID", true)); + + } catch (Exception e) { + request.setAttribute("errorName", "MonitoringAction"); + request.setAttribute("errorMessage", e.getMessage()); + + return "systemErrorContent"; + } + + return "timeChart"; + } + + /** + * Creates a list of users out of string with comma-delimited user IDs. + */ + private List parseUserList(HttpServletRequest request, String paramName, Collection users) { + String userIdList = request.getParameter(paramName); + String[] userIdArray = userIdList.split(","); + List result = new ArrayList<>(userIdArray.length); + + for (User user : users) { + Integer userId = user.getUserId(); + for (String includeId : userIdArray) { + if (userId.toString().equals(includeId)) { + result.add(user); + break; + } + } + } + + return result; + } + + @SuppressWarnings("unchecked") + private List getContributeActivities(Long lessonId, boolean skipCompletedBranching) { + List contributeActivities = getMonitoringService().getAllContributeActivityDTO(lessonId); + Lesson lesson = getLessonService().getLesson(lessonId); + + if (contributeActivities != null) { + List resultContributeActivities = new ArrayList<>(); + for (ContributeActivityDTO contributeActivity : contributeActivities) { + if (contributeActivity.getContributeEntries() != null) { + Iterator entryIterator = contributeActivity + .getContributeEntries().iterator(); + while (entryIterator.hasNext()) { + ContributeActivityDTO.ContributeEntry contributeEntry = entryIterator.next(); + + // extra filtering for chosen branching: do not show in Sequence tab if all users were assigned + if (skipCompletedBranching + && ContributionTypes.CHOSEN_BRANCHING.equals(contributeEntry.getContributionType())) { + Set learners = new HashSet<>(lesson.getLessonClass().getLearners()); + ChosenBranchingActivity branching = (ChosenBranchingActivity) getMonitoringService() + .getActivityById(contributeActivity.getActivityID()); + for (SequenceActivity branch : (Set) branching.getActivities()) { + Group group = branch.getSoleGroupForBranch(); + if (group != null) { + learners.removeAll(group.getUsers()); + } + } + contributeEntry.setIsComplete(learners.isEmpty()); + } + + if (!contributeEntry.getIsRequired() || contributeEntry.getIsComplete()) { + entryIterator.remove(); + } + } + + if (!contributeActivity.getContributeEntries().isEmpty()) { + resultContributeActivities.add(contributeActivity); + } + } + } + return resultContributeActivities; + } + return null; + } + + private static int getActivityCoordinate(Integer coord) { + return (coord == null) || (coord < 0) ? ObjectExtractor.DEFAULT_COORD : coord; + } + + /** + * Puts the searched learner in front of other learners in the list. + */ + private static List insertHighlightedLearner(User searchedLearner, List latestLearners, int limit) { + latestLearners.remove(searchedLearner); + LinkedList updatedLatestLearners = new LinkedList<>(latestLearners); + updatedLatestLearners.addFirst(searchedLearner); + if (updatedLatestLearners.size() > limit) { + updatedLatestLearners.removeLast(); + } + return updatedLatestLearners; + } +} \ No newline at end of file Fisheye: Tag 6e7ab7890111cde84e6557c3507a1e9d3682e318 refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/SequenceAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/SequenceController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/SequenceController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/SequenceController.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,94 @@ +/**************************************************************** + * 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.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.lamsfoundation.lams.learningdesign.SequenceActivity; +import org.lamsfoundation.lams.monitoring.service.IMonitoringService; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.util.WebUtil; +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.RequestMapping; + +/** + * The action servlet that provides the support for the Sequence activities. At present, this is only a basic view + * screen that lists the user's in the sequence. + * + * + * + * + * @author Fiona Malikoff + */ +@Controller +public class SequenceController { + + @Autowired + @Qualifier("monitoringService") + private IMonitoringService monitoringService; + + public static final String VIEW_SEQUENCE = "viewSequence"; + public static final String PARAM_LEARNERS = "learners"; + public static final String PARAM_LOCAL_FILES = "localFiles"; + + /** + * Display the view screen. + */ + @RequestMapping("/sequence") + public String viewSequence(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + long activityId = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + + SequenceActivity activity = (SequenceActivity) monitoringService.getActivityById(activityId, + SequenceActivity.class); + return viewSequence(activity, lessonId, false, request, monitoringService); + } + + protected String viewSequence(SequenceActivity activity, Long lessonId, boolean useLocalFiles, + HttpServletRequest request, IMonitoringService monitoringService) throws IOException, ServletException { + + // in general the progress engine expects the activity and lesson id to be in the request, + // so follow that standard. + request.setAttribute(AttributeNames.PARAM_ACTIVITY_ID, activity.getActivityId()); + request.setAttribute(AttributeNames.PARAM_LESSON_ID, lessonId); + request.setAttribute(AttributeNames.PARAM_TITLE, activity.getTitle()); + request.setAttribute(SequenceController.PARAM_LOCAL_FILES, useLocalFiles); + + // only show the group names if this is a group based branching activity - the names + // are meaningless for chosen and tool based branching + List learners = monitoringService.getLearnersAttemptedOrCompletedActivity(activity); + request.setAttribute(SequenceController.PARAM_LEARNERS, learners); + return "viewSequence"; + } + +} Fisheye: Tag 6e7ab7890111cde84e6557c3507a1e9d3682e318 refers to a dead (removed) revision in file `lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/TblMonitoringAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/TblMonitoringController.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/TblMonitoringController.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/TblMonitoringController.java (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,470 @@ +package org.lamsfoundation.lams.monitoring.web; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.lamsfoundation.lams.gradebook.GradebookUserActivity; +import org.lamsfoundation.lams.gradebook.service.IGradebookService; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.ActivityOrderComparator; +import org.lamsfoundation.lams.learningdesign.BranchingActivity; +import org.lamsfoundation.lams.learningdesign.ComplexActivity; +import org.lamsfoundation.lams.learningdesign.ContributionTypes; +import org.lamsfoundation.lams.learningdesign.GateActivity; +import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.Grouping; +import org.lamsfoundation.lams.learningdesign.GroupingActivity; +import org.lamsfoundation.lams.learningdesign.PermissionGateActivity; +import org.lamsfoundation.lams.learningdesign.SequenceActivity; +import org.lamsfoundation.lams.learningdesign.ToolActivity; +import org.lamsfoundation.lams.learningdesign.Transition; +import org.lamsfoundation.lams.learningdesign.dao.IActivityDAO; +import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.monitoring.dto.ContributeActivityDTO; +import org.lamsfoundation.lams.monitoring.dto.PermissionGateDTO; +import org.lamsfoundation.lams.monitoring.dto.TblGroupDTO; +import org.lamsfoundation.lams.monitoring.dto.TblUserDTO; +import org.lamsfoundation.lams.monitoring.service.IMonitoringService; +import org.lamsfoundation.lams.tool.ToolSession; +import org.lamsfoundation.lams.tool.service.ILamsCoreToolService; +import org.lamsfoundation.lams.tool.service.ILamsToolService; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.util.CentralConstants; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.action.LamsDispatchAction; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Displays TBL monitor. + * + * @author Andrey Balan + */ +public class TblMonitoringAction extends LamsDispatchAction { + + private static Logger log = Logger.getLogger(TblMonitoringAction.class); + + private static ILessonService lessonService; + private static IMonitoringService monitoringService; + private static ILamsCoreToolService coreToolService; + private static ILamsToolService toolService; + private static IActivityDAO activityDAO; + private static IGradebookService gradebookService; + + /** + * Displays addStudent page. + */ + @Override + public ActionForward unspecified(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws Exception { + initServices(); + + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Lesson lesson = lessonService.getLesson(lessonId); + request.setAttribute("lesson", lesson); + request.setAttribute("totalLearnersNumber", lesson.getAllLearners().size()); + + List lessonActivities = getLessonActivities(lesson); + setupAvailableActivityTypes(request, lessonActivities); + return mapping.findForward("tblmonitor"); + } + + /** + * Shows Teams page + */ + public ActionForward teams(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Lesson lesson = lessonService.getLesson(lessonId); + + List lessonActivities = getLessonActivities(lesson); + setupAvailableActivityTypes(request, lessonActivities); + boolean isScratchieAvailable = (request.getAttribute("isScratchieAvailable") != null) + && ((Boolean) request.getAttribute("isScratchieAvailable")); + boolean isIraMcqAvailable = (request.getAttribute("isIraMcqAvailable") != null) + && ((Boolean) request.getAttribute("isIraMcqAvailable")); + boolean isIraAssessmentAvailable = (request.getAttribute("isIraAssessmentAvailable") != null) + && ((Boolean) request.getAttribute("isIraAssessmentAvailable")); + Long iraToolActivityId = request.getAttribute("iraToolActivityId") == null ? null + : (Long) request.getAttribute("iraToolActivityId"); + Long traToolActivityId = request.getAttribute("traToolActivityId") == null ? null + : (Long) request.getAttribute("traToolActivityId"); + Long leaderselectionToolActivityId = request.getAttribute("leaderselectionToolActivityId") == null ? null + : (Long) request.getAttribute("leaderselectionToolActivityId"); + + //get all mcq and assessment scores + List iraGradebookUserActivities = new LinkedList<>(); + List traGradebookUserActivities = new LinkedList<>(); + if (isIraMcqAvailable || isIraAssessmentAvailable) { + iraGradebookUserActivities = gradebookService.getGradebookUserActivities(iraToolActivityId); + } + if (isScratchieAvailable) { + traGradebookUserActivities = gradebookService.getGradebookUserActivities(traToolActivityId); + } + + Set leaderUserIds = leaderselectionToolActivityId == null ? new HashSet() + : toolService.getLeaderUserId(leaderselectionToolActivityId); + + GroupingActivity groupingActivity = getGroupingActivity(lesson); + Grouping grouping = groupingActivity == null ? null : groupingActivity.getCreateGrouping(); + Set groups = grouping == null ? null : grouping.getGroups(); + + Set groupDtos = new TreeSet(); + if (groups != null) { + for (Group group : groups) { + TblGroupDTO groupDto = new TblGroupDTO(group); + groupDtos.add(groupDto); + + if (group.getUsers() != null) { + for (User user : group.getUsers()) { + TblUserDTO userDto = new TblUserDTO(user.getUserDTO()); + groupDto.getUserList().add(userDto); + + //set up all user leaders + if (leaderUserIds.contains(new Long(user.getUserId()))) { + userDto.setGroupLeader(true); + groupDto.setGroupLeader(userDto); + } + + if (isIraMcqAvailable || isIraAssessmentAvailable) { + //find according iraGradebookUserActivity + for (GradebookUserActivity iraGradebookUserActivity : iraGradebookUserActivities) { + if (iraGradebookUserActivity.getLearner().getUserId().equals(user.getUserId())) { + userDto.setIraScore(iraGradebookUserActivity.getMark()); + break; + } + } + } + + if (isScratchieAvailable) { + //find according traGradebookUserActivity + for (GradebookUserActivity traGradebookUserActivity : traGradebookUserActivities) { + if (traGradebookUserActivity.getLearner().getUserId().equals(user.getUserId())) { + //we set traScore multiple times, but it's doesn't matter + groupDto.setTraScore(traGradebookUserActivity.getMark()); + break; + } + } + } + } + } + } + } + request.setAttribute("groupDtos", groupDtos); + + return mapping.findForward("teams"); + } + + /** + * Shows Gates page + */ + public ActionForward gates(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + + List permissionGates = new ArrayList(); + + List contributeActivities = monitoringService.getAllContributeActivityDTO(lessonId); + if (contributeActivities != null) { + for (ContributeActivityDTO contributeActivity : contributeActivities) { + + if (contributeActivity.getContributeEntries() != null) { + + //check if there is any persmission gates entries + for (ContributeActivityDTO.ContributeEntry contributeEntry : contributeActivity + .getContributeEntries()) { + if (ContributionTypes.PERMISSION_GATE.equals(contributeEntry.getContributionType())) { + + Long activityId = contributeActivity.getActivityID(); + Activity activity = monitoringService.getActivityById(activityId); + PermissionGateDTO gateDto = new PermissionGateDTO((PermissionGateActivity) activity); + + gateDto.setUrl(contributeEntry.getURL()); + gateDto.setComplete(contributeEntry.getIsComplete()); + + int waitingLearnersCount = lessonService.getCountLearnersHaveAttemptedActivity(activity); + gateDto.setWaitingLearnersCount(waitingLearnersCount); + + permissionGates.add(gateDto); + break; + } + } + } + } + } + + request.setAttribute("permissionGates", permissionGates); + return mapping.findForward("gates"); + } + + /** + * Shows forum page + */ + public ActionForward forum(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + long forumActivityId = WebUtil.readLongParam(request, "activityId"); + ToolActivity forumActivity = (ToolActivity) monitoringService.getActivityById(forumActivityId); + + int attemptedLearnersNumber = lessonService.getCountLearnersHaveAttemptedActivity(forumActivity); + request.setAttribute("attemptedLearnersNumber", attemptedLearnersNumber); + + Set toolSessions = forumActivity.getToolSessions(); + request.setAttribute("toolSessions", toolSessions); + + return mapping.findForward("forum"); + } + + /** + * Shows peerreview page + */ + public ActionForward peerreview(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + long peerreviewActivityId = WebUtil.readLongParam(request, "activityId"); + ToolActivity peerreviewActivity = (ToolActivity) monitoringService.getActivityById(peerreviewActivityId); + + int attemptedLearnersNumber = lessonService.getCountLearnersHaveAttemptedActivity(peerreviewActivity); + request.setAttribute("attemptedLearnersNumber", attemptedLearnersNumber); + + Set toolSessions = peerreviewActivity.getToolSessions(); + request.setAttribute("toolSessions", toolSessions); + + return mapping.findForward("peerreview"); + } + + /** + * Shows sequence diagram page + */ + public ActionForward sequence(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Lesson lesson = lessonService.getLesson(lessonId); + request.setAttribute("lesson", lesson); + return mapping.findForward("sequence"); + } + + /** + * Returns lesson activities sorted by the learning design order. + */ + @SuppressWarnings("unchecked") + private List getLessonActivities(Lesson lesson) { + + /* + * Hibernate CGLIB is failing to load the first activity in the sequence as a ToolActivity for some mysterious + * reason Causes a ClassCastException when you try to cast it, even if it is a ToolActivity. + * + * THIS IS A HACK to retrieve the first tool activity manually so it can be cast as a ToolActivity - if it is + * one + */ + Activity firstActivity = activityDAO + .getActivityByActivityId(lesson.getLearningDesign().getFirstActivity().getActivityId()); + List activities = new ArrayList(); + sortActivitiesByLearningDesignOrder(firstActivity, activities); + + return activities; + } + + /** + * Sort all activities by the learning design order. + * + * @param activity + * @param sortedActivities + */ + private void sortActivitiesByLearningDesignOrder(Activity activity, List sortedActivities) { + sortedActivities.add(activity); + + //in case of branching activity - add all activities based on their orderId + if (activity.isBranchingActivity()) { + BranchingActivity branchingActivity = (BranchingActivity) activity; + Set sequenceActivities = new TreeSet(new ActivityOrderComparator()); + sequenceActivities.addAll(branchingActivity.getActivities()); + for (Activity sequenceActivityNotInitialized : sequenceActivities) { + SequenceActivity sequenceActivity = (SequenceActivity) monitoringService + .getActivityById(sequenceActivityNotInitialized.getActivityId()); + Set childActivities = new TreeSet(new ActivityOrderComparator()); + childActivities.addAll(sequenceActivity.getActivities()); + + //add one by one in order to initialize all activities + for (Activity childActivity : childActivities) { + Activity activityInit = monitoringService.getActivityById(childActivity.getActivityId()); + sortedActivities.add(activityInit); + } + } + + // In case of complex activity (parallel, help or optional activity) add all its children activities. + // They will be sorted by orderId + } else if (activity.isComplexActivity()) { + ComplexActivity complexActivity = (ComplexActivity) activity; + Set childActivities = new TreeSet(new ActivityOrderComparator()); + childActivities.addAll(complexActivity.getActivities()); + + // add one by one in order to initialize all activities + for (Activity childActivity : childActivities) { + Activity activityInit = monitoringService.getActivityById(childActivity.getActivityId()); + sortedActivities.add(activityInit); + } + } + + Transition transitionFrom = activity.getTransitionFrom(); + if (transitionFrom != null) { + // query activity from DB as transition holds only proxied activity object + Long nextActivityId = transitionFrom.getToActivity().getActivityId(); + Activity nextActivity = monitoringService.getActivityById(nextActivityId); + + sortActivitiesByLearningDesignOrder(nextActivity, sortedActivities); + } + } + + private GroupingActivity getGroupingActivity(Lesson lesson) { + Set activities = new TreeSet(); + + /* + * Hibernate CGLIB is failing to load the first activity in the sequence as a ToolActivity for some mysterious + * reason Causes a ClassCastException when you try to cast it, even if it is a ToolActivity. + * + * THIS IS A HACK to retrieve the first tool activity manually so it can be cast as a ToolActivity - if it is + * one + */ + Activity firstActivity = monitoringService + .getActivityById(lesson.getLearningDesign().getFirstActivity().getActivityId()); + activities.add(firstActivity); + activities.addAll(lesson.getLearningDesign().getActivities()); + + for (Activity activity : activities) { + if (activity instanceof GroupingActivity) { + return (GroupingActivity) activity; + } + } + + return null; + } + + private void setupAvailableActivityTypes(HttpServletRequest request, List activities) { + + //check if there is Scratchie activity. It's used only in case of LKC TBL monitoring, when all assessment are treated as AEs + boolean isScratchieAvailable = false; + for (Activity activity : activities) { + if (activity instanceof ToolActivity) { + ToolActivity toolActivity = (ToolActivity) activity; + String toolSignature = toolActivity.getTool().getToolSignature(); + if (CentralConstants.TOOL_SIGNATURE_SCRATCHIE.equals(toolSignature)) { + isScratchieAvailable = true; + break; + } + } + } + + boolean scratchiePassed = false; + boolean iraPassed = false; + String assessmentToolContentIds = ""; + String assessmentActivityTitles = ""; + for (Activity activity : activities) { + if (activity instanceof ToolActivity) { + ToolActivity toolActivity = (ToolActivity) activity; + String toolSignature = toolActivity.getTool().getToolSignature(); + Long toolContentId = toolActivity.getToolContentId(); + Long toolActivityId = toolActivity.getActivityId(); + String toolTitle = toolActivity.getTitle(); + + //count only the first MCQ or Assessmnet as iRA + if (!iraPassed && (CentralConstants.TOOL_SIGNATURE_MCQ.equals(toolSignature) + || isScratchieAvailable && CentralConstants.TOOL_SIGNATURE_ASSESSMENT.equals(toolSignature))) { + iraPassed = true; + if (CentralConstants.TOOL_SIGNATURE_MCQ.equals(toolSignature)) { + request.setAttribute("isIraMcqAvailable", true); + + } else { + request.setAttribute("isIraAssessmentAvailable", true); + } + request.setAttribute("iraToolContentId", toolContentId); + request.setAttribute("iraToolActivityId", toolActivityId); + + continue; + } + + //aes are counted only after Scratchie activity, or for LKC TBL monitoring + if ((scratchiePassed || !isScratchieAvailable) && CentralConstants.TOOL_SIGNATURE_ASSESSMENT.equals(toolSignature)) { + request.setAttribute("isAeAvailable", true); + //prepare assessment details to be passed to Assessment tool + assessmentToolContentIds += toolContentId + ","; + assessmentActivityTitles += toolTitle + "\\,"; + + } else if (CentralConstants.TOOL_SIGNATURE_FORUM.equals(toolSignature)) { + request.setAttribute("isForumAvailable", true); + request.setAttribute("forumActivityId", toolActivityId); + + } else if (CentralConstants.TOOL_SIGNATURE_PEER_REVIEW.equals(toolSignature)) { + request.setAttribute("isPeerreviewAvailable", true); + request.setAttribute("peerreviewToolContentId", toolContentId); + + //tRA is the first scratchie activity + } else if (!scratchiePassed && CentralConstants.TOOL_SIGNATURE_SCRATCHIE.equals(toolSignature)) { + scratchiePassed = true; + + request.setAttribute("isScratchieAvailable", true); + request.setAttribute("traToolContentId", toolContentId); + request.setAttribute("traToolActivityId", toolActivityId); + } + + if (CentralConstants.TOOL_SIGNATURE_LEADERSELECTION.equals(toolSignature)) { + request.setAttribute("leaderselectionToolActivityId", toolActivityId); + request.setAttribute("leaderselectionToolContentId", toolContentId); + } + + } else if (activity instanceof GateActivity) { + request.setAttribute("isGatesAvailable", true); + } + } + + request.setAttribute("assessmentToolContentIds", assessmentToolContentIds); + request.setAttribute("assessmentActivityTitles", assessmentActivityTitles); + } + + private void initServices() { + ServletContext servletContext = this.getServlet().getServletContext(); + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + if (lessonService == null) { + lessonService = (ILessonService) ctx.getBean("lessonService"); + } + + if (monitoringService == null) { + monitoringService = (IMonitoringService) ctx.getBean("monitoringService"); + } + + if (coreToolService == null) { + coreToolService = (ILamsCoreToolService) ctx.getBean("lamsCoreToolService"); + } + + if (toolService == null) { + toolService = (ILamsToolService) ctx.getBean("lamsToolService"); + } + + if (activityDAO == null) { + activityDAO = (IActivityDAO) ctx.getBean("activityDAO"); + } + + if (gradebookService == null) { + gradebookService = (IGradebookService) ctx.getBean("gradebookService"); + } + + } + +} Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/ToolOutputBranchingAction.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r6e7ab7890111cde84e6557c3507a1e9d3682e318 --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/ToolOutputBranchingAction.java (.../ToolOutputBranchingAction.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/ToolOutputBranchingAction.java (.../ToolOutputBranchingAction.java) (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -36,6 +36,6 @@ * * */ -public class ToolOutputBranchingAction extends BranchingAction { +public class ToolOutputBranchingAction extends BranchingController { } Index: lams_monitoring/web/WEB-INF/spring-servlet.xml =================================================================== diff -u --- lams_monitoring/web/WEB-INF/spring-servlet.xml (revision 0) +++ lams_monitoring/web/WEB-INF/spring-servlet.xml (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file Index: lams_monitoring/web/WEB-INF/web.xml =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r6e7ab7890111cde84e6557c3507a1e9d3682e318 --- lams_monitoring/web/WEB-INF/web.xml (.../web.xml) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_monitoring/web/WEB-INF/web.xml (.../web.xml) (revision 6e7ab7890111cde84e6557c3507a1e9d3682e318) @@ -73,6 +73,14 @@ org.springframework.web.context.ContextLoaderListener + + + spring + + org.springframework.web.servlet.DispatcherServlet + + 1 + action @@ -97,7 +105,7 @@ - action + spring *.do