Index: lams_learning/src/java/org/lamsfoundation/lams/learning/learningApplicationContext.xml =================================================================== diff -u -rffe8c4f79b7746db65aa6707f15ef1e001b90d71 -r8033905f61b3c293f92b5c9c468dd22606ab9a42 --- lams_learning/src/java/org/lamsfoundation/lams/learning/learningApplicationContext.xml (.../learningApplicationContext.xml) (revision ffe8c4f79b7746db65aa6707f15ef1e001b90d71) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/learningApplicationContext.xml (.../learningApplicationContext.xml) (revision 8033905f61b3c293f92b5c9c468dd22606ab9a42) @@ -73,6 +73,7 @@ PROPAGATION_REQUIRED PROPAGATION_REQUIRED PROPAGATION_REQUIRED,readOnly + PROPAGATION_REQUIRES_NEW Index: lams_learning/src/java/org/lamsfoundation/lams/learning/service/ICoreLearnerService.java =================================================================== diff -u -r81bec2c4c018269cb6a8d65212aae7e37b406fb3 -r8033905f61b3c293f92b5c9c468dd22606ab9a42 --- lams_learning/src/java/org/lamsfoundation/lams/learning/service/ICoreLearnerService.java (.../ICoreLearnerService.java) (revision 81bec2c4c018269cb6a8d65212aae7e37b406fb3) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/service/ICoreLearnerService.java (.../ICoreLearnerService.java) (revision 8033905f61b3c293f92b5c9c468dd22606ab9a42) @@ -23,6 +23,7 @@ package org.lamsfoundation.lams.learning.service; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -34,6 +35,7 @@ import org.lamsfoundation.lams.learningdesign.Group; import org.lamsfoundation.lams.learningdesign.Grouping; import org.lamsfoundation.lams.learningdesign.SequenceActivity; +import org.lamsfoundation.lams.learningdesign.dao.IActivityDAO; import org.lamsfoundation.lams.lesson.LearnerProgress; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.lesson.dto.LessonDTO; @@ -335,4 +337,9 @@ */ SequenceActivity selectBranch(Lesson lesson, BranchingActivity branchingActivity, Integer learnerId, Long branchId) throws LearnerServiceException; + + /* Added for RepopulateProgressMarksServlet - can be removed later */ + String[] recalcProgressForLearner(Lesson lesson, ArrayList activityList, LearnerProgress learnerProgress, boolean updateGradebookForAll); + IActivityDAO getActivityDAO(); + } Index: lams_learning/src/java/org/lamsfoundation/lams/learning/service/LearnerService.java =================================================================== diff -u -rbdf9ae9c42902b4b2a275fdc787e1814368c8fb2 -r8033905f61b3c293f92b5c9c468dd22606ab9a42 --- lams_learning/src/java/org/lamsfoundation/lams/learning/service/LearnerService.java (.../LearnerService.java) (revision bdf9ae9c42902b4b2a275fdc787e1814368c8fb2) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/service/LearnerService.java (.../LearnerService.java) (revision 8033905f61b3c293f92b5c9c468dd22606ab9a42) @@ -51,6 +51,7 @@ import org.lamsfoundation.lams.learningdesign.BranchActivityEntry; import org.lamsfoundation.lams.learningdesign.BranchCondition; import org.lamsfoundation.lams.learningdesign.BranchingActivity; +import org.lamsfoundation.lams.learningdesign.ComplexActivity; import org.lamsfoundation.lams.learningdesign.ConditionGateActivity; import org.lamsfoundation.lams.learningdesign.DataFlowObject; import org.lamsfoundation.lams.learningdesign.GateActivity; @@ -69,6 +70,7 @@ import org.lamsfoundation.lams.learningdesign.dao.IDataFlowDAO; import org.lamsfoundation.lams.learningdesign.dao.IGroupUserDAO; import org.lamsfoundation.lams.learningdesign.dao.IGroupingDAO; +import org.lamsfoundation.lams.lesson.CompletedActivityProgress; import org.lamsfoundation.lams.lesson.LearnerProgress; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.lesson.dao.ILearnerProgressDAO; @@ -78,6 +80,8 @@ import org.lamsfoundation.lams.lesson.service.LessonServiceException; import org.lamsfoundation.lams.logevent.LogEvent; import org.lamsfoundation.lams.logevent.service.ILogEventService; +import org.lamsfoundation.lams.tool.Tool; +import org.lamsfoundation.lams.tool.ToolCompletionStatus; import org.lamsfoundation.lams.tool.ToolOutput; import org.lamsfoundation.lams.tool.ToolSession; import org.lamsfoundation.lams.tool.exception.LamsToolServiceException; @@ -1415,4 +1419,184 @@ } return true; } + + /* Added for RepopulateProgressMarksServlet - can be removed later */ + private static final String TOOL_SIGNATURE_ASSESSMENT = "laasse10"; + private static final String TOOL_SIGNATURE_SCRATCHIE = "lascrt11"; + private static final String TOOL_SIGNATURE_MCQ = "lamc11"; + + public String[] recalcProgressForLearner(Lesson lesson, ArrayList activityList, LearnerProgress learnerProgress, boolean updateGradebookForAll) { + + StringBuilder auditLogBuilder = new StringBuilder(""); + StringBuilder errorBuilder = new StringBuilder(""); + + User learner = learnerProgress.getUser(); + Date lessonStartDate = learnerProgress.getStartDate(); + auditLogBuilder.append("\n\nUpdating ").append(learner.getLogin()).append(" ") + .append(learner.getFullName()).append("\n"); + + boolean updated = false; + for (Activity activity : activityList) { + + CompletedActivityProgress completedProgress = learnerProgress.getCompletedActivities().get(activity); + if (completedProgress == null || completedProgress.getStartDate() == null || completedProgress.getFinishDate() == null) { + updated = updateProgress(lesson.getLessonId(), auditLogBuilder, errorBuilder, learnerProgress, + learner, lessonStartDate, activity) || updated; + } + + // is completed (previously or just now?), in which case update gradebook. + if (activity.isToolActivity()) { + CompletedActivityProgress updatedCompletedProgress = learnerProgress.getCompletedActivities().get( + activity); + if (updatedCompletedProgress != null) { + ToolActivity toolActivity = (ToolActivity) activity; + Tool tool = toolActivity.getTool(); + if (updateGradebookForAll || TOOL_SIGNATURE_ASSESSMENT.equals(tool.getToolSignature()) + || TOOL_SIGNATURE_SCRATCHIE.equals(tool.getToolSignature()) + || TOOL_SIGNATURE_MCQ.equals(tool.getToolSignature())) { + auditLogBuilder.append("Pushing mark to Gradebook for activity ") + .append(activity.getActivityId()).append(" ").append(activity.getTitle()).append("\n"); + gradebookService.updateUserActivityGradebookMark(lesson, activity, learner); + } + } + } + + } + + if ( updated ) + learnerProgressDAO.updateLearnerProgress(learnerProgress); + + return new String[] { auditLogBuilder.toString(), errorBuilder.toString() }; + } + + private boolean updateProgress(Long lessonId, StringBuilder auditLogBuilder, StringBuilder errorBuilder, + LearnerProgress learnerProgress, User learner, Date lessonStartDate, Activity activity) { + + boolean updated = false; + ToolCompletionStatus results = recalcActivityProgress(activity, learner, lessonId, learnerProgress, auditLogBuilder, errorBuilder); + + // results == null ignore - won't harm anything if it is in attempted and nothing in the tool and + // do not remove from completed in case it was force completed, or the tool doesn't support this + + if (results != null) { + + if (results.getStatus() == ToolCompletionStatus.ACTIVITY_COMPLETED) { + // completed + Date startedDateFromAttempted = learnerProgress.getAttemptedActivities().get(activity); + CompletedActivityProgress cap = learnerProgress.getCompletedActivities().get(activity); + if (cap != null) { + if (cap.getStartDate() == null) { + if (startedDateFromAttempted != null) + cap.setStartDate(startedDateFromAttempted); + else if (results.getStartDate() != null) + cap.setStartDate(results.getStartDate()); + } + if (cap.getFinishDate() == null && results.getFinishDate() != null) { + cap.setFinishDate(results.getFinishDate()); + } + } else { + cap = new CompletedActivityProgress(learnerProgress, activity, + startedDateFromAttempted != null ? startedDateFromAttempted : results.getStartDate(), + results.getFinishDate()); + } + if ( cap.getStartDate() == null ) { + // must have something or it is not seen as completed + cap.setStartDate(lessonStartDate); + } + learnerProgress.getCompletedActivities().put(activity, cap); + learnerProgress.getAttemptedActivities().remove(activity); + + auditLogBuilder.append("Progress updated for completed activity ").append(activity.getActivityId()) + .append(" ").append(activity.getTitle()).append("\n"); + updated = true; + + } else if (results.getStatus() == ToolCompletionStatus.ACTIVITY_ATTEMPTED) { + // Attempted - if not already there add with tool's start date, or failing that the tool's value for + // session start date, or the core's value for session start date, or the lesson start date. + // Must have a date or it can't be saved. + if (results.getStartDate() != null) + learnerProgress.getAttemptedActivities().putIfAbsent(activity, results.getStartDate()); + else { + learnerProgress.getAttemptedActivities().putIfAbsent(activity, lessonStartDate); + } + auditLogBuilder.append("Progress updated for attempted activity ").append(activity.getActivityId()) + .append(" ").append(activity.getTitle()).append("\n"); + updated = true; + } + + } + + return updated; + } + + private ToolCompletionStatus recalcActivityProgress(Activity activity, User learner, Long lessonId, LearnerProgress learnerProgress, StringBuilder auditLogEntry, StringBuilder errorBuilder) { + + ToolCompletionStatus status = null; + + if (activity.isToolActivity()) { + ToolSession toolSession = lamsCoreToolService.getToolSessionByLearner(learner, activity); + if (toolSession != null) { + status = lamsCoreToolService.getCompletionStatusFromTool(learner, activity); + if ( status.getStartDate() == null ) + status.setStartDate(toolSession.getCreateDateTime()); + } + + } else if ( activity.isComplexActivity() ) { + ComplexActivity complexActivity = (ComplexActivity) activity; + boolean attempted = false; + boolean allComplete = true; + Date caStartDate = null; + Date caEndDate = null; + for (Iterator i = complexActivity.getActivities().iterator(); i.hasNext();) { + Activity childActivity = (Activity) i.next(); + Date childStartDate = learnerProgress.getAttemptedActivities().get(childActivity); + Date childEndDate = null; + if ( childStartDate != null ) { + attempted = true; + } + CompletedActivityProgress childCap = learnerProgress.getCompletedActivities().get(childActivity); + if ( childCap == null ) { + allComplete = false; + } else { + attempted = true; + if ( childStartDate == null ) + childStartDate = childCap.getStartDate(); + childEndDate = childCap.getFinishDate(); + } + if ( caStartDate == null || (childStartDate != null && childStartDate.before(caStartDate)) ) + caStartDate = childStartDate; + if ( caEndDate == null || (childEndDate != null && childEndDate.after(caStartDate)) ) + caEndDate = childEndDate; + } + + if ( attempted ) { + if ( allComplete ) + status = new ToolCompletionStatus(ToolCompletionStatus.ACTIVITY_COMPLETED, caStartDate, caEndDate); + else + status = new ToolCompletionStatus(ToolCompletionStatus.ACTIVITY_ATTEMPTED, caStartDate, null); + } + + } else if ( activity.isGateActivity() ){ + // do nothing + ; + } else if ( activity.isGroupingActivity() ) { + GroupingActivity groupingActivity = (GroupingActivity) activity; + Grouping grouping = groupingActivity.getCreateGrouping(); + if ( grouping.doesLearnerExist(learner) ) { + status = new ToolCompletionStatus(ToolCompletionStatus.ACTIVITY_COMPLETED, null, null); + } + } else { + errorBuilder.append("Unable to update status for unexpected activity ").append(activity.getActivityId()) + .append(" ").append(activity.getTitle()); + } + return status; + + } + + @Override + public IActivityDAO getActivityDAO() { + return activityDAO; + } + + } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/web/RepopulateProgressMarksServlet.java =================================================================== diff -u --- lams_learning/src/java/org/lamsfoundation/lams/learning/web/RepopulateProgressMarksServlet.java (revision 0) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/web/RepopulateProgressMarksServlet.java (revision 8033905f61b3c293f92b5c9c468dd22606ab9a42) @@ -0,0 +1,218 @@ +/**************************************************************** + * 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.learning.web; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.hibernate.SessionFactory; +import org.lamsfoundation.lams.gradebook.service.IGradebookService; +import org.lamsfoundation.lams.learning.service.ICoreLearnerService; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.ComplexActivity; +import org.lamsfoundation.lams.learningdesign.LearningDesign; +import org.lamsfoundation.lams.learningdesign.LearningDesignProcessor; +import org.lamsfoundation.lams.learningdesign.SimpleActivity; +import org.lamsfoundation.lams.learningdesign.ToolActivity; +import org.lamsfoundation.lams.learningdesign.dao.IActivityDAO; +import org.lamsfoundation.lams.learningdesign.exception.LearningDesignProcessorException; +import org.lamsfoundation.lams.lesson.CompletedActivityProgress; +import org.lamsfoundation.lams.lesson.LearnerProgress; +import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.dao.ILearnerProgressDAO; +import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.tool.Tool; +import org.lamsfoundation.lams.tool.ToolCompletionStatus; +import org.lamsfoundation.lams.tool.ToolSession; +import org.lamsfoundation.lams.tool.service.ILamsCoreToolService; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.util.audit.IAuditService; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.orm.hibernate4.SessionHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +@SuppressWarnings("serial") +public class RepopulateProgressMarksServlet extends HttpServlet { + + private static Logger log = Logger.getLogger(RepopulateProgressMarksServlet.class); + + private static IAuditService auditService; + private static ILessonService lessonService; + private static ICoreLearnerService learnerService; + + @Override + public void init() throws ServletException { + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + RepopulateProgressMarksServlet.auditService = (IAuditService) ctx.getBean("auditService"); + RepopulateProgressMarksServlet.lessonService = (ILessonService) ctx.getBean("lessonService"); + RepopulateProgressMarksServlet.learnerService = (ICoreLearnerService) ctx.getBean("learnerService"); + } + + @Override + @SuppressWarnings("unchecked") + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + String header=""; + StringBuilder errorBuilder = new StringBuilder(""); + StringBuilder auditLogBuilder = new StringBuilder(""); + UserDTO userDTO = null; + + PrintWriter out = response.getWriter(); + try { + + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + Integer restrictToLearnerId = WebUtil.readIntParam(request, AttributeNames.PARAM_USER_ID, true); + Boolean gradebookAll = WebUtil.readBooleanParam(request, "gradebookAll", false); + HttpSession ss = SessionManager.getSession(); + userDTO = (UserDTO) ss.getAttribute(AttributeNames.USER); + + Lesson lesson = lessonService.getLesson(lessonId); + if (lesson == null) { + log.error("RepopulateProgressMarksServlet: lesson not found " + lessonId); + out.println("ERROR: lesson not found " + lessonId); + return; + } + + header = new StringBuilder( + "Learner progress and gradebook marks to be repopulated for lesson ").append(lessonId).append(" ") + .append(lesson.getLessonName()) + .append(".\n----------------------------------------------------------------------------------\n\n").toString(); + + ActivitiesToCheckProcessor processor = new ActivitiesToCheckProcessor(lesson.getLearningDesign(), + learnerService.getActivityDAO()); + processor.parseLearningDesign(); + ArrayList activityList = processor.getActivityList(); + + auditLogBuilder.append("Full log:\n\nUpdating progress for the following activities: "); + for (Activity activity : activityList) { + auditLogBuilder.append(activity.getActivityId()).append(" ").append(activity.getTitle()).append("; "); + } + auditLogBuilder.append("\n\n"); + + if (restrictToLearnerId == null) { + Set progresses = (Set) lesson.getLearnerProgresses(); + for (LearnerProgress learnerProgress : progresses) { + processLearner(errorBuilder, auditLogBuilder, lesson, activityList, learnerProgress, gradebookAll); + } + } else { + LearnerProgress learnerProgress = learnerService.getProgress(restrictToLearnerId, lessonId); + if (learnerProgress != null) { + processLearner(errorBuilder, auditLogBuilder, lesson, activityList, learnerProgress, gradebookAll); + } else { + log.error("RepopulateProgressMarksServlet: learner's progress not found " + restrictToLearnerId); + out.println("ERROR: progress for learner " + restrictToLearnerId + " not found"); + return; + } + } + + } catch (Throwable e) { + + log.error("Error: " + e.getMessage() + e.getCause(), e); + errorBuilder.append("Error occured ").append(e.getMessage()); + } + + String errors = errorBuilder.append("\n").toString(); + String msg; + if (errors.length() > 1) { + msg = new StringBuilder(header).append("Errors occured. Some data may be been updated.\n").append(errors).append(auditLogBuilder.toString()).toString(); + } else { + msg = new StringBuilder(header).append("Successful run, no errors\n").append(auditLogBuilder.toString()).toString(); + } + auditService.log(userDTO, "RepopulateProgressMarksServlet",msg); + out.println(msg); + return; + } + + private void processLearner(StringBuilder errorBuilder, StringBuilder auditLogBuilder, Lesson lesson, + ArrayList activityList, LearnerProgress learnerProgress, boolean updateGradebookForAll) { + try { + String messages[] = learnerService.recalcProgressForLearner(lesson, activityList, learnerProgress, updateGradebookForAll); + auditLogBuilder.append(messages[0]); + errorBuilder.append(messages[1]); + } catch ( Throwable e ) { + log.error("Error thrown while processing "+learnerProgress.getUser().getLogin(), e); + String msg = new StringBuilder("Error occured while processing user ") + .append(learnerProgress.getUser().getLogin()).append(" ").append(learnerProgress.getUser().getFullName()).append(". Proceeding entries in log for this user may or may not have worked. Error was ") + .append(e.getMessage()).append("\n") + .toString(); + auditLogBuilder.append(msg); + errorBuilder.append(msg); + } + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, + IOException { + doGet(request, response); + } + + class ActivitiesToCheckProcessor extends LearningDesignProcessor { + + ArrayList activityList; + + public ActivitiesToCheckProcessor(LearningDesign design, IActivityDAO activityDAO) { + super(design, activityDAO); + activityList = new ArrayList(); + } + + @Override + public boolean startComplexActivity(ComplexActivity activity) throws LearningDesignProcessorException { + return true; + } + + @Override + public void endComplexActivity(ComplexActivity activity) throws LearningDesignProcessorException { + activityList.add(activity); + } + + @Override + public void startSimpleActivity(SimpleActivity activity) throws LearningDesignProcessorException { + } + + @Override + public void endSimpleActivity(SimpleActivity activity) throws LearningDesignProcessorException { + activityList.add(activity); + } + + public ArrayList getActivityList() { + return activityList; + } + } +} \ No newline at end of file Index: lams_learning/web/WEB-INF/web.xml =================================================================== diff -u -r0ddeb3a1dcf29cbbba6ed0fccbd139f9c31c347f -r8033905f61b3c293f92b5c9c468dd22606ab9a42 --- lams_learning/web/WEB-INF/web.xml (.../web.xml) (revision 0ddeb3a1dcf29cbbba6ed0fccbd139f9c31c347f) +++ lams_learning/web/WEB-INF/web.xml (.../web.xml) (revision 8033905f61b3c293f92b5c9c468dd22606ab9a42) @@ -96,6 +96,18 @@ 1 + + + RepopulateProgressMarksServlet + + org.lamsfoundation.lams.learning.web.RepopulateProgressMarksServlet + + + + RepopulateProgressMarksServlet + /repopulateProgress + +