Index: lams_central/conf/security/Owasp.CsrfGuard.properties =================================================================== diff -u -r8d372a4476d25a498b5af8c04e82576edc7b0976 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_central/conf/security/Owasp.CsrfGuard.properties (.../Owasp.CsrfGuard.properties) (revision 8d372a4476d25a498b5af8c04e82576edc7b0976) +++ lams_central/conf/security/Owasp.CsrfGuard.properties (.../Owasp.CsrfGuard.properties) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -143,6 +143,8 @@ org.owasp.csrfguard.protected.dokuAuthoringDefineLater=/lams/tool/ladoku11/authoring/definelater.do org.owasp.csrfguard.protected.dokuMonitoringUpdateLearnerMark=/lams/tool/ladoku11/monitoring/updateLearnerMark.do org.owasp.csrfguard.protected.dokuMonitoringChangeLeader=/lams/tool/ladoku11/monitoring/changeLeaderForGroup.do +org.owasp.csrfguard.protected.dokuMonitoringUpdateTimeLimit=/lams/tool/ladoku11/monitoring/updateTimeLimit.do +org.owasp.csrfguard.protected.dokuMonitoringUpdateIndividualTimeLimit=/lams/tool/ladoku11/monitoring/updateIndividualTimeLimit.do org.owasp.csrfguard.protected.forumAuthoringSave=/lams/tool/lafrum11/authoring/update.do org.owasp.csrfguard.protected.forumAuthoringDefineLater=/lams/tool/lafrum11/authoring/definelater.do Index: lams_common/src/java/org/lamsfoundation/lams/web/controller/AbstractTimeLimitWebsocketServer.java =================================================================== diff -u -r7b189d6353baf83e0218dd064ef543d53b555a99 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_common/src/java/org/lamsfoundation/lams/web/controller/AbstractTimeLimitWebsocketServer.java (.../AbstractTimeLimitWebsocketServer.java) (revision 7b189d6353baf83e0218dd064ef543d53b555a99) +++ lams_common/src/java/org/lamsfoundation/lams/web/controller/AbstractTimeLimitWebsocketServer.java (.../AbstractTimeLimitWebsocketServer.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -4,6 +4,7 @@ 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; @@ -299,6 +300,24 @@ } } + protected static Long getSecondsLeft(AbstractTimeLimitWebsocketServer instance, long toolContentId, int userId, + boolean getCurrentLaunchedDateByDefault) { + // get time from cache + TimeCache timeCache = instance.timeCaches.get(toolContentId); + if (timeCache == null) { + // if the requested learner is first to enter the activity, there is no cache yet, + // so fetch data straight from DB + timeCache = instance.getExistingTimeSettings(toolContentId, Arrays.asList(userId)); + LocalDateTime timeLimitLaunchedDate = timeCache.timeLimitLaunchedDate.get(userId); + if (timeLimitLaunchedDate == null && getCurrentLaunchedDateByDefault) { + // temporarily set launch date to now + // it will properly set by next sendWorker run + timeCache.timeLimitLaunchedDate.put(userId, LocalDateTime.now()); + } + } + return instance.getSecondsLeft(timeCache, userId); + } + protected Long getSecondsLeft(TimeCache timeCache, int userId) { if (timeCache.relativeTimeLimit == 0 && timeCache.absoluteTimeLimit == null) { // no time limit is set at all @@ -312,6 +331,8 @@ if (timeCache.absoluteTimeLimit != null) { // the limit is same for everyone finish = timeCache.absoluteTimeLimit; + } else if (launchedDate == null) { + return null; } else { // the limit is his entry plus relative time limit finish = launchedDate.plusSeconds(timeCache.relativeTimeLimit); Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -r7b189d6353baf83e0218dd064ef543d53b555a99 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 7b189d6353baf83e0218dd064ef543d53b555a99) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -328,7 +328,7 @@ @Override public boolean checkTimeLimitExceeded(long toolContentId, int userId) { Long secondsLeft = LearningWebsocketServer.getSecondsLeft(toolContentId, userId); - return secondsLeft != null && secondsLeft.equals(0); + return secondsLeft != null && secondsLeft.equals(0L); } @Override Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningWebsocketServer.java =================================================================== diff -u -r7b189d6353baf83e0218dd064ef543d53b555a99 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 7b189d6353baf83e0218dd064ef543d53b555a99) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -40,7 +40,10 @@ protected Logger getLog() { return log; } - + + /** + * Gets settings from DB. + */ @Override protected TimeCache getExistingTimeSettings(long toolContentId, Collection userIds) { Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); @@ -76,9 +79,11 @@ return result; } + /** + * Fetches or creates a singleton of this websocket server. + */ public static Long getSecondsLeft(long toolContentId, int userId) { LearningWebsocketServer instance = LearningWebsocketServer.getInstance(); - TimeCache timeCache = instance.timeCaches.get(toolContentId); - return timeCache == null ? null : instance.getSecondsLeft(timeCache, userId); + return AbstractTimeLimitWebsocketServer.getSecondsLeft(instance, toolContentId, userId, true); } } \ No newline at end of file Index: lams_tool_doku/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r2ce1ca7a4995e15743c5800fdd789868b0236836 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 2ce1ca7a4995e15743c5800fdd789868b0236836) +++ lams_tool_doku/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -103,6 +103,25 @@ monitoring.summary.gallery.walk.start.confirm = Are you sure you want to finish collaboration phase and start Gallery Walk? monitoring.summary.gallery.walk.finish = Finish Gallery Walk monitoring.summary.gallery.walk.finish.confirm = Are you sure you want to finish Gallery Walk? +label.monitoring.summary.time.limit = Timing limits +label.monitoring.summary.time.limit.relative = Duration relative to from student start +label.monitoring.summary.time.limit.relative.desc = Set the duration in minutes that the learners will have to have to complete the doKu. The duration will apply from the moment each learner begins the doKu. For example, if set to 5 minutes, all learners will have 5 minutes from the moment each of them start the doKu. +label.monitoring.summary.time.limit.absolute = Duration for all learners +label.monitoring.summary.time.limit.absolute.desc = Set the number of minutes to finish the doKu for all learners. This duration applies to all learners regardless when each of them starts the doKu. Set the minutes and click Start for the time to apply. +label.monitoring.summary.time.limit.individual = Individual extensions +label.monitoring.summary.time.limit.individual.desc = Apply duration extensions for individual learners. Search the learner and then apply extra time. +label.monitoring.summary.time.limit.minutes = minutes +label.monitoring.summary.time.limit.enabled = Enabled +label.monitoring.summary.time.limit.disabled = Disabled +label.monitoring.summary.time.limit.start = Start +label.monitoring.summary.time.limit.cancel = Cancel +label.monitoring.summary.time.limit.plus.minute.1 = Plus 1 minute +label.monitoring.summary.time.limit.plus.minute.5 = Plus 5 minutes +label.monitoring.summary.time.limit.minus.minute.1 = Minus 1 minute +label.monitoring.summary.time.limit.minus.minute.5 = Minus 5 minutes +label.monitoring.summary.time.limit.finish.now = Finish now +label.monitoring.summary.time.limit.finish.now.confirm = Are you sure you want to make all learners finish their work right now? +label.monitoring.summary.time.limit.individual.placeholder = Type name label.gallery.walk = Gallery Walk label.gallery.walk.wait.finish = The Gallery Walk activity has not been set as completed by the teacher. If you have completed, please contact the teacher for assistance. label.rating = Rating @@ -112,7 +131,6 @@ label.gallery.walk.your.group = (Your group) label.gallery.walk.ratings.header = Ratings label.gallery.walk.wait.start = As part of this activity, the teacher has planned for you to view and maybe comment on the work of other teams. The activity of seeing and commenting on others' work is called Gallery Walk. Please wait for the teacher to start the Gallery Walk. If this is taking too long, please contact the teacher for assistance. -label.time.limit.manual.start = Start time limit manually on monitoring screen label.gallery.walk.wait.start.preview = Continue to Gallery Walk. Since you are in preview mode, you can do it immediately. Regular learners get this message would need to wait for a teacher to start Gallery Walk on monitoring screen. label.gallery.walk.wait.finish.preview = Continue to Gallery Walk summary. Since you are in preview mode, you can do it immediately. Regular learners get this message would need to wait for a teacher to finish Gallery Walk on monitoring screen. label.gallery.walk.preview = You are in preview mode. You only see your own group. Regular learners would see other groups' work and would be able to comment on it and rate it. Index: lams_tool_doku/conf/language/lams/ApplicationResources_en_AU.properties =================================================================== diff -u -r06890c4feb9daa16dcea27bc2d38a31c42b33a59 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 06890c4feb9daa16dcea27bc2d38a31c42b33a59) +++ lams_tool_doku/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -112,7 +112,6 @@ label.gallery.walk.your.group = (Your group) label.gallery.walk.ratings.header = Ratings label.gallery.walk.wait.start = As part of this activity, the teacher has planned for you to view and maybe comment on the work of other teams. The activity of seeing and commenting on others' work is called Gallery Walk. Please wait for the teacher to start the Gallery Walk. If this is taking too long, please contact the teacher for assistance. -label.time.limit.manual.start = Start time limit manually on monitoring screen label.gallery.walk.wait.start.preview = Continue to Gallery Walk. Since you are in preview mode, you can do it immediately. Regular learners get this message would need to wait for a teacher to start Gallery Walk on monitoring screen. label.gallery.walk.wait.finish.preview = Continue to Gallery Walk summary. Since you are in preview mode, you can do it immediately. Regular learners get this message would need to wait for a teacher to finish Gallery Walk on monitoring screen. label.gallery.walk.preview = You are in preview mode. You only see your own group. Regular learners would see other groups' work and would be able to comment on it and rate it. Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/DokumaranConstants.java =================================================================== diff -u -r148241ef71d53c340db44d23458316416e8f3c0f -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/DokumaranConstants.java (.../DokumaranConstants.java) (revision 148241ef71d53c340db44d23458316416e8f3c0f) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/DokumaranConstants.java (.../DokumaranConstants.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -26,7 +26,7 @@ public class DokumaranConstants { public static final String TOOL_SIGNATURE = "ladoku11"; - public static final String RESOURCE_SERVICE = "dokumaranService"; + public static final String DOKUMARAN_SERVICE = "dokumaranService"; public static final String TOOL_CONTENT_HANDLER_NAME = "dokumaranToolContentHandler"; Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dao/DokumaranUserDAO.java =================================================================== diff -u -r3b951be8d201e41a829d079895d30c9b51fd1dce -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dao/DokumaranUserDAO.java (.../DokumaranUserDAO.java) (revision 3b951be8d201e41a829d079895d30c9b51fd1dce) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dao/DokumaranUserDAO.java (.../DokumaranUserDAO.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -33,8 +33,6 @@ DokumaranUser getUserByUserIDAndContentID(Long userId, Long contentId); - DokumaranUser getUserByUserIDAndContentIDViaSession(Long userId, Long contentId); - List getBySessionID(Long sessionId); /** Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dao/hibernate/DokumaranUserDAOHibernate.java =================================================================== diff -u -re743566518bd5c53d6574987565cfb8581a92c66 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dao/hibernate/DokumaranUserDAOHibernate.java (.../DokumaranUserDAOHibernate.java) (revision e743566518bd5c53d6574987565cfb8581a92c66) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dao/hibernate/DokumaranUserDAOHibernate.java (.../DokumaranUserDAOHibernate.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -35,8 +35,6 @@ private static final String FIND_BY_USER_ID_CONTENT_ID = "from " + DokumaranUser.class.getName() + " as u where u.userId =? and u.dokumaran.contentId=?"; - private static final String FIND_BY_USER_ID_CONTENT_ID_VIA_SESSION = "from " + DokumaranUser.class.getName() - + " as u where u.userId =? and u.session.dokumaran.contentId=?"; private static final String FIND_BY_USER_ID_SESSION_ID = "from " + DokumaranUser.class.getName() + " as u where u.userId =? and u.session.sessionId=?"; private static final String FIND_BY_SESSION_ID = "from " + DokumaranUser.class.getName() @@ -63,15 +61,6 @@ } @Override - public DokumaranUser getUserByUserIDAndContentIDViaSession(Long userId, Long contentId) { - List list = this.doFind(FIND_BY_USER_ID_CONTENT_ID_VIA_SESSION, new Object[] { userId, contentId }); - if (list == null || list.size() == 0) { - return null; - } - return (DokumaranUser) list.get(0); - } - - @Override @SuppressWarnings("unchecked") public List getBySessionID(Long sessionId) { return this.doFind(FIND_BY_SESSION_ID, sessionId); Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dbupdates/patch20210210.sql =================================================================== diff -u --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dbupdates/patch20210210.sql (revision 0) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dbupdates/patch20210210.sql (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -0,0 +1,36 @@ +-- Turn off autocommit, so nothing is committed if there is an error +SET AUTOCOMMIT = 0; +SET FOREIGN_KEY_CHECKS=0; +-- Put all sql statements below here + +--LDEV-5142 Rewrite time limits to be similar to Assessment's LDEV-5041 +ALTER TABLE tl_ladoku11_user ADD COLUMN time_limit_launched_date DATETIME; + +UPDATE tl_ladoku11_user AS u + LEFT JOIN tl_ladoku11_session AS s ON u.session_uid = s.uid + JOIN tl_ladoku11_dokumaran AS d ON u.dokumaran_uid = d.uid OR s.dokumaran_uid = d.uid +SET u.time_limit_launched_date = d.time_limit_launched_date, + u.dokumaran_uid = d.uid; + +ALTER TABLE tl_ladoku11_dokumaran DROP COLUMN time_limit_manual_start, + DROP COLUMN time_limit_launched_date, + ADD COLUMN absolute_time_limit DATETIME AFTER time_limit; + +ALTER TABLE tl_ladoku11_dokumaran CHANGE COLUMN time_limit relative_time_limit smallint unsigned NOT NULL DEFAULT '0'; + +CREATE TABLE tl_ladoku11_time_limit ( + dokumaran_uid BIGINT NOT NULL, + user_id BIGINT NOT NULL, + adjustment SMALLINT NOT NULL DEFAULT 0, + CONSTRAINT FK_tl_ladoku11_time_limit_1 FOREIGN KEY (dokumaran_uid) + REFERENCES tl_ladoku11_dokumaran (uid) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT FK_tl_ladoku11_time_limit_2 FOREIGN KEY (user_id) + REFERENCES lams_user (user_id) ON DELETE CASCADE ON UPDATE CASCADE +); + +-- Put all sql statements above here + +-- If there were no errors, commit and restore autocommit to on +COMMIT; +SET AUTOCOMMIT = 1; +SET FOREIGN_KEY_CHECKS=1; Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dokumaranApplicationContext.xml =================================================================== diff -u -r8d372a4476d25a498b5af8c04e82576edc7b0976 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dokumaranApplicationContext.xml (.../dokumaranApplicationContext.xml) (revision 8d372a4476d25a498b5af8c04e82576edc7b0976) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/dokumaranApplicationContext.xml (.../dokumaranApplicationContext.xml) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -64,6 +64,9 @@ + + + Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/model/Dokumaran.java =================================================================== diff -u -r8e8e7b3f0982e5e772b90202f01d43f1ee52962d -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/model/Dokumaran.java (.../Dokumaran.java) (revision 8e8e7b3f0982e5e772b90202f01d43f1ee52962d) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/model/Dokumaran.java (.../Dokumaran.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -23,16 +23,22 @@ package org.lamsfoundation.lams.tool.dokumaran.model; +import java.time.LocalDateTime; import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; import javax.persistence.Table; import org.apache.commons.lang.StringUtils; @@ -76,15 +82,17 @@ @Column(name = "allow_multiple_leaders") private boolean allowMultipleLeaders; - @Column(name = "time_limit") - private int timeLimit; + @Column(name = "relative_time_limit") + private int relativeTimeLimit; - @Column(name = "time_limit_manual_start") - private boolean timeLimitManualStart; + @Column(name = "absolute_time_limit") + private LocalDateTime absoluteTimeLimit; - //date when teacher has started time counter (pressed start button) - @Column(name = "time_limit_launched_date") - private Date timeLimitLaunchedDate; + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "tl_ladoku11_time_limit", joinColumns = @JoinColumn(name = "dokumaran_uid")) + @MapKeyColumn(name = "user_id") + @Column(name = "adjustment") + private Map timeLimitAdjustments = new HashMap<>(); @Column(name = "show_chat") private boolean showChat; @@ -166,6 +174,8 @@ Dokumaran.log.error("When clone " + Dokumaran.class + " failed"); } + dokumaran.setTimeLimitAdjustments(new HashMap<>(this.getTimeLimitAdjustments())); + return dokumaran; } @@ -343,35 +353,32 @@ /** * @return Returns the time limitation, that students have to complete an attempt. */ - public int getTimeLimit() { - return timeLimit; + public int getRelativeTimeLimit() { + return relativeTimeLimit; } /** * @param timeLimit * the time limitation, that students have to complete an attempt. */ - public void setTimeLimit(int timeLimit) { - this.timeLimit = timeLimit; + public void setRelativeTimeLimit(int timeLimit) { + this.relativeTimeLimit = timeLimit; } - public boolean isTimeLimitManualStart() { - return timeLimitManualStart; + public LocalDateTime getAbsoluteTimeLimit() { + return absoluteTimeLimit; } - public void setTimeLimitManualStart(boolean timeLimitManualStart) { - this.timeLimitManualStart = timeLimitManualStart; + public void setAbsoluteTimeLimit(LocalDateTime absoluteTimeLimit) { + this.absoluteTimeLimit = absoluteTimeLimit; } - /** - * @return date when teacher has started time counter (pressed start button) - */ - public Date getTimeLimitLaunchedDate() { - return timeLimitLaunchedDate; + public Map getTimeLimitAdjustments() { + return timeLimitAdjustments; } - public void setTimeLimitLaunchedDate(Date timeLimitLaunchedDate) { - this.timeLimitLaunchedDate = timeLimitLaunchedDate; + public void setTimeLimitAdjustments(Map timeLimitAdjustments) { + this.timeLimitAdjustments = timeLimitAdjustments; } public boolean isShowChat() { Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/model/DokumaranUser.java =================================================================== diff -u -r1ee503e3d0e0228ea8a45025fddf15d9623c0377 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/model/DokumaranUser.java (.../DokumaranUser.java) (revision 1ee503e3d0e0228ea8a45025fddf15d9623c0377) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/model/DokumaranUser.java (.../DokumaranUser.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -23,7 +23,7 @@ package org.lamsfoundation.lams.tool.dokumaran.model; -import java.util.Date; +import java.time.LocalDateTime; import javax.persistence.Column; import javax.persistence.Entity; @@ -34,7 +34,6 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; -import javax.persistence.Transient; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; @@ -82,20 +81,10 @@ @JoinColumn(name = "dokumaran_uid") private Dokumaran dokumaran; - // =============== NON Persisit value: for display use =========== + // date when user has started activity + @Column(name = "time_limit_launched_date") + private LocalDateTime timeLimitLaunchedDate; - // the user access some reousrce item date time. Use in monitoring summary page - @Transient - private Date accessDate; - - // dokumaran item complete date. Use in monitoring summary page - @Transient - private Date completeDate; - - // difference between completeDate and accessDate - @Transient - private Date timeTaken; - public DokumaranUser() { } @@ -105,7 +94,7 @@ this.lastName = user.getLastName(); this.loginName = user.getLogin(); this.session = session; - this.dokumaran = null; + this.dokumaran = session.getDokumaran(); this.sessionFinished = false; this.leader = false; } @@ -232,28 +221,11 @@ this.leader = leader; } - public Date getAccessDate() { - return accessDate; + public LocalDateTime getTimeLimitLaunchedDate() { + return timeLimitLaunchedDate; } - public void setAccessDate(Date accessDate) { - this.accessDate = accessDate; + public void setTimeLimitLaunchedDate(LocalDateTime timeLimitLaunchedDate) { + this.timeLimitLaunchedDate = timeLimitLaunchedDate; } - - public Date getCompleteDate() { - return completeDate; - } - - public void setCompleteDate(Date completeDate) { - this.completeDate = completeDate; - } - - public Date getTimeTaken() { - return timeTaken; - } - - public void setTimeTaken(Date timeTaken) { - this.timeTaken = timeTaken; - } - -} +} \ No newline at end of file Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/DokumaranService.java =================================================================== diff -u -r1597a42cd0d882c7554053e17481671ed45513fb -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/DokumaranService.java (.../DokumaranService.java) (revision 1597a42cd0d882c7554053e17481671ed45513fb) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/DokumaranService.java (.../DokumaranService.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -25,6 +25,7 @@ import java.io.IOException; import java.security.InvalidParameterException; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Date; import java.util.LinkedList; @@ -45,9 +46,13 @@ import org.lamsfoundation.lams.etherpad.service.IEtherpadService; import org.lamsfoundation.lams.etherpad.util.EtherpadUtil; import org.lamsfoundation.lams.learning.service.ILearnerService; +import org.lamsfoundation.lams.learningdesign.Grouping; +import org.lamsfoundation.lams.learningdesign.ToolActivity; import org.lamsfoundation.lams.learningdesign.service.ExportToolContentException; import org.lamsfoundation.lams.learningdesign.service.IExportToolContentService; import org.lamsfoundation.lams.learningdesign.service.ImportToolContentException; +import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.service.ILessonService; import org.lamsfoundation.lams.notebook.model.NotebookEntry; import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; import org.lamsfoundation.lams.notebook.service.ICoreNotebookService; @@ -73,6 +78,7 @@ import org.lamsfoundation.lams.tool.dokumaran.model.Dokumaran; import org.lamsfoundation.lams.tool.dokumaran.model.DokumaranSession; import org.lamsfoundation.lams.tool.dokumaran.model.DokumaranUser; +import org.lamsfoundation.lams.tool.dokumaran.web.controller.LearningWebsocketServer; import org.lamsfoundation.lams.tool.exception.DataMissingException; import org.lamsfoundation.lams.tool.exception.ToolException; import org.lamsfoundation.lams.tool.service.ILamsToolService; @@ -112,6 +118,8 @@ private ILearnerService learnerService; + private ILessonService lessonService; + private IRatingService ratingService; private IExportToolContentService exportContentService; @@ -304,64 +312,32 @@ Dokumaran dokumaran = session.getDokumaran(); ObjectNode jsonCommand = JsonNodeFactory.instance.objectNode(); - jsonCommand.put("hookTrigger", "doku-leader-change-refresh-" + toolSessionId); + jsonCommand.put("hookTrigger", "doku-refresh-" + dokumaran.getContentId()); learnerService.createCommandForLearners(dokumaran.getContentId(), userIds, jsonCommand.toString()); } @Override - public void launchTimeLimit(Long toolContentId) throws IOException { - Dokumaran dokumaran = getDokumaranByContentId(toolContentId); - dokumaran.setTimeLimitLaunchedDate(new Date()); - dokumaranDao.saveObject(dokumaran); - } - - @Override - public void addOneMinute(Long toolContentId) throws IOException { - Dokumaran dokumaran = getDokumaranByContentId(toolContentId); - - int timeLimit = dokumaran.getTimeLimit(); - if (timeLimit == 0) { - return; + public LocalDateTime launchTimeLimit(long toolContentId, int userId) { + DokumaranUser user = getUserByIDAndContent(Long.valueOf(userId), toolContentId); + if (user != null) { + LocalDateTime launchedDate = LocalDateTime.now(); + user.setTimeLimitLaunchedDate(launchedDate); + dokumaranUserDao.saveObject(user); + return launchedDate; } - - int newTimeLimit; - if (checkTimeLimitExceeded(dokumaran)) { - dokumaran.setTimeLimitLaunchedDate(new Date()); - newTimeLimit = 1;//d - } else { - newTimeLimit = timeLimit + 1; - } - dokumaran.setTimeLimit(newTimeLimit); - dokumaranDao.saveObject(dokumaran); + return null; } @Override - public long getSecondsLeft(Dokumaran dokumaran) { - - long secondsLeft = 0; - if (dokumaran.getTimeLimit() != 0) { - // if teacher has started the time limit already - calculate remaining time, and full time otherwise - secondsLeft = dokumaran.getTimeLimitLaunchedDate() == null ? dokumaran.getTimeLimit() * 60 - : dokumaran.getTimeLimit() * 60 - - (System.currentTimeMillis() - dokumaran.getTimeLimitLaunchedDate().getTime()) / 1000; - // change negative to 0 - secondsLeft = Math.max(0, secondsLeft); - } - - return secondsLeft; + public boolean checkTimeLimitExceeded(Dokumaran dokumaran, int userId) { + Long secondsLeft = LearningWebsocketServer.getSecondsLeft(dokumaran.getContentId(), userId); + return secondsLeft != null && secondsLeft.equals(0L); } @Override - public boolean checkTimeLimitExceeded(Dokumaran dokumaran) { - int timeLimit = dokumaran.getTimeLimit(); - if (timeLimit == 0) { - return false; - } - - // check if the time limit is exceeded - Date timeLimitLaunchedDate = dokumaran.getTimeLimitLaunchedDate(); - return (timeLimitLaunchedDate != null) - && timeLimitLaunchedDate.getTime() + timeLimit * 60000 < System.currentTimeMillis(); + public List getPossibleIndividualTimeLimitUsers(long toolContentId, String searchString) { + Lesson lesson = lessonService.getLessonByToolContentId(toolContentId); + return lessonService.getLessonLearners(lesson.getLessonId(), searchString, null, null, true); } @Override @@ -372,9 +348,7 @@ @Override public DokumaranUser getUserByLoginAndContent(String login, long contentId) { List user = dokumaranUserDao.findByProperty(User.class, "login", login); - return user.isEmpty() ? null - : dokumaranUserDao.getUserByUserIDAndContentIDViaSession(user.get(0).getUserId().longValue(), - contentId); + return user.isEmpty() ? null : getUserByIDAndContent(user.get(0).getUserId().longValue(), contentId); } @Override @@ -566,6 +540,13 @@ return (DokumaranUser) dokumaranUserDao.getObject(DokumaranUser.class, uid); } + @Override + public Grouping getGrouping(long toolContentId) { + ToolActivity toolActivity = (ToolActivity) userManagementService + .findByProperty(ToolActivity.class, "toolContentId", toolContentId).get(0); + return toolActivity.getApplyGrouping() ? toolActivity.getGrouping() : null; + } + private List createGalleryWalkRatingCriterion(long toolContentId) { List criteria = ratingService.getCriteriasByToolContentId(toolContentId); @@ -638,7 +619,7 @@ private void sendGalleryWalkRefreshRequest(Dokumaran dokumaran) { ObjectNode jsonCommand = JsonNodeFactory.instance.objectNode(); - jsonCommand.put("hookTrigger", "gallery-walk-refresh-" + dokumaran.getContentId()); + jsonCommand.put("hookTrigger", "doku-refresh-" + dokumaran.getContentId()); // get all learners in this doku Set userIds = dokumaranSessionDao.getByContentId(dokumaran.getContentId()).stream() .flatMap(session -> dokumaranUserDao.getBySessionID(session.getSessionId()).stream()) @@ -1130,6 +1111,10 @@ this.learnerService = learnerService; } + public void setLessonService(ILessonService lessonService) { + this.lessonService = lessonService; + } + public void setRatingService(IRatingService ratingService) { this.ratingService = ratingService; } @@ -1170,7 +1155,7 @@ dokumaran.setInstructions(JsonUtil.optString(toolContentJSON, "etherpadInstructions")); dokumaran.setCreated(updateDate); - dokumaran.setTimeLimit(JsonUtil.optInt(toolContentJSON, "timeLimit", 0)); + dokumaran.setRelativeTimeLimit(JsonUtil.optInt(toolContentJSON, "timeLimit", 0)); dokumaran.setShowChat(JsonUtil.optBoolean(toolContentJSON, "showChat", Boolean.FALSE)); dokumaran.setShowLineNumbers(JsonUtil.optBoolean(toolContentJSON, "showLineNumbers", Boolean.FALSE)); dokumaran.setSharedPadId(JsonUtil.optString(toolContentJSON, "sharedPadId")); @@ -1181,7 +1166,7 @@ dokumaran.setUseSelectLeaderToolOuput( JsonUtil.optBoolean(toolContentJSON, "useSelectLeaderToolOuput", Boolean.FALSE)); dokumaran.setAllowMultipleLeaders(JsonUtil.optBoolean(toolContentJSON, "allowMultipleLeaders", Boolean.FALSE)); - + dokumaran.setGalleryWalkEnabled(JsonUtil.optBoolean(toolContentJSON, "galleryWalkEnabled", false)); if (dokumaran.isGalleryWalkEnabled()) { dokumaran.setGalleryWalkReadOnly(JsonUtil.optBoolean(toolContentJSON, "galleryWalkReadOnly", false)); Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/IDokumaranService.java =================================================================== diff -u -r8d372a4476d25a498b5af8c04e82576edc7b0976 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/IDokumaranService.java (.../IDokumaranService.java) (revision 8d372a4476d25a498b5af8c04e82576edc7b0976) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/IDokumaranService.java (.../IDokumaranService.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -24,18 +24,21 @@ package org.lamsfoundation.lams.tool.dokumaran.service; import java.io.IOException; +import java.time.LocalDateTime; import java.util.List; import javax.servlet.http.Cookie; import org.lamsfoundation.lams.etherpad.EtherpadException; +import org.lamsfoundation.lams.learningdesign.Grouping; import org.lamsfoundation.lams.notebook.model.NotebookEntry; import org.lamsfoundation.lams.tool.dokumaran.dto.ReflectDTO; import org.lamsfoundation.lams.tool.dokumaran.dto.SessionDTO; import org.lamsfoundation.lams.tool.dokumaran.model.Dokumaran; import org.lamsfoundation.lams.tool.dokumaran.model.DokumaranSession; import org.lamsfoundation.lams.tool.dokumaran.model.DokumaranUser; import org.lamsfoundation.lams.tool.service.ICommonToolService; +import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; /** @@ -106,29 +109,16 @@ /** * Stores date when user has started activity with time limit. - * - * @param toolContentId - * @throws IOException - * @throws JSONException */ - void launchTimeLimit(Long toolContentId) throws IOException; + LocalDateTime launchTimeLimit(long toolContentId, int userId); - void addOneMinute(Long toolContentId) throws IOException; - /** - * Calculates how many seconds left till the time limit will expire. - * - * @param assessment - * @return - */ - long getSecondsLeft(Dokumaran dokumaran); - - /** - * @param assessment * @return whether the time limit is exceeded already */ - boolean checkTimeLimitExceeded(Dokumaran dokumaran); + boolean checkTimeLimitExceeded(Dokumaran dokumaran, int userId); + List getPossibleIndividualTimeLimitUsers(long toolContentId, String searchString); + Cookie createEtherpadCookieForLearner(DokumaranUser user, DokumaranSession session) throws DokumaranApplicationException, EtherpadException; @@ -241,4 +231,6 @@ void finishGalleryWalk(long toolContentId) throws IOException; void changeLeaderForGroup(long toolSessionId, long leaderUserId); + + Grouping getGrouping(long toolContentId); } \ No newline at end of file Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/controller/LearningController.java =================================================================== diff -u -rea8629a2ce87bf6bf4b91e1b1d47b908a3dd2e74 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/controller/LearningController.java (.../LearningController.java) (revision ea8629a2ce87bf6bf4b91e1b1d47b908a3dd2e74) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/controller/LearningController.java (.../LearningController.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -147,15 +147,6 @@ request.setAttribute(DokumaranConstants.ATTR_DOKUMARAN, dokumaran); return "pages/learning/waitforleader"; } - if (dokumaran.getTimeLimit() > 0 && dokumaran.getTimeLimitLaunchedDate() == null) { - if (dokumaran.isTimeLimitManualStart() && (mode == null || !mode.isAuthor())) { - // time limit is set but hasn't yet launched by a teac1her - show waitForTimeLimitLaunch page - return "pages/learning/waitForTimeLimitLaunch"; - } - // there is no manual start, so the first learner that enters starts the timer - dokumaran.setTimeLimitLaunchedDate(new Date()); - dokumaranService.saveOrUpdate(dokumaran); - } boolean isUserLeader = (user != null) && dokumaranService.isUserLeader(leaders, user.getUserId()); boolean hasEditRight = !dokumaran.isUseSelectLeaderToolOuput() @@ -245,11 +236,7 @@ dokumaranService.saveOrUpdate(dokumaran); //time limit - boolean isTimeLimitEnabled = hasEditRight && !finishedLock && dokumaran.getTimeLimit() != 0; - long secondsLeft = isTimeLimitEnabled ? dokumaranService.getSecondsLeft(dokumaran) : 0; - request.setAttribute(DokumaranConstants.ATTR_SECONDS_LEFT, secondsLeft); - - boolean isTimeLimitExceeded = dokumaranService.checkTimeLimitExceeded(dokumaran); + boolean isTimeLimitExceeded = dokumaranService.checkTimeLimitExceeded(dokumaran, user.getUserId().intValue()); request.setAttribute("timeLimitExceeded", isTimeLimitExceeded); String padId = session.getPadId(); Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/controller/LearningWebsocketServer.java =================================================================== diff -u -rb8cffb90895b237f8974633720e6c491699117f4 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/controller/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision b8cffb90895b237f8974633720e6c491699117f4) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/controller/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -1,212 +1,88 @@ package org.lamsfoundation.lams.tool.dokumaran.web.controller; -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; +import java.time.LocalDateTime; +import java.util.Collection; -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.ServerEndpoint; import org.apache.log4j.Logger; import org.lamsfoundation.lams.tool.dokumaran.DokumaranConstants; import org.lamsfoundation.lams.tool.dokumaran.model.Dokumaran; import org.lamsfoundation.lams.tool.dokumaran.model.DokumaranUser; import org.lamsfoundation.lams.tool.dokumaran.service.IDokumaranService; -import org.lamsfoundation.lams.util.hibernate.HibernateSessionManager; +import org.lamsfoundation.lams.web.controller.AbstractTimeLimitWebsocketServer; +import org.lamsfoundation.lams.web.controller.AbstractTimeLimitWebsocketServer.EndpointConfigurator; 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; - /** - * Sends time limit start and +1min events to learners. + * Controls Dokumaran time limits * * @author Marcin Cieslak - * @author Andrey Balan */ -@ServerEndpoint("/learningWebsocket") -public class LearningWebsocketServer { +@ServerEndpoint(value = "/learningWebsocket", configurator = EndpointConfigurator.class) +public class LearningWebsocketServer extends AbstractTimeLimitWebsocketServer { - /** - * 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 = LearningWebsocketServer.getDokumaranService() - .getDokumaranByContentId(toolContentId); - int timeLimit = dokumaran.getTimeLimit(); - if (dokumaran.getTimeLimitLaunchedDate() != null && timeLimit != 0) { - Integer cachedTimeLimit = timeLimitCache.get(toolContentId); - if (cachedTimeLimit == null) { - timeLimitCache.put(toolContentId, timeLimit); - LearningWebsocketServer.sendPageRefreshRequest(toolContentId); - } else if (!cachedTimeLimit.equals(timeLimit)) { - timeLimitCache.put(toolContentId, timeLimit); - LearningWebsocketServer.sendAddTimeRequest(toolContentId, timeLimit - cachedTimeLimit); - } - } - } - } catch (IllegalStateException e) { - // do nothing as server is probably shutting down and we could not obtain Hibernate session - } catch (Exception e) { - // error caught, but carry on - LearningWebsocketServer.log.error("Error in Dokumaran worker thread", e); - } finally { - try { - HibernateSessionManager.closeSession(); - Thread.sleep(SendWorker.CHECK_INTERVAL); - } catch (IllegalStateException | InterruptedException e) { - stopFlag = true; - LearningWebsocketServer.log.warn("Stopping Dokumaran worker thread"); - } - } - } - } - }; - 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. - */ - @OnOpen - public void registerUser(Session websocket) throws IOException { - Long toolContentID = Long - .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); - String login = websocket.getUserPrincipal().getName(); - DokumaranUser user = LearningWebsocketServer.getDokumaranService().getUserByLoginAndContent(login, - toolContentID); - if (user == null) { - throw new SecurityException("User \"" + login - + "\" is not a participant in Dokumaran activity with tool content ID " + toolContentID); + public LearningWebsocketServer() { + if (dokumaranService == null) { + WebApplicationContext wac = WebApplicationContextUtils + .getRequiredWebApplicationContext(SessionManager.getServletContext()); + dokumaranService = (IDokumaranService) wac.getBean(DokumaranConstants.DOKUMARAN_SERVICE); } - - Set toolContentWebsockets = websockets.get(toolContentID); - if (toolContentWebsockets == null) { - toolContentWebsockets = ConcurrentHashMap.newKeySet(); - websockets.put(toolContentID, toolContentWebsockets); - } - toolContentWebsockets.add(websocket); - - if (log.isDebugEnabled()) { - log.debug("User " + login + " entered Dokumaran with toolContentId: " + toolContentID); - } } - /** - * When user leaves the activity. - */ - @OnClose - public void unregisterUser(Session websocket, CloseReason reason) { - Long toolContentID = Long - .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); - 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 Dokumaran 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() - : "")); - } + @Override + protected Logger getLog() { + return log; } /** - * Monitor has added one more minute to the time limit. All learners will need - * to add +1 minute to their countdown counters. + * Gets settings from DB. */ - private static void sendAddTimeRequest(Long toolContentId, int timeLimit) throws IOException { - Set toolContentWebsockets = websockets.get(toolContentId); - if (toolContentWebsockets == null) { - return; - } + @Override + protected TimeCache getExistingTimeSettings(long toolContentId, Collection userIds) { + Dokumaran dokumaran = dokumaranService.getDokumaranByContentId(toolContentId); + TimeCache existingTimeSettings = new TimeCache(); - ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); - responseJSON.put("addTime", timeLimit); - String response = responseJSON.toString(); + existingTimeSettings.absoluteTimeLimit = dokumaran.getAbsoluteTimeLimit(); + existingTimeSettings.relativeTimeLimit = dokumaran.getRelativeTimeLimit() * 60; + existingTimeSettings.timeLimitAdjustment = dokumaran.getTimeLimitAdjustments(); - for (Session websocket : toolContentWebsockets) { - if (websocket.isOpen()) { - websocket.getBasicRemote().sendText(response); + for (Integer userId : userIds) { + DokumaranUser user = dokumaranService.getUserByIDAndContent(userId.longValue(), toolContentId); + if (user != null && user.getTimeLimitLaunchedDate() != null) { + existingTimeSettings.timeLimitLaunchedDate.put(userId, user.getTimeLimitLaunchedDate()); } } + + return existingTimeSettings; } + @Override + protected LocalDateTime launchUserTimeLimit(long toolContentId, int userId) { + return dokumaranService.launchTimeLimit(toolContentId, userId); + } + /** - * Monitor has launched time limit. All learners will need to refresh the page in order to stop showing them - * waitForTimeLimitLaunch page. + * Fetches or creates a singleton of this websocket server. */ - private static void sendPageRefreshRequest(Long toolContentId) throws IOException { - Set toolContentWebsockets = websockets.get(toolContentId); - if (toolContentWebsockets == null) { - return; + public static LearningWebsocketServer getInstance() { + LearningWebsocketServer result = (LearningWebsocketServer) AbstractTimeLimitWebsocketServer + .getInstance(LearningWebsocketServer.class.getName()); + if (result == null) { + result = new LearningWebsocketServer(); + AbstractTimeLimitWebsocketServer.registerInstance(result); } - - ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); - responseJSON.put("pageRefresh", true); - String response = responseJSON.toString(); - - for (Session websocket : toolContentWebsockets) { - if (websocket.isOpen()) { - websocket.getBasicRemote().sendText(response); - } - } + return result; } - private static IDokumaranService getDokumaranService() { - if (dokumaranService == null) { - WebApplicationContext wac = WebApplicationContextUtils - .getRequiredWebApplicationContext(SessionManager.getServletContext()); - dokumaranService = (IDokumaranService) wac.getBean(DokumaranConstants.RESOURCE_SERVICE); - } - return dokumaranService; + public static Long getSecondsLeft(long toolContentId, int userId) { + LearningWebsocketServer instance = LearningWebsocketServer.getInstance(); + return AbstractTimeLimitWebsocketServer.getSecondsLeft(instance, toolContentId, userId, true); } } \ No newline at end of file Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/controller/MonitoringController.java =================================================================== diff -u -r2ce1ca7a4995e15743c5800fdd789868b0236836 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/controller/MonitoringController.java (.../MonitoringController.java) (revision 2ce1ca7a4995e15743c5800fdd789868b0236836) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/controller/MonitoringController.java (.../MonitoringController.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -25,10 +25,17 @@ import java.io.IOException; import java.io.PrintWriter; +import java.security.InvalidParameterException; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.stream.Collectors; import javax.servlet.ServletException; @@ -42,6 +49,8 @@ import org.lamsfoundation.lams.etherpad.EtherpadException; import org.lamsfoundation.lams.gradebook.GradebookUserActivity; import org.lamsfoundation.lams.gradebook.service.IGradebookService; +import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.Grouping; import org.lamsfoundation.lams.security.ISecurityService; import org.lamsfoundation.lams.tool.ToolSession; import org.lamsfoundation.lams.tool.dokumaran.DokumaranConstants; @@ -52,9 +61,12 @@ import org.lamsfoundation.lams.tool.dokumaran.model.DokumaranUser; import org.lamsfoundation.lams.tool.dokumaran.service.IDokumaranService; import org.lamsfoundation.lams.tool.service.ILamsCoreToolService; +import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; import org.lamsfoundation.lams.util.Configuration; import org.lamsfoundation.lams.util.ConfigurationKeys; +import org.lamsfoundation.lams.util.MessageService; import org.lamsfoundation.lams.util.WebUtil; import org.lamsfoundation.lams.web.session.SessionManager; import org.lamsfoundation.lams.web.util.AttributeNames; @@ -84,10 +96,16 @@ public static final int LEARNER_MARKS_SORTING_LAST_NAME_ASC = 2; public static final int LEARNER_MARKS_SORTING_LAST_NAME_DESC = 3; + private static final Comparator USER_NAME_COMPARATOR = Comparator.comparing(User::getFirstName) + .thenComparing(User::getLastName).thenComparing(User::getLogin); + @Autowired private IDokumaranService dokumaranService; @Autowired + private IUserManagementService userManagementService; + + @Autowired private IGradebookService gradebookService; @Autowired @@ -97,6 +115,10 @@ @Autowired private ISecurityService securityService; + @Autowired + @Qualifier("dokumaranMessageService") + private MessageService messageService; + @RequestMapping("/summary") private String summary(HttpServletRequest request, HttpServletResponse response) throws EtherpadException { // initial Session Map @@ -124,11 +146,6 @@ sessionMap.put(DokumaranConstants.ATTR_REFLECT_LIST, relectList); } - //time limit - boolean isTimeLimitEnabled = dokumaran.getTimeLimit() != 0; - long secondsLeft = isTimeLimitEnabled ? dokumaranService.getSecondsLeft(dokumaran) : 0; - sessionMap.put(DokumaranConstants.ATTR_SECONDS_LEFT, secondsLeft); - // cache into sessionMap sessionMap.put(DokumaranConstants.ATTR_SUMMARY_LIST, groupList); sessionMap.put(DokumaranConstants.ATTR_HAS_FAULTY_SESSION, hasFaultySession); @@ -257,30 +274,6 @@ } - /** - * Stores date when user has started activity with time limit - * - * @throws IOException - * @throws JSONException - */ - @RequestMapping("/launchTimeLimit") - private void launchTimeLimit(HttpServletRequest request) throws IOException { - Long toolContentId = WebUtil.readLongParam(request, DokumaranConstants.ATTR_TOOL_CONTENT_ID, false); - - dokumaranService.launchTimeLimit(toolContentId); - } - - /** - * Stores date when user has started activity with time limit - */ - @RequestMapping("/addOneMinute") - private void addOneMinute(HttpServletRequest request) throws IOException { - Long toolContentId = WebUtil.readLongParam(request, DokumaranConstants.ATTR_TOOL_CONTENT_ID, false); - - dokumaranService.addOneMinute(toolContentId); - - } - @RequestMapping(path = "/displayChangeLeaderForGroupDialogFromActivity") public String displayChangeLeaderForGroupDialogFromActivity( @RequestParam(name = AttributeNames.PARAM_TOOL_SESSION_ID) long toolSessionId) { @@ -323,6 +316,156 @@ return "pages/monitoring/summary"; } + @RequestMapping(path = "/updateTimeLimit", method = RequestMethod.POST) + @ResponseStatus(HttpStatus.OK) + public void updateTimeLimit(@RequestParam(name = AttributeNames.PARAM_TOOL_CONTENT_ID) long toolContentId, + @RequestParam int relativeTimeLimit, @RequestParam(required = false) Long absoluteTimeLimit) { + if (relativeTimeLimit < 0) { + throw new InvalidParameterException( + "Relative time limit must not be negative and it is " + relativeTimeLimit); + } + if (absoluteTimeLimit != null && relativeTimeLimit != 0) { + throw new InvalidParameterException( + "Relative time limit must not be provided when absolute time limit is set"); + } + + Dokumaran dokumaran = dokumaranService.getDokumaranByContentId(toolContentId); + dokumaran.setRelativeTimeLimit(relativeTimeLimit); + // set time limit as seconds from start of epoch, using current server time zone + dokumaran.setAbsoluteTimeLimit(absoluteTimeLimit == null ? null + : LocalDateTime.ofEpochSecond(absoluteTimeLimit, 0, OffsetDateTime.now().getOffset())); + dokumaranService.saveOrUpdate(dokumaran); + } + + @RequestMapping(path = "/getPossibleIndividualTimeLimitUsers", method = RequestMethod.GET) + @ResponseBody + public String getPossibleIndividualTimeLimitUsers( + @RequestParam(name = AttributeNames.PARAM_TOOL_CONTENT_ID) long toolContentId, + @RequestParam(name = "term") String searchString) { + Dokumaran dokumaran = dokumaranService.getDokumaranByContentId(toolContentId); + Map timeLimitAdjustments = dokumaran.getTimeLimitAdjustments(); + + List users = dokumaranService.getPossibleIndividualTimeLimitUsers(toolContentId, searchString); + Grouping grouping = dokumaranService.getGrouping(toolContentId); + + ArrayNode responseJSON = JsonNodeFactory.instance.arrayNode(); + String groupLabel = messageService.getMessage("monitoring.label.group") + " \""; + if (grouping != null) { + Set groups = grouping.getGroups(); + for (Group group : groups) { + if (!group.getUsers().isEmpty() && group.getGroupName().contains(searchString.toLowerCase())) { + ObjectNode groupJSON = JsonNodeFactory.instance.objectNode(); + groupJSON.put("label", groupLabel + group.getGroupName() + "\""); + groupJSON.put("value", "group-" + group.getGroupId()); + responseJSON.add(groupJSON); + } + } + } + + for (User user : users) { + if (!timeLimitAdjustments.containsKey(user.getUserId())) { + // this format is required by jQuery UI autocomplete + ObjectNode userJSON = JsonNodeFactory.instance.objectNode(); + userJSON.put("value", "user-" + user.getUserId()); + + String name = user.getFirstName() + " " + user.getLastName() + " (" + user.getLogin() + ")"; + if (grouping != null) { + Group group = grouping.getGroupBy(user); + if (group != null) { + name += " - " + group.getGroupName(); + } + } + + userJSON.put("label", name); + responseJSON.add(userJSON); + } + } + return responseJSON.toString(); + } + + @RequestMapping(path = "/getExistingIndividualTimeLimitUsers", method = RequestMethod.GET) + @ResponseBody + public String getExistingIndividualTimeLimitUsers( + @RequestParam(name = AttributeNames.PARAM_TOOL_CONTENT_ID) long toolContentId) { + Dokumaran dokumaran = dokumaranService.getDokumaranByContentId(toolContentId); + Map timeLimitAdjustments = dokumaran.getTimeLimitAdjustments(); + Grouping grouping = dokumaranService.getGrouping(toolContentId); + // find User objects based on their userIDs and sort by name + List users = timeLimitAdjustments.keySet().stream() + .map(userId -> userManagementService.getUserById(userId)).sorted(USER_NAME_COMPARATOR) + .collect(Collectors.toList()); + + if (grouping != null) { + // Make a map group -> its users who have a time limit set + // key are sorted by group name, users in each group are sorted by name + List groupedUsers = grouping.getGroups().stream() + .collect(Collectors.toMap(Group::getGroupName, group -> { + return group.getUsers().stream() + .filter(user -> timeLimitAdjustments.containsKey(user.getUserId())) + .collect(Collectors.toCollection(() -> new TreeSet<>(USER_NAME_COMPARATOR))); + }, (s1, s2) -> { + s1.addAll(s2); + return s1; + }, TreeMap::new)).values().stream().flatMap(Set::stream).collect(Collectors.toList()); + + // from general user list remove grouped users + users.removeAll(groupedUsers); + // at the end of list, add remaining, not yet grouped users + groupedUsers.addAll(users); + users = groupedUsers; + } + + ArrayNode responseJSON = JsonNodeFactory.instance.arrayNode(); + for (User user : users) { + ObjectNode userJSON = JsonNodeFactory.instance.objectNode(); + userJSON.put("userId", user.getUserId()); + userJSON.put("adjustment", timeLimitAdjustments.get(user.getUserId().intValue())); + + String name = user.getFirstName() + " " + user.getLastName() + " (" + user.getLogin() + ")"; + if (grouping != null) { + Group group = grouping.getGroupBy(user); + if (group != null && !group.isNull()) { + name += " - " + group.getGroupName(); + } + } + userJSON.put("name", name); + + responseJSON.add(userJSON); + } + return responseJSON.toString(); + } + + @RequestMapping(path = "/updateIndividualTimeLimit", method = RequestMethod.POST) + @ResponseStatus(HttpStatus.OK) + public void updateIndividualTimeLimit(@RequestParam(name = AttributeNames.PARAM_TOOL_CONTENT_ID) long toolContentId, + @RequestParam String itemId, @RequestParam(required = false) Integer adjustment) { + Dokumaran dokumaran = dokumaranService.getDokumaranByContentId(toolContentId); + Map timeLimitAdjustments = dokumaran.getTimeLimitAdjustments(); + Set userIds = null; + + // itemId can user- or group- + String[] itemIdParts = itemId.split("-"); + if (itemIdParts[0].equalsIgnoreCase("group")) { + // add all users from a group, except for ones who are already added + Group group = (Group) userManagementService.findById(Group.class, Long.valueOf(itemIdParts[1])); + userIds = group.getUsers().stream().map(User::getUserId) + .filter(userId -> !timeLimitAdjustments.containsKey(userId)).collect(Collectors.toSet()); + } else { + // adjust for a single user + userIds = new HashSet<>(); + userIds.add(Integer.valueOf(itemIdParts[1])); + } + + for (Integer userId : userIds) { + if (adjustment == null) { + timeLimitAdjustments.remove(userId); + } else { + timeLimitAdjustments.put(userId, adjustment); + } + } + dokumaranService.saveOrUpdate(dokumaran); + } + private Integer getUserId() { HttpSession ss = SessionManager.getSession(); UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/form/DokumaranForm.java =================================================================== diff -u -r58e32af997a2da8fc96806124b29fc68fdefb149 -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/form/DokumaranForm.java (.../DokumaranForm.java) (revision 58e32af997a2da8fc96806124b29fc68fdefb149) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/form/DokumaranForm.java (.../DokumaranForm.java) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -68,7 +68,7 @@ // if it is start page, all data read out from database or current session // so need not reset checkbox to refresh value! if (!StringUtils.equals(param, "start") && !StringUtils.equals(param, "initPage")) { - dokumaran.setTimeLimit(0); + dokumaran.setRelativeTimeLimit(0); dokumaran.setShowChat(false); dokumaran.setShowLineNumbers(false); dokumaran.setLockWhenFinished(false); Index: lams_tool_doku/web/pages/authoring/advance.jsp =================================================================== diff -u -r8e8e7b3f0982e5e772b90202f01d43f1ee52962d -r0ebaba46f0ce87b5138e8e70cc1965887c1c7787 --- lams_tool_doku/web/pages/authoring/advance.jsp (.../advance.jsp) (revision 8e8e7b3f0982e5e772b90202f01d43f1ee52962d) +++ lams_tool_doku/web/pages/authoring/advance.jsp (.../advance.jsp) (revision 0ebaba46f0ce87b5138e8e70cc1965887c1c7787) @@ -40,19 +40,12 @@
-
-
- -
-