Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/DokumaranService.java =================================================================== diff -u -ree6e1d2416d85cca9e842ee818489bbb94ed27ed -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/DokumaranService.java (.../DokumaranService.java) (revision ee6e1d2416d85cca9e842ee818489bbb94ed27ed) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/DokumaranService.java (.../DokumaranService.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -147,25 +147,27 @@ } @Override - public List checkLeaderSelectToolForSessionLeader(DokumaranUser user, Long toolSessionId, boolean isFirstTimeAccess) { + public List checkLeaderSelectToolForSessionLeader(DokumaranUser user, Long toolSessionId, + boolean isFirstTimeAccess) { if ((user == null) || (toolSessionId == null)) { return null; } DokumaranSession session = getDokumaranSessionBySessionId(toolSessionId); Dokumaran dokumaran = session.getDokumaran(); - List leaders = new ArrayList(); + List leaders = new ArrayList<>(); if (dokumaran.isAllowMultipleLeaders() && !isGroupedActivity(dokumaran.getContentId())) { - + List createdLeaders = dokumaranUserDao.getLeadersBySessionId(toolSessionId); leaders.addAll(createdLeaders); - + // check leader select tool for a leader only in case Dokumaran activity is accessed by this user for the first // time. We need to add this check in order to reduce amount of queries to Leader Selection tool. if (isFirstTimeAccess) { //get all leaders from Leader Selection tool - Set allLeaderUserIds = toolService.getAllLeaderUserIds(toolSessionId, user.getUserId().intValue()); + Set allLeaderUserIds = toolService.getAllLeaderUserIds(toolSessionId, + user.getUserId().intValue()); for (Long leaderUserId : allLeaderUserIds) { //in case current user is leader - store his leader status if (leaderUserId.equals(user.getUserId())) { @@ -174,7 +176,7 @@ leaders.add(user); continue; } - + //check if such leader is already created inside doKumaran boolean isLeaderCreated = false; for (DokumaranUser leader : createdLeaders) { @@ -183,7 +185,7 @@ break; } } - + //if the leader is not yet created - create him if (!isLeaderCreated && (getUserByIDAndSession(leaderUserId, toolSessionId) == null)) { log.debug("creating new user with userId: " + leaderUserId); @@ -194,7 +196,7 @@ leaders.add(leader); } } - + } } else { DokumaranUser leader = session.getGroupLeader(); @@ -219,7 +221,7 @@ dokumaranSessionDao.saveObject(session); } } - + if (leader != null) { leaders.add(leader); } @@ -237,7 +239,7 @@ } return false; } - + @Override public boolean isLeaderResponseFinalized(List leaders) { for (DokumaranUser leader : leaders) { @@ -247,49 +249,47 @@ } return false; } - + @Override public boolean isLeaderResponseFinalized(Long toolSessionId) { DokumaranSession session = getDokumaranSessionBySessionId(toolSessionId); Dokumaran dokumaran = session.getDokumaran(); - + boolean isLeaderResponseFinalized = false; if (dokumaran.isAllowMultipleLeaders() && !isGroupedActivity(dokumaran.getContentId())) { - + List leaders = dokumaranUserDao.getLeadersBySessionId(toolSessionId); for (DokumaranUser leader : leaders) { if (leader.isSessionFinished()) { isLeaderResponseFinalized = true; break; } } - + } else { DokumaranUser leader = session.getGroupLeader(); isLeaderResponseFinalized = (leader != null) && leader.isSessionFinished(); } return isLeaderResponseFinalized; } - + @Override public void launchTimeLimit(Long toolContentId) throws JSONException, IOException { Dokumaran dokumaran = getDokumaranByContentId(toolContentId); dokumaran.setTimeLimitLaunchedDate(new Date()); dokumaranDao.saveObject(dokumaran); - - LearningWebsocketServer.sendPageRefreshRequest(dokumaran.getContentId()); } - + @Override public void addOneMinute(Long toolContentId) throws JSONException, IOException { Dokumaran dokumaran = getDokumaranByContentId(toolContentId); - + int timeLimit = dokumaran.getTimeLimit(); if (timeLimit == 0) { return; } - + int newTimeLimit; if (checkTimeLimitExceeded(dokumaran)) { dokumaran.setTimeLimitLaunchedDate(new Date()); @@ -299,8 +299,6 @@ } dokumaran.setTimeLimit(newTimeLimit); dokumaranDao.saveObject(dokumaran); - - LearningWebsocketServer.sendAddOneMinuteRequest(dokumaran.getContentId()); } @Override @@ -395,7 +393,7 @@ // DokumaranSession session = dokumaranSessionDao.getSessionBySessionId(toolSessionId); // session.setStatus(DokumaranConstants.COMPLETED); // dokumaranSessionDao.saveObject(session); - + //finish Etherpad session. Encapsulate it in try-catch block as we don't want it to affect regular LAMS workflow. try { EPLiteClient client = initializeEPLiteClient(); @@ -434,7 +432,7 @@ @Override public List getSummary(Long contentId) { - List groupList = new ArrayList(); + List groupList = new ArrayList<>(); // get all sessions in a dokumaran and retrieve all dokumaran items under this session // plus initial dokumaran items by author creating (resItemList) @@ -448,9 +446,10 @@ String padId = session.getPadId(); group.setPadId(padId); - + //mark all session that has had problems with pad initializations so that they could be fixed in monitoring by a teacher - if (StringUtils.isEmpty(session.getEtherpadReadOnlyId()) || StringUtils.isEmpty(session.getEtherpadGroupId())) { + if (StringUtils.isEmpty(session.getEtherpadReadOnlyId()) + || StringUtils.isEmpty(session.getEtherpadGroupId())) { group.setSessionFaulty(true); } @@ -462,7 +461,7 @@ @Override public List getReflectList(Long contentId) { - List reflections = new LinkedList(); + List reflections = new LinkedList<>(); List sessionList = dokumaranSessionDao.getByContentId(contentId); for (DokumaranSession session : sessionList) { @@ -546,10 +545,10 @@ public boolean isGroupedActivity(long toolContentID) { return toolService.isGroupedActivity(toolContentID); } - + @Override public void auditLogStartEditingActivityInMonitor(long toolContentID) { - toolService.auditLogStartEditingActivityInMonitor(toolContentID); + toolService.auditLogStartEditingActivityInMonitor(toolContentID); } // ******************************************************************************* @@ -748,7 +747,7 @@ } } - + @Override public void createPad(Dokumaran dokumaran, DokumaranSession session) throws DokumaranConfigurationException { Long toolSessionId = session.getSessionId(); @@ -832,7 +831,7 @@ public Cookie createEtherpadCookieForLearner(DokumaranUser user, DokumaranSession session) throws DokumaranConfigurationException, URISyntaxException, DokumaranApplicationException { String groupId = session.getEtherpadGroupId(); - + //don't allow sessions that has had problems with pad initializations. they could be fixed in monitoring by a teacher if (StringUtils.isEmpty(session.getEtherpadReadOnlyId()) || StringUtils.isEmpty(groupId)) { throw new DokumaranApplicationException( @@ -878,7 +877,7 @@ Map etherpadSessions = client.listSessionsOfAuthor(authorId); for (DokumaranSession session : sessionList) { String groupId = session.getEtherpadGroupId(); - + //skip sessions that has had problems with pad initializations so that they could be fixed in monitoring by a teacher if (StringUtils.isEmpty(session.getEtherpadReadOnlyId()) || StringUtils.isEmpty(groupId)) { continue; @@ -890,13 +889,13 @@ return createEtherpadCookie(etherpadSessionIds); } - + /** * Returns valid Etherpad session. Returns existing one if finds such one and creates the new one otherwise */ private String getEtherpadSession(String authorId, String etherpadGroupId, Map etherpadSessions) { String etherpadSessionId = null; - + // search for already existing user's session boolean isValidForMoreThan1Hour = false; for (String etherpadSessionIdIter : (Set) etherpadSessions.keySet()) { @@ -914,7 +913,7 @@ if (isValidForMoreThan1Hour) { etherpadSessionId = etherpadSessionIdIter; break; - + } else { // can't delete expired sessions as Etherpad throws an exception. Nonetheless it returns expired // ones when client.listSessionsOfAuthor(authorId) is requested @@ -931,10 +930,10 @@ return etherpadSessionId; } - + /** * Constructs cookie to be stored at a clientside browser. - * + * * @param etherpadSessionIds * @return * @throws URISyntaxException @@ -944,7 +943,8 @@ String etherpadServerUrl = etherpadServerUrlConfig.getConfigValue(); URI uri = new URI(etherpadServerUrl); //regex to get the top level part of a domain - Pattern p = Pattern.compile("^(?:\\w+://)?[^:?#/\\s]*?([^.\\s]+\\.(?:[a-z]{2,}|co\\.uk|org\\.uk|ac\\.uk|edu\\.au|org\\.au|com\\.au|edu\\.sg|com\\.sg|net\\.sg|org\\.sg|gov\\.sg|per\\.sg))(?:[:?#/]|$)"); + Pattern p = Pattern.compile( + "^(?:\\w+://)?[^:?#/\\s]*?([^.\\s]+\\.(?:[a-z]{2,}|co\\.uk|org\\.uk|ac\\.uk|edu\\.au|org\\.au|com\\.au|edu\\.sg|com\\.sg|net\\.sg|org\\.sg|gov\\.sg|per\\.sg))(?:[:?#/]|$)"); // eg: uri.getHost() will return "www.foo.com" Matcher m = p.matcher(uri.getHost()); String topLevelDomain = m.matches() ? "." + m.group(1) : uri.getHost(); @@ -956,7 +956,7 @@ etherpadSessionCookie.setMaxAge(-1); etherpadSessionCookie.setPath("/"); - return etherpadSessionCookie; + return etherpadSessionCookie; } @Override @@ -1033,7 +1033,7 @@ @Override public List getToolOutputs(String name, Long toolContentId) { - return new ArrayList(); + return new ArrayList<>(); } @Override Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/action/LearningWebsocketServer.java =================================================================== diff -u -r7b5832fa8e607005c926eb10a4bf429fe185f092 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/action/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 7b5832fa8e607005c926eb10a4bf429fe185f092) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/action/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -1,8 +1,10 @@ package org.lamsfoundation.lams.tool.dokumaran.web.action; import java.io.IOException; +import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import javax.websocket.CloseReason; @@ -15,7 +17,14 @@ import org.apache.log4j.Logger; import org.apache.tomcat.util.json.JSONException; import org.apache.tomcat.util.json.JSONObject; +import org.lamsfoundation.lams.tool.dokumaran.DokumaranConstants; +import org.lamsfoundation.lams.tool.dokumaran.model.Dokumaran; +import org.lamsfoundation.lams.tool.dokumaran.service.IDokumaranService; +import org.lamsfoundation.lams.util.hibernate.HibernateSessionManager; +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; /** * Sends time limit start and +1min events to learners. @@ -26,10 +35,74 @@ @ServerEndpoint("/learningWebsocket") public class LearningWebsocketServer { - private static Logger log = Logger.getLogger(LearningWebsocketServer.class); + /** + * A singleton which updates Learners with Leader selection. + */ + private static class SendWorker extends Thread { + private boolean stopFlag = false; + // how ofter the thread runs + private static final long CHECK_INTERVAL = 3000; + @Override + public void run() { + while (!stopFlag) { + try { + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + HibernateSessionManager.openSession(); + Iterator>> entryIterator = LearningWebsocketServer.websockets.entrySet() + .iterator(); + // go through activities and update registered learners with reports and vote count + while (entryIterator.hasNext()) { + Entry> entry = entryIterator.next(); + Long toolContentId = entry.getKey(); + // if all learners left the activity, remove the obsolete mapping + Set dokuWebsockets = entry.getValue(); + if (dokuWebsockets.isEmpty()) { + entryIterator.remove(); + timeLimitCache.remove(toolContentId); + continue; + } + + Dokumaran dokumaran = getDokumaranService().getDokumaranByContentId(toolContentId); + int timeLimit = dokumaran.getTimeLimit(); + if (dokumaran.getTimeLimitLaunchedDate() != null && timeLimit != 0) { + Integer cachedTimeLimit = timeLimitCache.get(toolContentId); + if (cachedTimeLimit == null || !cachedTimeLimit.equals(timeLimit)) { + timeLimitCache.put(toolContentId, timeLimit); + sendAddTimeRequest(toolContentId, + timeLimit - (cachedTimeLimit == null ? 0 : cachedTimeLimit)); + } + } + } + } catch (Exception e) { + // error caught, but carry on + LearningWebsocketServer.log.error("Error in Dokumaran worker thread", e); + } finally { + HibernateSessionManager.closeSession(); + try { + Thread.sleep(SendWorker.CHECK_INTERVAL); + } catch (InterruptedException e) { + LearningWebsocketServer.log.warn("Stopping Dokumaran worker thread"); + stopFlag = true; + } + } + } + } + }; + + private static final Logger log = Logger.getLogger(LearningWebsocketServer.class); + + private static final SendWorker sendWorker = new SendWorker(); private static final Map> websockets = new ConcurrentHashMap>(); + private static final Map timeLimitCache = new ConcurrentHashMap(); + private static IDokumaranService dokumaranService; + + static { + // run the singleton thread + LearningWebsocketServer.sendWorker.start(); + } + /** * Registeres the Learner for processing. */ @@ -75,14 +148,14 @@ * Monitor has added one more minute to the time limit. All learners will need * to add +1 minute to their countdown counters. */ - public static void sendAddOneMinuteRequest(Long toolContentID) throws JSONException, IOException { - Set toolContentWebsockets = websockets.get(toolContentID); + public static void sendAddTimeRequest(Long toolContentId, int timeLimit) throws JSONException, IOException { + Set toolContentWebsockets = websockets.get(toolContentId); if (toolContentWebsockets == null) { return; } JSONObject responseJSON = new JSONObject(); - responseJSON.put("addOneMinute", true); + responseJSON.put("addTime", timeLimit); String response = responseJSON.toString(); for (Session websocket : toolContentWebsockets) { @@ -92,24 +165,12 @@ } } - /** - * Monitor has launched time limit. All learners will need to refresh the page in order to stop showing them - * waitForTimeLimitLaunch page. - */ - public static void sendPageRefreshRequest(Long toolContentID) throws JSONException, IOException { - Set toolContentWebsockets = websockets.get(toolContentID); - if (toolContentWebsockets == null) { - return; + private static IDokumaranService getDokumaranService() { + if (dokumaranService == null) { + WebApplicationContext wac = WebApplicationContextUtils + .getRequiredWebApplicationContext(SessionManager.getServletContext()); + dokumaranService = (IDokumaranService) wac.getBean(DokumaranConstants.RESOURCE_SERVICE); } - - JSONObject responseJSON = new JSONObject(); - responseJSON.put("pageRefresh", true); - String response = responseJSON.toString(); - - for (Session websocket : toolContentWebsockets) { - if (websocket.isOpen()) { - websocket.getBasicRemote().sendText(response); - } - } + return dokumaranService; } } \ No newline at end of file Index: lams_tool_doku/web/pages/learning/learning.jsp =================================================================== diff -u -re5c28ec27d3a1d9c0c58729336662ac62bed1182 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_doku/web/pages/learning/learning.jsp (.../learning.jsp) (revision e5c28ec27d3a1d9c0c58729336662ac62bed1182) +++ lams_tool_doku/web/pages/learning/learning.jsp (.../learning.jsp) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -136,14 +136,14 @@ var input = JSON.parse(e.data); //monitor has added one minute to the total timeLimit time - if (input.addOneMinute) { + if (input.addTime) { //reload page in order to allow editing the pad again if (!$('#countdown').length) { location.reload(); } - var times = $("#countdown").countdown('getTimes'); - var secondsLeft = times[4]*3600 + times[5]*60 + times[6] + 60; + var times = $("#countdown").countdown('getTimes'), + secondsLeft = times[4]*3600 + times[5]*60 + times[6] + +addTime*60; $('#countdown').countdown('option', "until", '+' + secondsLeft + 'S'); return; Index: lams_tool_leader/src/java/org/lamsfoundation/lams/tool/leaderselection/service/LeaderselectionService.java =================================================================== diff -u -r2abc3485dc2d24ea02044a64271f3ee0d3b8c11b -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_leader/src/java/org/lamsfoundation/lams/tool/leaderselection/service/LeaderselectionService.java (.../LeaderselectionService.java) (revision 2abc3485dc2d24ea02044a64271f3ee0d3b8c11b) +++ lams_tool_leader/src/java/org/lamsfoundation/lams/tool/leaderselection/service/LeaderselectionService.java (.../LeaderselectionService.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -21,7 +21,6 @@ * **************************************************************** */ - package org.lamsfoundation.lams.tool.leaderselection.service; import java.io.IOException; @@ -68,7 +67,6 @@ import org.lamsfoundation.lams.tool.leaderselection.util.LeaderselectionConstants; import org.lamsfoundation.lams.tool.leaderselection.util.LeaderselectionException; import org.lamsfoundation.lams.tool.leaderselection.util.LeaderselectionToolContentHandler; -import org.lamsfoundation.lams.tool.leaderselection.web.actions.LearningWebsocketServer; import org.lamsfoundation.lams.tool.service.ILamsToolService; import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; @@ -157,10 +155,10 @@ public ToolOutput getToolOutput(String name, Long toolSessionId, Long learnerId) { return getLeaderselectionOutputFactory().getToolOutput(name, this, toolSessionId, learnerId); } - + @Override public List getToolOutputs(String name, Long toolContentId) { - return new ArrayList(); + return new ArrayList<>(); } @Override @@ -349,8 +347,6 @@ session.setGroupLeader(newLeader); saveOrUpdateSession(session); - - LearningWebsocketServer.sendPageRefreshRequest(toolSessionId); } @Override @@ -591,10 +587,10 @@ public boolean isGroupedActivity(long toolContentID) { return toolService.isGroupedActivity(toolContentID); } - + @Override public void auditLogStartEditingActivityInMonitor(long toolContentID) { - toolService.auditLogStartEditingActivityInMonitor(toolContentID); + toolService.auditLogStartEditingActivityInMonitor(toolContentID); } @Override Index: lams_tool_leader/src/java/org/lamsfoundation/lams/tool/leaderselection/web/actions/LearningWebsocketServer.java =================================================================== diff -u -r187836d78d83ba86212116f1bf465f567b47f133 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_leader/src/java/org/lamsfoundation/lams/tool/leaderselection/web/actions/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 187836d78d83ba86212116f1bf465f567b47f133) +++ lams_tool_leader/src/java/org/lamsfoundation/lams/tool/leaderselection/web/actions/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -1,7 +1,9 @@ package org.lamsfoundation.lams.tool.leaderselection.web.actions; import java.io.IOException; +import java.util.Iterator; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -15,6 +17,10 @@ import org.apache.log4j.Logger; import org.apache.tomcat.util.json.JSONException; import org.apache.tomcat.util.json.JSONObject; +import org.lamsfoundation.lams.tool.leaderselection.service.ILeaderselectionService; +import org.lamsfoundation.lams.tool.leaderselection.service.LeaderselectionServiceProxy; +import org.lamsfoundation.lams.util.hibernate.HibernateSessionManager; +import org.lamsfoundation.lams.web.session.SessionManager; import org.lamsfoundation.lams.web.util.AttributeNames; /** @@ -25,11 +31,66 @@ */ @ServerEndpoint("/learningWebsocket") public class LearningWebsocketServer { + /** + * A singleton which updates Learners with Leader selection. + */ + private static class SendWorker extends Thread { + private boolean stopFlag = false; + // how ofter the thread runs + private static final long CHECK_INTERVAL = 3000; - private static Logger log = Logger.getLogger(LearningWebsocketServer.class); + @Override + public void run() { + while (!stopFlag) { + try { + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + HibernateSessionManager.openSession(); + Iterator>> entryIterator = LearningWebsocketServer.websockets.entrySet() + .iterator(); + // go through activities and update registered learners with reports and vote count + while (entryIterator.hasNext()) { + Entry> entry = entryIterator.next(); + Long toolSessionId = entry.getKey(); + // if all learners left the activity, remove the obsolete mapping + Set sessionWebsockets = entry.getValue(); + if (sessionWebsockets.isEmpty()) { + entryIterator.remove(); + continue; + } - private static final Map> websockets = new ConcurrentHashMap>(); + boolean finished = LearningWebsocketServer.getLeaderService() + .getSessionBySessionId(toolSessionId).getGroupLeader() != null; + if (finished) { + LearningWebsocketServer.sendPageRefreshRequest(toolSessionId); + } + } + } catch (Exception e) { + // error caught, but carry on + LearningWebsocketServer.log.error("Error in Leader worker thread", e); + } finally { + HibernateSessionManager.closeSession(); + try { + Thread.sleep(SendWorker.CHECK_INTERVAL); + } catch (InterruptedException e) { + LearningWebsocketServer.log.warn("Stopping Leader worker thread"); + stopFlag = true; + } + } + } + } + }; + private static final Logger log = Logger.getLogger(LearningWebsocketServer.class); + + private static final SendWorker sendWorker = new SendWorker(); + private static final Map> websockets = new ConcurrentHashMap<>(); + private static ILeaderselectionService leaderService; + + static { + // run the singleton thread + LearningWebsocketServer.sendWorker.start(); + } + /** * Registeres the Learner for processing. */ @@ -72,7 +133,7 @@ } /** - * This method is called when leader has just been selected and all non-leaders should refresh their pages in order + * This method is called when leader has been selected and all non-leaders should refresh their pages in order * to see new leader name and a Finish button. */ public static void sendPageRefreshRequest(Long toolSessionId) throws JSONException, IOException { @@ -91,4 +152,12 @@ } } } + + private static ILeaderselectionService getLeaderService() { + if (LearningWebsocketServer.leaderService == null) { + LearningWebsocketServer.leaderService = LeaderselectionServiceProxy + .getLeaderselectionService(SessionManager.getServletContext()); + } + return LearningWebsocketServer.leaderService; + } } \ No newline at end of file Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/util/FinishScratchingJob.java =================================================================== diff -u -r5e5c78e2345ed588a0fd341d4e7c53ed8402b958 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/util/FinishScratchingJob.java (.../FinishScratchingJob.java) (revision 5e5c78e2345ed588a0fd341d4e7c53ed8402b958) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/util/FinishScratchingJob.java (.../FinishScratchingJob.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -23,7 +23,7 @@ * @author Andrey Balan */ public class FinishScratchingJob extends QuartzJobBean { - + private static Logger log = Logger.getLogger(FinishScratchingJob.class); @Override @@ -34,21 +34,12 @@ Map properties = context.getJobDetail().getJobDataMap(); Long toolSessionId = (Long) properties.get("toolSessionId"); ScratchieSession toolSession = scratchieService.getScratchieSessionBySessionId(toolSessionId); - + //proceed only in case the leader hasn't finished scratching on his own if (!toolSession.isScratchingFinished()) { try { //mark scratching as finished to stop showing learning.jsp to the leader scratchieService.setScratchingFinished(toolSessionId); - - // if non-leaders should not wait for notebook or burning questions to be submitted by the leader - let them see Finish button - boolean isWaitingForLeaderToSubmit = scratchieService.isWaitingForLeaderToSubmit(toolSession); - if (isWaitingForLeaderToSubmit) { - LearningWebsocketServer.sendPageRefreshRequest(toolSessionId); - } else { - LearningWebsocketServer.sendCloseRequest(toolSessionId); - } - } catch (JSONException | IOException e) { throw new RuntimeException(e); } @@ -58,7 +49,7 @@ log.debug("Scratching is finished for toolSessionId: " + toolSessionId.longValue()); } } - + protected IScratchieService getScratchieService(JobExecutionContext context) throws JobExecutionException { try { SchedulerContext sc = context.getScheduler().getContext(); Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningAction.java =================================================================== diff -u -r6b0799727ee0d8f143a76e08e80a85ffb8e73807 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningAction.java (.../LearningAction.java) (revision 6b0799727ee0d8f143a76e08e80a85ffb8e73807) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningAction.java (.../LearningAction.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -252,7 +252,7 @@ // set scratched flag for display purpose Collection items = service.getItemsWithIndicatedScratches(toolSessionId); - + // shuffling items if (scratchie.isShuffleItems()) { //items is a Set at this moment @@ -319,22 +319,22 @@ redirect.addParameter(ScratchieConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); return redirect; - // show notebook page to the leader + // show notebook page to the leader } 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 @@ -345,7 +345,8 @@ // 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; + - (System.currentTimeMillis() - toolSession.getTimeLimitLaunchedDate().getTime()) + / 1000; // change negative number or zero to 1 so it can autosubmit results secondsLeft = Math.max(1, secondsLeft); } @@ -441,11 +442,12 @@ return null; } - + /** * Stores date when user has started activity with time limit - * @throws ScratchieApplicationException - * @throws SchedulerException + * + * @throws ScratchieApplicationException + * @throws SchedulerException */ private ActionForward launchTimeLimit(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ScratchieApplicationException, SchedulerException { @@ -455,13 +457,13 @@ .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; @@ -495,17 +497,11 @@ ScratchieSession toolSession = service.getScratchieSessionBySessionId(toolSessionId); Long userUid = (Long) sessionMap.get(ScratchieConstants.ATTR_USER_UID); boolean isUserFinished = (boolean) sessionMap.get(ScratchieConstants.ATTR_USER_FINISHED); - + if (toolSession.isUserGroupLeader(userUid) && !toolSession.isScratchingFinished()) { service.setScratchingFinished(toolSessionId); } - // in case of the leader (and if he hasn't done this last time accessing showResults page) we should let all other learners - // see Next Activity button - if (toolSession.isUserGroupLeader(userUid) && !isUserFinished) { - LearningWebsocketServer.sendCloseRequest(toolSessionId); - } - // get updated score from ScratchieSession int score = toolSession.getMark(); int maxScore = (Integer) sessionMap.get(ScratchieConstants.ATTR_MAX_SCORE); Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningWebsocketServer.java =================================================================== diff -u -r7cb18d1521c69062337439737a624a60a27013d4 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 7cb18d1521c69062337439737a624a60a27013d4) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -21,6 +21,7 @@ import org.apache.tomcat.util.json.JSONObject; import org.lamsfoundation.lams.tool.scratchie.model.ScratchieAnswer; import org.lamsfoundation.lams.tool.scratchie.model.ScratchieItem; +import org.lamsfoundation.lams.tool.scratchie.model.ScratchieSession; import org.lamsfoundation.lams.tool.scratchie.service.IScratchieService; import org.lamsfoundation.lams.tool.scratchie.service.ScratchieServiceProxy; import org.lamsfoundation.lams.util.hibernate.HibernateSessionManager; @@ -56,17 +57,40 @@ while (entryIterator.hasNext()) { Entry> entry = entryIterator.next(); Long toolSessionId = entry.getKey(); - try { - send(toolSessionId); - } catch (JSONException e) { - LearningWebsocketServer.log.error("Error while building Scratchie answer JSON", e); - } // if all learners left the activity, remove the obsolete mapping Set sessionWebsockets = entry.getValue(); if (sessionWebsockets.isEmpty()) { entryIterator.remove(); LearningWebsocketServer.cache.remove(toolSessionId); + continue; } + + ScratchieSession toolSession = LearningWebsocketServer.getScratchieService() + .getScratchieSessionBySessionId(toolSessionId); + if (toolSession.isScratchingFinished()) { + boolean isWaitingForLeaderToSubmit = LearningWebsocketServer.getScratchieService() + .isWaitingForLeaderToSubmit(toolSession); + if (isWaitingForLeaderToSubmit) { + Object cache = LearningWebsocketServer.cache.get(toolSessionId); + // missing cache is a marker that we've been here before, + // so no need to send refresh again + if (cache != null) { + LearningWebsocketServer.cache.remove(toolSessionId); + LearningWebsocketServer.sendPageRefreshRequest(toolSessionId); + } + } else { + // this should make all websockets close on client side, + // so next run will not happen + LearningWebsocketServer.sendCloseRequest(toolSessionId); + } + continue; + } + + try { + send(toolSessionId); + } catch (JSONException e) { + LearningWebsocketServer.log.error("Error while building Scratchie answer JSON", e); + } } } catch (Exception e) { // error caught, but carry on @@ -106,13 +130,13 @@ if (sessionCache == null) { sessionCache = LearningWebsocketServer.cache.get(toolSessionId); if (sessionCache == null) { - sessionCache = new TreeMap>(); + sessionCache = new TreeMap<>(); LearningWebsocketServer.cache.put(toolSessionId, sessionCache); } } itemCache = sessionCache.get(itemUid); if (itemCache == null) { - itemCache = new TreeMap(); + itemCache = new TreeMap<>(); sessionCache.put(itemUid, itemCache); } } @@ -149,14 +173,14 @@ } } - private static Logger log = Logger.getLogger(LearningWebsocketServer.class); + private static final Logger log = Logger.getLogger(LearningWebsocketServer.class); private static IScratchieService scratchieService; private static final SendWorker sendWorker = new SendWorker(); // maps toolSessionId -> itemUid -> answerUid -> isCorrect - private static final Map>> cache = new ConcurrentHashMap>>(); - private static final Map> websockets = new ConcurrentHashMap>(); + private static final Map>> cache = new ConcurrentHashMap<>(); + private static final Map> websockets = new ConcurrentHashMap<>(); static { // run the singleton thread Index: lams_tool_scratchie/web/pages/learning/questionlist.jsp =================================================================== diff -u -ra7abd606c1cbadff976b75ab062f1466358f8c96 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_scratchie/web/pages/learning/questionlist.jsp (.../questionlist.jsp) (revision a7abd606c1cbadff976b75ab062f1466358f8c96) +++ lams_tool_scratchie/web/pages/learning/questionlist.jsp (.../questionlist.jsp) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -78,6 +78,7 @@ // leader finished the activity if (input.close) { + scratchieWebsocket.close(); $('#finishButton').show(); return; } Index: lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/LearningAction.java =================================================================== diff -u -r2c1a1ddb008c9257866826d3068d8ecfe0768e35 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/LearningAction.java (.../LearningAction.java) (revision 2c1a1ddb008c9257866826d3068d8ecfe0768e35) +++ lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/LearningAction.java (.../LearningAction.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -285,8 +285,6 @@ setupDTOs(request, session, scribeUser); scribeService.saveOrUpdateScribeUser(scribeUser); - LearningWebsocketServer.sendCloseRequest(session.getSessionId()); - if (session.getScribe().isShowAggregatedReports()) { setupOtherGroupReportDTO(request, session, scribeUser); } Index: lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/LearningWebsocketServer.java =================================================================== diff -u -r7cb18d1521c69062337439737a624a60a27013d4 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 7cb18d1521c69062337439737a624a60a27013d4) +++ lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -68,17 +68,27 @@ while (entryIterator.hasNext()) { Entry> entry = entryIterator.next(); Long toolSessionId = entry.getKey(); - try { - send(toolSessionId, null); - } catch (JSONException e) { - LearningWebsocketServer.log.error("Error while building Scribe report JSON", e); - } // if all learners left the activity, remove the obsolete mapping Set sessionWebsockets = entry.getValue(); if (sessionWebsockets.isEmpty()) { entryIterator.remove(); LearningWebsocketServer.cache.remove(toolSessionId); + continue; } + + ScribeSession scribeSession = LearningWebsocketServer.getScribeService() + .getSessionBySessionId(toolSessionId); + if (scribeSession.isForceComplete()) { + sendCloseRequest(toolSessionId); + continue; + } + + try { + send(toolSessionId, null); + } catch (JSONException e) { + LearningWebsocketServer.log.error("Error while building Scribe report JSON", e); + } + } } catch (Exception e) { // error caught, but carry on @@ -136,7 +146,7 @@ Long uid = storedReport.getUid(); String cachedReportText = sessionCache.reports.get(uid); String storedReportText = StringEscapeUtils.escapeHtml(storedReport.getEntryText()); - storedReportText = storedReportText != null ? storedReportText.replaceAll("\n", "
") : null; + storedReportText = storedReportText != null ? storedReportText.replaceAll("\n", "
") : null; if (cachedReportText == null ? storedReportText != null : (storedReportText == null) || (cachedReportText.length() != storedReportText.length()) || !cachedReportText.equals(storedReportText)) { @@ -261,7 +271,7 @@ // just a ping every few minutes return; } - + JSONObject requestJSON = new JSONObject(input); switch (requestJSON.getString("type")) { case "vote": Index: lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/MonitoringAction.java =================================================================== diff -u -r2f725f8ef2aa09a2663b2335bf67213074426d11 -rf06669784fcf047185a7f9c48587b3096474aa2a --- lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/MonitoringAction.java (.../MonitoringAction.java) (revision 2f725f8ef2aa09a2663b2335bf67213074426d11) +++ lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/MonitoringAction.java (.../MonitoringAction.java) (revision f06669784fcf047185a7f9c48587b3096474aa2a) @@ -147,8 +147,6 @@ session.setForceComplete(true); scribeService.saveOrUpdateScribeSession(session); - LearningWebsocketServer.sendCloseRequest(session.getSessionId()); - ScribeDTO scribeDTO = setupScribeDTO(session.getScribe()); request.setAttribute("monitoringDTO", scribeDTO); request.setAttribute("contentFolderID", monForm.getContentFolderID());