Index: lams_common/src/java/org/lamsfoundation/lams/web/controller/AbstractTimeLimitWebsocketServer.java =================================================================== diff -u -r4352b39a6fd8ac3ccf9c75d6284179c9f004bf4e -r78cd74a85c22416138e9dd68dfb7e246c29b4c35 --- lams_common/src/java/org/lamsfoundation/lams/web/controller/AbstractTimeLimitWebsocketServer.java (.../AbstractTimeLimitWebsocketServer.java) (revision 4352b39a6fd8ac3ccf9c75d6284179c9f004bf4e) +++ lams_common/src/java/org/lamsfoundation/lams/web/controller/AbstractTimeLimitWebsocketServer.java (.../AbstractTimeLimitWebsocketServer.java) (revision 78cd74a85c22416138e9dd68dfb7e246c29b4c35) @@ -1,54 +1,45 @@ package org.lamsfoundation.lams.web.controller; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +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; + +import javax.websocket.CloseReason; +import javax.websocket.CloseReason.CloseCodes; +import javax.websocket.OnClose; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpointConfig; import java.io.IOException; import java.lang.reflect.Method; import java.time.Duration; import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import javax.websocket.CloseReason; -import javax.websocket.CloseReason.CloseCodes; -import javax.websocket.OnClose; -import javax.websocket.OnOpen; -import javax.websocket.Session; -import javax.websocket.server.ServerEndpointConfig; - -import org.apache.log4j.Logger; -import org.lamsfoundation.lams.usermanagement.User; -import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; -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; - -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; - /** - * Controls activity time limits. - * Can be used in tools to set relative and absolute time limits for learners. + * Controls activity time limits. Can be used in tools to set relative and absolute time limits for learners. * * @author Marcin Cieslak */ public abstract class AbstractTimeLimitWebsocketServer extends ServerEndpointConfig.Configurator { /** - * By default Endpoint instances are created for each request. - * We need persistent data to cache time settings. - * We can not use static fields as each subclass needs to have own set of fields. - * This class registers and retrieves Endpoints as singletons. + * By default Endpoint instances are created for each request. We need persistent data to cache time settings. We + * can not use static fields as each subclass needs to have own set of fields. This class registers and retrieves + * Endpoints as singletons. */ public static class EndpointConfigurator extends ServerEndpointConfig.Configurator { private static final Map TIME_LIMIT_ENDPOINT_INSTANCES = new ConcurrentHashMap<>(); @@ -58,8 +49,8 @@ public T getEndpointInstance(Class endpointClass) throws InstantiationException { // is there an instance already - AbstractTimeLimitWebsocketServer instance = AbstractTimeLimitWebsocketServer - .getInstance(endpointClass.getName()); + AbstractTimeLimitWebsocketServer instance = AbstractTimeLimitWebsocketServer.getInstance( + endpointClass.getName()); if (instance == null) { // every subclass must have a static getInstance() method try { @@ -110,7 +101,9 @@ // do nothing as server is probably shutting down and we could not obtain Hibernate session } catch (Exception e) { // error caught, but carry on - log.error("Error in Assessment worker thread", e); + log.error("Error in time limit worker thread", e); + // extra details for debugging + e.printStackTrace(); } finally { HibernateSessionManager.closeSession(); } @@ -142,8 +135,8 @@ executor.scheduleAtFixedRate(sendWorker, 0, CHECK_INTERVAL, TimeUnit.SECONDS); if (userManagementService == null) { - WebApplicationContext wac = WebApplicationContextUtils - .getRequiredWebApplicationContext(SessionManager.getServletContext()); + WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext( + SessionManager.getServletContext()); userManagementService = (IUserManagementService) wac.getBean("userManagementService"); } } @@ -160,8 +153,8 @@ */ protected static void registerInstance(AbstractTimeLimitWebsocketServer instance) { String endpointClassName = instance.getClass().getName(); - AbstractTimeLimitWebsocketServer existingInstance = EndpointConfigurator.TIME_LIMIT_ENDPOINT_INSTANCES - .get(endpointClassName); + AbstractTimeLimitWebsocketServer existingInstance = EndpointConfigurator.TIME_LIMIT_ENDPOINT_INSTANCES.get( + endpointClassName); if (existingInstance != null) { throw new IllegalStateException("Endpoint " + endpointClassName + " already existing in the pool."); } @@ -173,7 +166,7 @@ */ protected boolean processActivity(long toolContentId, Collection websockets) throws IOException { // if all learners left the activity, remove the obsolete mapping - if (websockets.isEmpty()) { + if (websockets == null || websockets.isEmpty()) { timeCaches.remove(toolContentId); return false; } @@ -186,7 +179,8 @@ } // get only currently active users, not all activity participants - Collection userIds = websockets.stream().filter(w -> w.getUserProperties() != null) + Collection userIds = websockets.stream() + .filter(w -> w.getUserProperties() != null && w.getUserProperties().get("userId") != null) .collect(Collectors.mapping(w -> (Integer) w.getUserProperties().get("userId"), Collectors.toSet())); // get activity data from DB TimeCache existingTimeSettings = getExistingTimeSettings(toolContentId, userIds); @@ -198,7 +192,8 @@ timeCache.relativeTimeLimit = existingTimeSettings.relativeTimeLimit; updateAllUsers = true; } - if (timeCache.absoluteTimeLimit == null ? existingTimeSettings.absoluteTimeLimit != null + if (timeCache.absoluteTimeLimit == null + ? existingTimeSettings.absoluteTimeLimit != null : !timeCache.absoluteTimeLimit.equals(existingTimeSettings.absoluteTimeLimit)) { timeCache.absoluteTimeLimit = existingTimeSettings.absoluteTimeLimit; updateAllUsers = true; @@ -255,8 +250,8 @@ */ @OnOpen public void registerUser(Session websocket) throws IOException { - Long toolContentId = Long - .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); + Long toolContentId = Long.valueOf( + websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); String login = websocket.getUserPrincipal().getName(); Integer userId = getUserId(login); if (userId == null) { @@ -289,21 +284,18 @@ */ @OnClose public void unregisterUser(Session websocket, CloseReason reason) { - Long toolContentID = Long - .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); + Long toolContentID = Long.valueOf( + websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); Set sessionWebsockets = websockets.get(toolContentID); if (sessionWebsockets != null) { websockets.get(toolContentID).remove(websocket); if (log.isDebugEnabled()) { // If there was something wrong with the connection, put it into logs. log.debug("User " + websocket.getUserPrincipal().getName() + " left activity with tool content ID: " - + toolContentID - + (!(reason.getCloseCode().equals(CloseCodes.GOING_AWAY) - || reason.getCloseCode().equals(CloseCodes.NORMAL_CLOSURE)) - ? ". Abnormal close. Code: " + reason.getCloseCode() + ". Reason: " - + reason.getReasonPhrase() - : "")); + + toolContentID + (!(reason.getCloseCode().equals(CloseCodes.GOING_AWAY) + || reason.getCloseCode().equals(CloseCodes.NORMAL_CLOSURE)) ? ". Abnormal close. Code: " + + reason.getCloseCode() + ". Reason: " + reason.getReasonPhrase() : "")); } } } Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningWebsocketServer.java =================================================================== diff -u -r5a579db63172ae32b83984fb7cde499217bbcdd4 -r78cd74a85c22416138e9dd68dfb7e246c29b4c35 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 5a579db63172ae32b83984fb7cde499217bbcdd4) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 78cd74a85c22416138e9dd68dfb7e246c29b4c35) @@ -1,10 +1,5 @@ package org.lamsfoundation.lams.tool.assessment.web.controller; -import java.time.LocalDateTime; -import java.util.Collection; - -import javax.websocket.server.ServerEndpoint; - import org.apache.log4j.Logger; import org.lamsfoundation.lams.tool.assessment.AssessmentConstants; import org.lamsfoundation.lams.tool.assessment.model.Assessment; @@ -17,6 +12,10 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; +import javax.websocket.server.ServerEndpoint; +import java.time.LocalDateTime; +import java.util.Collection; + /** * Controls Assessment time limits * @@ -31,8 +30,8 @@ public LearningWebsocketServer() { if (assessmentService == null) { - WebApplicationContext wac = WebApplicationContextUtils - .getRequiredWebApplicationContext(SessionManager.getServletContext()); + WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext( + SessionManager.getServletContext()); assessmentService = (IAssessmentService) wac.getBean(AssessmentConstants.ASSESSMENT_SERVICE); } } @@ -47,28 +46,35 @@ */ @Override protected TimeCache getExistingTimeSettings(long toolContentId, Collection userIds) { - Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); TimeCache existingTimeSettings = new TimeCache(); + Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); + if (assessment == null) { + return existingTimeSettings; + } existingTimeSettings.absoluteTimeLimit = assessment.getAbsoluteTimeLimit(); existingTimeSettings.relativeTimeLimit = assessment.getRelativeTimeLimit() * 60; existingTimeSettings.timeLimitAdjustment = assessment.getTimeLimitAdjustments(); - for (Integer userId : userIds) { - AssessmentResult result = null; - if (assessment.isUseSelectLeaderToolOuput()) { - AssessmentUser user = assessmentService.getUserByIdAndContent(userId.longValue(), toolContentId); - AssessmentUser leader = user.getSession().getGroupLeader(); - if (leader != null) { - // if team leader is enabled, show consistent timer for all group members - result = assessmentService.getLastAssessmentResult(assessment.getUid(), leader.getUserId()); + if (userIds != null) { + for (Integer userId : userIds) { + AssessmentResult result = null; + if (assessment.isUseSelectLeaderToolOuput()) { + AssessmentUser user = assessmentService.getUserByIdAndContent(userId.longValue(), toolContentId); + if (user != null) { + AssessmentUser leader = user.getSession().getGroupLeader(); + if (leader != null) { + // if team leader is enabled, show consistent timer for all group members + result = assessmentService.getLastAssessmentResult(assessment.getUid(), leader.getUserId()); + } + } + } else { + result = assessmentService.getLastAssessmentResult(assessment.getUid(), userId.longValue()); } - } else { - result = assessmentService.getLastAssessmentResult(assessment.getUid(), userId.longValue()); - } - if (result != null && result.getTimeLimitLaunchedDate() != null) { - existingTimeSettings.timeLimitLaunchedDate.put(userId, result.getTimeLimitLaunchedDate()); + if (result != null && result.getTimeLimitLaunchedDate() != null) { + existingTimeSettings.timeLimitLaunchedDate.put(userId, result.getTimeLimitLaunchedDate()); + } } } @@ -81,8 +87,8 @@ } public static LearningWebsocketServer getInstance() { - LearningWebsocketServer result = (LearningWebsocketServer) AbstractTimeLimitWebsocketServer - .getInstance(LearningWebsocketServer.class.getName()); + LearningWebsocketServer result = (LearningWebsocketServer) AbstractTimeLimitWebsocketServer.getInstance( + LearningWebsocketServer.class.getName()); if (result == null) { result = new LearningWebsocketServer(); AbstractTimeLimitWebsocketServer.registerInstance(result);