Index: lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20210506.sql =================================================================== diff -u -r8967cf285e8f080454bfeb80ea34e9022041ee34 -r89350b37c57bdf618cc90c09d792ce7d865ad4da --- lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20210506.sql (.../patch20210506.sql) (revision 8967cf285e8f080454bfeb80ea34e9022041ee34) +++ lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20210506.sql (.../patch20210506.sql) (revision 89350b37c57bdf618cc90c09d792ce7d865ad4da) @@ -13,7 +13,8 @@ user_id BIGINT, selected_option TINYINT UNSIGNED, PRIMARY KEY (uid), - INDEX IDX_lams_discussion_sentiment_burning_question_uid (burning_question_uid), + INDEX IDX_lams_discussion_sentiment_1 (burning_question_uid), + UNIQUE INDEX UQ_lams_discussion_sentiment_2 (lesson_id, tool_content_id, burning_question_id, user_id), CONSTRAINT FK_lams_discussion_sentiment_1 FOREIGN KEY (lesson_id) REFERENCES lams_lesson (lesson_id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT FK_lams_discussion_sentiment_2 FOREIGN KEY (tool_content_id) REFERENCES lams_tool_content (tool_content_id) Index: lams_common/src/java/org/lamsfoundation/lams/learning/service/ILearnerService.java =================================================================== diff -u -ra05bb8ff92c659cc340b037de664fd0d9b103c96 -r89350b37c57bdf618cc90c09d792ce7d865ad4da --- lams_common/src/java/org/lamsfoundation/lams/learning/service/ILearnerService.java (.../ILearnerService.java) (revision a05bb8ff92c659cc340b037de664fd0d9b103c96) +++ lams_common/src/java/org/lamsfoundation/lams/learning/service/ILearnerService.java (.../ILearnerService.java) (revision 89350b37c57bdf618cc90c09d792ce7d865ad4da) @@ -200,5 +200,7 @@ void createCommandForLearners(Long toolContentId, Collection userIds, String jsonCommand); + void createCommandForLessonLearners(Long toolContentId, String jsonCommand); + boolean isLearnerStartedLessonByContentId(int userId, long toolContentId); } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/command/CommandWebsocketServer.java =================================================================== diff -u -rcd4af70336301a4c551cf28afbfc5534e3e6e7c9 -r89350b37c57bdf618cc90c09d792ce7d865ad4da --- lams_learning/src/java/org/lamsfoundation/lams/learning/command/CommandWebsocketServer.java (.../CommandWebsocketServer.java) (revision cd4af70336301a4c551cf28afbfc5534e3e6e7c9) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/command/CommandWebsocketServer.java (.../CommandWebsocketServer.java) (revision 89350b37c57bdf618cc90c09d792ce7d865ad4da) @@ -9,14 +9,14 @@ import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; -import javax.websocket.CloseReason; import javax.websocket.OnClose; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import org.apache.log4j.Logger; import org.lamsfoundation.lams.learning.command.model.Command; +import org.lamsfoundation.lams.learning.discussion.service.IDiscussionSentimentService; import org.lamsfoundation.lams.learning.service.ILearnerFullService; import org.lamsfoundation.lams.learning.service.ILearnerService; import org.lamsfoundation.lams.util.hibernate.HibernateSessionManager; @@ -35,6 +35,8 @@ private static ILearnerFullService learnerService; + private static IDiscussionSentimentService discussionSentimentService; + /** * A singleton which updates Learners with messages and commands. */ @@ -136,6 +138,8 @@ String login = websocket.getUserPrincipal().getName(); sessionWebsockets.put(login, websocket); + + discussionSentimentService.restartDiscussionForLearner(lessonId, login); } /** Index: lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/dao/IDiscussionSentimentDAO.java =================================================================== diff -u -r8967cf285e8f080454bfeb80ea34e9022041ee34 -r89350b37c57bdf618cc90c09d792ce7d865ad4da --- lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/dao/IDiscussionSentimentDAO.java (.../IDiscussionSentimentDAO.java) (revision 8967cf285e8f080454bfeb80ea34e9022041ee34) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/dao/IDiscussionSentimentDAO.java (.../IDiscussionSentimentDAO.java) (revision 89350b37c57bdf618cc90c09d792ce7d865ad4da) @@ -10,4 +10,6 @@ DiscussionSentimentVote getActiveDiscussion(long lessonId); Map getDiscussionAggregatedVotes(long lessonId, long toolContentId, Long burningQuestionUid); + + DiscussionSentimentVote getDiscussionVote(long lessonId, long toolContentId, Long burningQuestionUid, int userId); } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/dao/hibernate/DiscussionSentimentDAO.java =================================================================== diff -u -r8967cf285e8f080454bfeb80ea34e9022041ee34 -r89350b37c57bdf618cc90c09d792ce7d865ad4da --- lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/dao/hibernate/DiscussionSentimentDAO.java (.../DiscussionSentimentDAO.java) (revision 8967cf285e8f080454bfeb80ea34e9022041ee34) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/dao/hibernate/DiscussionSentimentDAO.java (.../DiscussionSentimentDAO.java) (revision 89350b37c57bdf618cc90c09d792ce7d865ad4da) @@ -32,4 +32,18 @@ return votes.stream().filter(v -> v.getUserId() != null).collect( Collectors.groupingBy(DiscussionSentimentVote::getSelectedOption, TreeMap::new, Collectors.counting())); } + + @Override + public DiscussionSentimentVote getDiscussionVote(long lessonId, long toolContentId, Long burningQuestionUid, + int userId) { + Map properties = new HashMap<>(); + properties.put("lessonId", lessonId); + properties.put("toolContentId", toolContentId); + if (burningQuestionUid != null) { + properties.put("burningQuestionUid", burningQuestionUid); + } + properties.put("userId", userId); + List votes = findByProperties(DiscussionSentimentVote.class, properties); + return votes.isEmpty() ? null : votes.get(0); + } } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/service/DiscussionSentimentService.java =================================================================== diff -u --- lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/service/DiscussionSentimentService.java (revision 0) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/service/DiscussionSentimentService.java (revision 89350b37c57bdf618cc90c09d792ce7d865ad4da) @@ -0,0 +1,120 @@ +package org.lamsfoundation.lams.learning.discussion.service; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.lamsfoundation.lams.learning.discussion.dao.IDiscussionSentimentDAO; +import org.lamsfoundation.lams.learning.discussion.model.DiscussionSentimentVote; +import org.lamsfoundation.lams.learning.service.ILearnerService; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; + +public class DiscussionSentimentService implements IDiscussionSentimentService { + private IDiscussionSentimentDAO discussionSentimentDAO; + + private ILearnerService learnerService; + + private IUserManagementService userManagementService; + + private Map> cachedActiveDiscussionTokens = new ConcurrentHashMap<>(); + private static long ACTIVE_DISCUSSION_TOKEN_REFRESH_PERIOD = 5 * 1000; + + @Override + public void startDiscussion(long lessonId, long toolContentId, Long burningQuestionUid) { + stopDiscussionInternal(lessonId); + + DiscussionSentimentVote activeDiscussionToken = new DiscussionSentimentVote(lessonId, toolContentId); + activeDiscussionToken.setBurningQuestionUid(burningQuestionUid); + discussionSentimentDAO.insert(activeDiscussionToken); + + cachedActiveDiscussionTokens.put(lessonId, Map.of(System.currentTimeMillis(), activeDiscussionToken)); + + learnerService.createCommandForLessonLearners(toolContentId, "discussion-start"); + } + + @Override + public void stopDiscussion(long lessonId) { + cachedActiveDiscussionTokens.remove(lessonId); + + DiscussionSentimentVote activeDiscussionToken = stopDiscussionInternal(lessonId); + if (activeDiscussionToken != null) { + learnerService.createCommandForLessonLearners(activeDiscussionToken.getToolContentId(), "discussion-stop"); + } + } + + @Override + public void restartDiscussionForLearner(long lessonId, String login) { + DiscussionSentimentVote cachedActiveDiscussionToken = getCachedActiveDiscussion(lessonId); + if (cachedActiveDiscussionToken == null) { + return; + } + User learner = userManagementService.getUserByLogin(login); + if (learner == null) { + return; + } + + DiscussionSentimentVote learnerVote = discussionSentimentDAO.getDiscussionVote(lessonId, + cachedActiveDiscussionToken.getToolContentId(), cachedActiveDiscussionToken.getBurningQuestionUid(), + learner.getUserId()); + + learnerService.createCommandForLearner(lessonId, login, + "discussion-start" + (learnerVote == null ? "" : "-vote-" + learnerVote.getSelectedOption())); + } + + @Override + public void addDiscussionVoteForLearner(long lessonId, int userId, int selectedOption) { + DiscussionSentimentVote cachedActiveDiscussionToken = getCachedActiveDiscussion(lessonId); + if (cachedActiveDiscussionToken == null) { + return; + } + + DiscussionSentimentVote learnerVote = discussionSentimentDAO.getDiscussionVote(lessonId, + cachedActiveDiscussionToken.getToolContentId(), cachedActiveDiscussionToken.getBurningQuestionUid(), + userId); + if (learnerVote != null) { + discussionSentimentDAO.delete(learnerVote); + } + + learnerVote = new DiscussionSentimentVote(lessonId, cachedActiveDiscussionToken.getToolContentId()); + learnerVote.setBurningQuestionUid(cachedActiveDiscussionToken.getBurningQuestionUid()); + learnerVote.setUserId(userId); + learnerVote.setSelectedOption(selectedOption); + + discussionSentimentDAO.insert(learnerVote); + } + + @Override + public Map getDiscussionAggregatedVotes(long lessonId, long toolContentId, Long burningQuestionUid) { + return discussionSentimentDAO.getDiscussionAggregatedVotes(lessonId, toolContentId, burningQuestionUid); + } + + private DiscussionSentimentVote getCachedActiveDiscussion(long lessonId) { + Map cachedActiveDiscussion = cachedActiveDiscussionTokens.get(lessonId); + + if (cachedActiveDiscussion != null && System.currentTimeMillis() + - cachedActiveDiscussion.keySet().iterator().next() < ACTIVE_DISCUSSION_TOKEN_REFRESH_PERIOD) { + return cachedActiveDiscussion.values().iterator().next(); + } + + DiscussionSentimentVote activeDiscussionToken = discussionSentimentDAO.getActiveDiscussion(lessonId); + if (activeDiscussionToken == null) { + if (cachedActiveDiscussion != null) { + cachedActiveDiscussionTokens.remove(lessonId); + } + return null; + } + + cachedActiveDiscussion = Map.of(System.currentTimeMillis(), activeDiscussionToken); + cachedActiveDiscussionTokens.put(lessonId, cachedActiveDiscussion); + + return activeDiscussionToken; + } + + private DiscussionSentimentVote stopDiscussionInternal(long lessonId) { + DiscussionSentimentVote currentActiveDiscussionToken = discussionSentimentDAO.getActiveDiscussion(lessonId); + if (currentActiveDiscussionToken != null) { + discussionSentimentDAO.delete(currentActiveDiscussionToken); + } + return currentActiveDiscussionToken; + } +} \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/service/IDiscussionSentimentService.java =================================================================== diff -u --- lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/service/IDiscussionSentimentService.java (revision 0) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/discussion/service/IDiscussionSentimentService.java (revision 89350b37c57bdf618cc90c09d792ce7d865ad4da) @@ -0,0 +1,17 @@ +package org.lamsfoundation.lams.learning.discussion.service; + +import java.util.Map; + +public interface IDiscussionSentimentService { + + void startDiscussion(long lessonId, long toolContentId, Long burningQuestionUid); + + void stopDiscussion(long lessonId); + + void restartDiscussionForLearner(long lessonId, String login); + + void addDiscussionVoteForLearner(long lessonId, int userId, int selectedOption); + + Map getDiscussionAggregatedVotes(long lessonId, long toolContentId, Long burningQuestionUid); + +} \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/service/LearnerService.java =================================================================== diff -u -r423858087ec4d185fb33c3f569fc7ede1d43001a -r89350b37c57bdf618cc90c09d792ce7d865ad4da --- lams_learning/src/java/org/lamsfoundation/lams/learning/service/LearnerService.java (.../LearnerService.java) (revision 423858087ec4d185fb33c3f569fc7ede1d43001a) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/service/LearnerService.java (.../LearnerService.java) (revision 89350b37c57bdf618cc90c09d792ce7d865ad4da) @@ -1443,6 +1443,18 @@ } } + @SuppressWarnings("unchecked") + public void createCommandForLessonLearners(Long toolContentId, String jsonCommand) { + // find lesson for given tool content ID + Long lessonId = lessonService.getLessonByToolContentId(toolContentId).getLessonId(); + + Collection learners = lessonService.getActiveLessonLearners(lessonId); + for (User learner : learners) { + Command command = new Command(lessonId, learner.getLogin(), jsonCommand); + commandDAO.insert(command); + } + } + @Override public List getCommandsForLesson(Long lessonId, Date laterThan) { return commandDAO.getNewCommands(lessonId, laterThan);