Index: lams_tool_scratchie/conf/hibernate/mappings/org/lamsfoundation/lams/tool/scratchie/model/ScratchieSession.hbm.xml
===================================================================
diff -u -r65efb0abb529cafc1977e284e2c9a4ed33722334 -rbeb150b1b726c122ccc5af1ba95083105f9948b8
--- lams_tool_scratchie/conf/hibernate/mappings/org/lamsfoundation/lams/tool/scratchie/model/ScratchieSession.hbm.xml (.../ScratchieSession.hbm.xml) (revision 65efb0abb529cafc1977e284e2c9a4ed33722334)
+++ lams_tool_scratchie/conf/hibernate/mappings/org/lamsfoundation/lams/tool/scratchie/model/ScratchieSession.hbm.xml (.../ScratchieSession.hbm.xml) (revision beb150b1b726c122ccc5af1ba95083105f9948b8)
@@ -33,6 +33,14 @@
insert="true"
column="session_end_date"
/>
+
+
+
@@ -137,6 +138,7 @@
PROPAGATION_REQUIRED,-java.lang.Exception
PROPAGATION_REQUIRED,-java.lang.Exception
PROPAGATION_REQUIRED,-java.lang.Exception
+ PROPAGATION_REQUIRED,-java.lang.Exception
PROPAGATION_REQUIRED,-java.lang.Exception
PROPAGATION_REQUIRED,-java.lang.Exception
PROPAGATION_REQUIRED,-java.lang.Exception
Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/IScratchieService.java
===================================================================
diff -u -rae63df3e06f618712e1eab064a05015eb45c2f17 -rbeb150b1b726c122ccc5af1ba95083105f9948b8
--- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/IScratchieService.java (.../IScratchieService.java) (revision ae63df3e06f618712e1eab064a05015eb45c2f17)
+++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/IScratchieService.java (.../IScratchieService.java) (revision beb150b1b726c122ccc5af1ba95083105f9948b8)
@@ -43,6 +43,7 @@
import org.lamsfoundation.lams.tool.scratchie.model.ScratchieSession;
import org.lamsfoundation.lams.tool.scratchie.model.ScratchieUser;
import org.lamsfoundation.lams.util.ExcelCell;
+import org.quartz.SchedulerException;
/**
* Interface that defines the contract that all ShareScratchie service provider must follow.
@@ -66,6 +67,14 @@
* @param toolSessionId
*/
ScratchieUser checkLeaderSelectToolForSessionLeader(ScratchieUser user, Long toolSessionId);
+
+ /**
+ * Stores date when user has started activity with time limit.
+ *
+ * @param sessionId
+ * @throws SchedulerException
+ */
+ void launchTimeLimit(Long sessionId) throws SchedulerException;
List getBurningQuestionsBySession(Long sessionId);
Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java
===================================================================
diff -u -rae63df3e06f618712e1eab064a05015eb45c2f17 -rbeb150b1b726c122ccc5af1ba95083105f9948b8
--- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision ae63df3e06f618712e1eab064a05015eb45c2f17)
+++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision beb150b1b726c122ccc5af1ba95083105f9948b8)
@@ -28,8 +28,10 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
+import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -39,6 +41,7 @@
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
+import java.util.TimeZone;
import java.util.TreeSet;
import org.apache.commons.lang.StringEscapeUtils;
@@ -87,6 +90,7 @@
import org.lamsfoundation.lams.tool.scratchie.model.ScratchieItem;
import org.lamsfoundation.lams.tool.scratchie.model.ScratchieSession;
import org.lamsfoundation.lams.tool.scratchie.model.ScratchieUser;
+import org.lamsfoundation.lams.tool.scratchie.util.FinishScratchingJob;
import org.lamsfoundation.lams.tool.scratchie.util.ScratchieAnswerComparator;
import org.lamsfoundation.lams.tool.scratchie.util.ScratchieItemComparator;
import org.lamsfoundation.lams.tool.scratchie.util.ScratchieToolContentHandler;
@@ -99,6 +103,12 @@
import org.lamsfoundation.lams.util.JsonUtil;
import org.lamsfoundation.lams.util.MessageService;
import org.lamsfoundation.lams.util.audit.IAuditService;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
/**
* @author Andrey Balan
@@ -149,6 +159,8 @@
private IEventNotificationService eventNotificationService;
private ScratchieOutputFactory scratchieOutputFactory;
+
+ private Scheduler scheduler;
// *******************************************************************************
// Service method
@@ -256,6 +268,39 @@
}
return leader;
}
+
+ @Override
+ public void launchTimeLimit(Long sessionId) throws SchedulerException {
+ ScratchieSession session = getScratchieSessionBySessionId(sessionId);
+ int timeLimit = session.getScratchie().getTimeLimit();
+ if (timeLimit == 0) {
+ return;
+ }
+
+ //store timeLimitLaunchedDate into DB
+ Date timeLimitLaunchedDate = new Date();
+ session.setTimeLimitLaunchedDate(timeLimitLaunchedDate);
+ scratchieSessionDao.saveObject(session);
+
+ //calculate timeLimit finish date
+ Calendar timeLimitFinishDate = new GregorianCalendar(TimeZone.getDefault());
+ timeLimitFinishDate.setTime(timeLimitLaunchedDate);
+ timeLimitFinishDate.add(Calendar.MINUTE, timeLimit);
+ //adding 5 extra seconds to let leader auto-submit results and store them in DB
+ timeLimitFinishDate.add(Calendar.SECOND, 5);
+
+ //start quartz job to notify non-leaders time is over
+ JobDetail finishScratchingJob = JobBuilder.newJob(FinishScratchingJob.class)
+ .withIdentity("finishScratching:" + sessionId)
+ .withDescription("Group name: " + session.getSessionName())
+ .usingJobData("toolSessionId", sessionId).build();
+
+ Trigger fnishScratchingTrigger = TriggerBuilder.newTrigger()
+ .withIdentity("fnishScratchingTrigger:" + sessionId)
+ .startAt(timeLimitFinishDate.getTime()).build();
+
+ scheduler.scheduleJob(finishScratchingJob, fnishScratchingTrigger);
+ }
@Override
public void changeUserMark(Long userId, Long sessionId, Integer newMark) {
@@ -2044,6 +2089,14 @@
public void setScratchieOutputFactory(ScratchieOutputFactory scratchieOutputFactory) {
this.scratchieOutputFactory = scratchieOutputFactory;
}
+
+ /**
+ * @param scheduler
+ * The scheduler to set.
+ */
+ public void setScheduler(Scheduler scheduler) {
+ this.scheduler = scheduler;
+ }
// ****************** REST methods *************************
Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/util/FinishScratchingJob.java
===================================================================
diff -u
--- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/util/FinishScratchingJob.java (revision 0)
+++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/util/FinishScratchingJob.java (revision beb150b1b726c122ccc5af1ba95083105f9948b8)
@@ -0,0 +1,60 @@
+package org.lamsfoundation.lams.tool.scratchie.util;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.apache.tomcat.util.json.JSONException;
+import org.lamsfoundation.lams.tool.scratchie.model.ScratchieSession;
+import org.lamsfoundation.lams.tool.scratchie.service.IScratchieService;
+import org.lamsfoundation.lams.tool.scratchie.web.action.LearningWebsocketServer;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.SchedulerContext;
+import org.quartz.SchedulerException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+
+/**
+ * The Quartz sheduling job that finishes scratching for the given ToolSession (which will automatically lead to showing
+ * Finish button all non-leaders and thus let then finish the activity).
+ *
+ * @author Andrey Balan
+ */
+public class FinishScratchingJob extends QuartzJobBean {
+
+ private static Logger log = Logger.getLogger(FinishScratchingJob.class);
+
+ @Override
+ protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
+ IScratchieService scratchieService = getScratchieService(context);
+
+ //getting toolSessionId set from scheduler
+ Map properties = context.getJobDetail().getJobDataMap();
+ Long toolSessionId = (Long) properties.get("toolSessionId");
+ ScratchieSession toolSession = scratchieService.getScratchieSessionBySessionId(toolSessionId);
+
+ // if leader hasn't finished scratching yet - let all non-leaders see Next Activity button
+ if (!toolSession.isScratchingFinished()) {
+ try {
+ scratchieService.setScratchingFinished(toolSessionId);
+ } catch (JSONException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Scratching is finished for toolSessionId: " + toolSessionId.longValue());
+ }
+ }
+
+ protected IScratchieService getScratchieService(JobExecutionContext context) throws JobExecutionException {
+ try {
+ SchedulerContext sc = context.getScheduler().getContext();
+ ApplicationContext cxt = (ApplicationContext) sc.get("context.central");
+ return (IScratchieService) cxt.getBean("scratchieService");
+ } catch (SchedulerException e) {
+ throw new JobExecutionException("Failed look up the scratchieService" + e.toString());
+ }
+ }
+}
Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningAction.java
===================================================================
diff -u -rae63df3e06f618712e1eab064a05015eb45c2f17 -rbeb150b1b726c122ccc5af1ba95083105f9948b8
--- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningAction.java (.../LearningAction.java) (revision ae63df3e06f618712e1eab064a05015eb45c2f17)
+++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningAction.java (.../LearningAction.java) (revision beb150b1b726c122ccc5af1ba95083105f9948b8)
@@ -72,6 +72,7 @@
import org.lamsfoundation.lams.web.session.SessionManager;
import org.lamsfoundation.lams.web.util.AttributeNames;
import org.lamsfoundation.lams.web.util.SessionMap;
+import org.quartz.SchedulerException;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
@@ -87,7 +88,7 @@
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
HttpServletResponse response)
- throws IOException, ServletException, JSONException, ScratchieApplicationException {
+ throws IOException, ServletException, JSONException, ScratchieApplicationException, SchedulerException {
String param = mapping.getParameter();
// -----------------------Scratchie Learner function ---------------------------
@@ -97,6 +98,9 @@
if (param.equals("recordItemScratched")) {
return recordItemScratched(mapping, form, request, response);
}
+ if (param.equals("launchTimeLimit")) {
+ return launchTimeLimit(mapping, form, request, response);
+ }
if (param.equals("finish")) {
return finish(mapping, form, request, response);
}
@@ -176,6 +180,30 @@
return mapping.findForward("waitforleader");
}
+ // forwards to the waitForLeader time limit pages
+ if (!mode.isTeacher()) {
+ boolean isNonLeader = !user.getUserId().equals(groupLeader.getUserId());
+ if (isNonLeader && scratchie.getTimeLimit() != 0 && !user.isSessionFinished()) {
+
+ //show waitForLeaderLaunchTimeLimit page if the leader hasn't started activity or hasn't pressed OK button to launch time limit
+ if (toolSession.getTimeLimitLaunchedDate() == null) {
+ request.setAttribute(ScratchieConstants.ATTR_WAITING_MESSAGE_KEY, "label.waiting.for.leader.launch.time.limit");
+ return mapping.findForward("waitForLeaderTimeLimit");
+ }
+
+ // check if the time limit is exceeded
+ boolean isTimeLimitExceeded = toolSession.getTimeLimitLaunchedDate().getTime()
+ + scratchie.getTimeLimit() * 60000 < System.currentTimeMillis();
+
+ // if the time is up and leader hasn't submitted response (as there will be a little delay between time
+ // is up and scratching is finished) - show waitForLeaderFinish page
+ if (isTimeLimitExceeded && !toolSession.isScratchingFinished()) {
+ request.setAttribute(ScratchieConstants.ATTR_WAITING_MESSAGE_KEY, "label.waiting.for.leader.finish");
+ return mapping.findForward("waitForLeaderTimeLimit");
+ }
+ }
+ }
+
// initial Session Map
SessionMap sessionMap = new SessionMap();
request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap);
@@ -306,24 +334,40 @@
redirect.addParameter(ScratchieConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID());
return redirect;
- // show leader notebook page
+ // show leader notebook page
} else if (isUserLeader && isScratchingFinished && isWaitingForLeaderToSubmitNotebook) {
ActionRedirect redirect = new ActionRedirect(mapping.findForwardConfig("newReflection"));
redirect.addParameter(ScratchieConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID());
redirect.addParameter(AttributeNames.ATTR_MODE, mode);
return redirect;
- // show results page
+ // show results page
} else if (isShowResults) {
ActionRedirect redirect = new ActionRedirect(mapping.findForwardConfig("showResults"));
redirect.addParameter(ScratchieConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID());
redirect.addParameter(AttributeNames.ATTR_MODE, mode);
return redirect;
- // show learning.jsp page
+ // show learning.jsp page
} else {
+ // time limit feature
+ boolean isTimeLimitEnabled = isUserLeader && !isScratchingFinished && scratchie.getTimeLimit() != 0;
+ boolean isTimeLimitNotLaunched = toolSession.getTimeLimitLaunchedDate() == null;
+ long secondsLeft = 1;
+ if (isTimeLimitEnabled) {
+ // if user has pressed OK button already - calculate remaining time, and full time otherwise
+ secondsLeft = isTimeLimitNotLaunched ? scratchie.getTimeLimit() * 60
+ : scratchie.getTimeLimit() * 60
+ - (System.currentTimeMillis() - toolSession.getTimeLimitLaunchedDate().getTime()) / 1000;
+ // change negative number or zero to 1 so it can autosubmit results
+ secondsLeft = Math.max(1, secondsLeft);
+ }
+ request.setAttribute(ScratchieConstants.ATTR_IS_TIME_LIMIT_ENABLED, isTimeLimitEnabled);
+ request.setAttribute(ScratchieConstants.ATTR_IS_TIME_LIMIT_NOT_LAUNCHED, isTimeLimitNotLaunched);
+ request.setAttribute(ScratchieConstants.ATTR_SECONDS_LEFT, secondsLeft);
+
// make non leaders also wait for burning questions submit
isWaitingForLeaderToSubmitNotebook |= isWaitingForLeaderToSubmitBurningQuestions;
@@ -351,7 +395,7 @@
ScratchieSession toolSession = service.getScratchieSessionBySessionId(toolSessionId);
- ScratchieUser leader = this.getCurrentUser(toolSessionId);
+ ScratchieUser leader = getCurrentUser(toolSessionId);
// only leader is allowed to scratch answers
if (!toolSession.isUserGroupLeader(leader.getUid())) {
return null;
@@ -386,7 +430,32 @@
return null;
}
+
+ /**
+ * Stores date when user has started activity with time limit
+ * @throws ScratchieApplicationException
+ * @throws SchedulerException
+ */
+ private ActionForward launchTimeLimit(ActionMapping mapping, ActionForm form, HttpServletRequest request,
+ HttpServletResponse response) throws ScratchieApplicationException, SchedulerException {
+ initializeScratchieService();
+ String sessionMapID = WebUtil.readStrParam(request, ScratchieConstants.ATTR_SESSION_MAP_ID);
+ SessionMap sessionMap = (SessionMap) request.getSession()
+ .getAttribute(sessionMapID);
+ final Long toolSessionId = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID);
+ ScratchieSession toolSession = service.getScratchieSessionBySessionId(toolSessionId);
+
+ ScratchieUser leader = getCurrentUser(toolSessionId);
+ // only leader is allowed to launch time limit
+ if (!toolSession.isUserGroupLeader(leader.getUid())) {
+ return null;
+ }
+
+ service.launchTimeLimit(toolSessionId);
+ return null;
+ }
+
/**
* Displays results page. When leader gets to this page, scratchingFinished column is set to true for all users.
*
Index: lams_tool_scratchie/web/WEB-INF/struts-config.xml
===================================================================
diff -u -r480406c3b8253f063ad53045227a09a79e98f1d5 -rbeb150b1b726c122ccc5af1ba95083105f9948b8
--- lams_tool_scratchie/web/WEB-INF/struts-config.xml (.../struts-config.xml) (revision 480406c3b8253f063ad53045227a09a79e98f1d5)
+++ lams_tool_scratchie/web/WEB-INF/struts-config.xml (.../struts-config.xml) (revision beb150b1b726c122ccc5af1ba95083105f9948b8)
@@ -205,6 +205,7 @@
parameter="start" >
+
@@ -215,6 +216,10 @@
type="org.lamsfoundation.lams.tool.scratchie.web.action.LearningAction"
parameter="recordItemScratched" >
+
+
Index: lams_tool_scratchie/web/pages/learning/learning.jsp
===================================================================
diff -u -r5f05f0ea0765aae9eaec741f5419fa9f7762f150 -rbeb150b1b726c122ccc5af1ba95083105f9948b8
--- lams_tool_scratchie/web/pages/learning/learning.jsp (.../learning.jsp) (revision 5f05f0ea0765aae9eaec741f5419fa9f7762f150)
+++ lams_tool_scratchie/web/pages/learning/learning.jsp (.../learning.jsp) (revision beb150b1b726c122ccc5af1ba95083105f9948b8)
@@ -69,20 +69,38 @@
}
//time limit feature
-
+
$(document).ready(function(){
- //show confirmation dialog
- $.blockUI({
- message: $('#timelimit-start-dialog'),
- css: { width: '325px', height: '120px'},
- overlayCSS: { opacity: '.98'}
- });
+
+ //show timelimit-start-dialog in order to start countdown
+ if (${isTimeLimitNotLaunched}) {
- //once OK button pressed start countdown
- $('#timelimit-start-ok').click(function() {
- $.unblockUI();
- displayCountdown();
- });
+ //show confirmation dialog
+ $.blockUI({
+ message: $('#timelimit-start-dialog'),
+ css: { width: '325px', height: '120px'},
+ overlayCSS: { opacity: '.98'}
+ });
+
+ //once OK button pressed start countdown
+ $('#timelimit-start-ok').click(function() {
+
+ //store date when user has started activity with time limit
+ $.ajax({
+ async: true,
+ url: '',
+ data: 'sessionMapID=${sessionMapID}',
+ type: 'post'
+ });
+
+ $.unblockUI();
+ displayCountdown();
+ });
+
+ } else {
+ displayCountdown();
+ }
+
});
function displayCountdown(){
@@ -103,14 +121,14 @@
});
$('#countdown').countdown({
- until: '+${scratchie.timeLimit * 60}S',
+ until: '+${secondsLeft}S',
format: 'hMS',
compact: true,
onTick: function(periods) {
//check for 30 seconds
if ((periods[4] == 0) && (periods[5] == 0) && (periods[6] <= 30)) {
$('#countdown').css('color', '#FF3333');
- }
+ }
},
onExpiry: function(periods) {
$.blockUI({ message: '
' });
Index: lams_tool_scratchie/web/pages/learning/waitForLeaderTimeLimit.jsp
===================================================================
diff -u
--- lams_tool_scratchie/web/pages/learning/waitForLeaderTimeLimit.jsp (revision 0)
+++ lams_tool_scratchie/web/pages/learning/waitForLeaderTimeLimit.jsp (revision beb150b1b726c122ccc5af1ba95083105f9948b8)
@@ -0,0 +1,38 @@
+
+
+<%@ include file="/common/taglibs.jsp"%>
+
+
+
+
+ <%@ include file="/common/header.jsp"%>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+