From 181fb6d95ee9d87fdd81d9fc4c848d645517929a Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 8 Jul 2021 13:08:47 +0200 Subject: [PATCH] SEBSERV-204 added collecting room restrictions (only one open at a time) --- .../model/session/RemoteProctoringRoom.java | 21 +++++++- .../session/ClientConnectionTable.java | 2 +- .../MonitoringProctoringService.java | 26 +++++---- .../proctoring/ProctoringGUIService.java | 8 +-- .../dao/AdditionalAttributesDAO.java | 3 +- .../dao/RemoteProctoringRoomDAO.java | 2 + .../dao/impl/AdditionalAttributesDAOImpl.java | 27 +++++++--- .../servicelayer/dao/impl/ExamDAOImpl.java | 3 +- .../dao/impl/RemoteProctoringRoomDAOImpl.java | 53 +++++++++++++++++-- .../ExamProctoringRoomServiceImpl.java | 16 ++++++ 10 files changed, 131 insertions(+), 30 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/RemoteProctoringRoom.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/RemoteProctoringRoom.java index ffd279d3..0b442619 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/RemoteProctoringRoom.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/RemoteProctoringRoom.java @@ -22,8 +22,10 @@ import ch.ethz.seb.sebserver.gbl.util.Utils; @JsonIgnoreProperties(ignoreUnknown = true) public class RemoteProctoringRoom { + public static final String ATTR_IS_OPEN = "isOpen"; + public static final RemoteProctoringRoom NULL_ROOM = new RemoteProctoringRoom( - null, null, null, null, null, false, null, null, null); + null, null, null, null, null, false, null, null, null, false); @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID) public final Long id; @@ -52,6 +54,9 @@ public class RemoteProctoringRoom { @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ROOM_DATA) public final String additionalRoomData; + @JsonProperty(ATTR_IS_OPEN) + public final Boolean isOpen; + @JsonCreator public RemoteProctoringRoom( @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID) final Long id, @@ -62,7 +67,8 @@ public class RemoteProctoringRoom { @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_TOWNHALL_ROOM) final Boolean townhallRoom, @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_BREAK_OUT_CONNECTIONS) final Collection breakOutConnections, @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_JOIN_KEY) final CharSequence joinKey, - @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ROOM_DATA) final String additionalRoomData) { + @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ROOM_DATA) final String additionalRoomData, + @JsonProperty(ATTR_IS_OPEN) final Boolean isOpen) { this.id = id; this.examId = examId; @@ -73,6 +79,7 @@ public class RemoteProctoringRoom { this.breakOutConnections = Utils.immutableCollectionOf(breakOutConnections); this.joinKey = joinKey; this.additionalRoomData = additionalRoomData; + this.isOpen = isOpen; } public Long getId() { @@ -111,6 +118,10 @@ public class RemoteProctoringRoom { return this.additionalRoomData; } + public Boolean getIsOpen() { + return this.isOpen; + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); @@ -128,6 +139,12 @@ public class RemoteProctoringRoom { builder.append(this.townhallRoom); builder.append(", breakOutConnections="); builder.append(this.breakOutConnections); + builder.append(", joinKey="); + builder.append(this.joinKey); + builder.append(", additionalRoomData="); + builder.append(this.additionalRoomData); + builder.append(", isOpen="); + builder.append(this.isOpen); builder.append("]"); return builder.toString(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 1574e13d..aa821159 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -354,7 +354,7 @@ public final class ClientConnectionTable { data.getConnectionId(), UpdatableTableItem::new); tableItem.push(data); - if (this.statusFilterChanged || this.forceUpdateAll) { + if (this.statusFilterChanged || this.forceUpdateAll || needsSync) { this.toDelete.remove(data.getConnectionId()); } }); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java index 986581b1..42eaf11c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java @@ -201,14 +201,11 @@ public class MonitoringProctoringService { final PageAction action = actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM) .withEntityKey(entityKey) - .withExec(_action -> { - final int actualRoomSize = proctoringGUIService - .getActualCollectingRoomSize(room.name); - if (actualRoomSize <= 0) { - return _action; - } - return openExamProctoringRoom(proctoringSettings, room, _action); - }) + .withExec(_action -> openExamProctoringRoom( + proctoringGUIService, + proctoringSettings, + room, + _action)) .withNameAttributes( room.subject, room.roomSize, @@ -229,6 +226,7 @@ public class MonitoringProctoringService { .withParentEntityKey(entityKey); this.proctorRoomConnectionsPopup.show(pc, collectingRoom.subject); })); + processProctorRoomActionActivation( proctoringGUIService.getCollectingRoomActionItem(room.name), room, pageContext); @@ -239,10 +237,15 @@ public class MonitoringProctoringService { } private PageAction openExamProctoringRoom( + final ProctoringGUIService proctoringGUIService, final ProctoringServiceSettings proctoringSettings, final RemoteProctoringRoom room, final PageAction action) { + if (!proctoringGUIService.isCollectingRoomEnabled(room.name)) { + return action; + } + final ProctoringRoomConnection proctoringConnectionData = this.pageService .getRestService() .getBuilder(GetProctorRoomConnection.class) @@ -437,13 +440,16 @@ public class MonitoringProctoringService { final PageContext pageContext) { try { + + final boolean active = room.roomSize > 0 && !room.isOpen; final Display display = pageContext.getRoot().getDisplay(); final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY); - final Image image = room.roomSize > 0 + final Image image = active ? action.definition.icon.getImage(display) : action.definition.icon.getGreyedImage(display); treeItem.setImage(image); - treeItem.setForeground(room.roomSize > 0 ? null : new Color(display, Constants.GREY_DISABLED)); + treeItem.setForeground(active ? null : new Color(display, Constants.GREY_DISABLED)); + } catch (final Exception e) { log.warn("Failed to set Proctor-Room-Activation: ", e.getMessage()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringGUIService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringGUIService.java index 318fec50..9d69b307 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringGUIService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringGUIService.java @@ -78,7 +78,6 @@ public class ProctoringGUIService { final RemoteProctoringRoom remoteProctoringRoom = getRemoteProctoringRoom(item); if (remoteProctoringRoom != null && remoteProctoringRoom.roomSize > 0) { showConnectionsPopup.accept(remoteProctoringRoom); - //this.proctorRoomConnectionsPopup.show(pc, remoteProctoringRoom.subject); } } }); @@ -99,12 +98,13 @@ public class ProctoringGUIService { .orElse(null); } - public int getActualCollectingRoomSize(final String roomName) { + public boolean isCollectingRoomEnabled(final String roomName) { try { - return this.collectingRoomsActionState.get(roomName).a.roomSize; + final Pair pair = this.collectingRoomsActionState.get(roomName); + return pair.a.roomSize > 0 && !pair.a.isOpen; } catch (final Exception e) { log.error("Failed to get actual collecting room size for room: {} cause: ", roomName, e.getMessage()); - return -1; + return false; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/AdditionalAttributesDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/AdditionalAttributesDAO.java index bf8f6345..2947f30e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/AdditionalAttributesDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/AdditionalAttributesDAO.java @@ -69,7 +69,8 @@ public interface AdditionalAttributesDAO { /** Use this to delete all additional attributes for a given entity. * + * @param type the entity type * @param entityId the entity identifier (primary-key) */ - void deleteAll(Long entityId); + void deleteAll(EntityType type, Long entityId); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java index 27c86813..b78d596f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java @@ -132,4 +132,6 @@ public interface RemoteProctoringRoomDAO { * @return Result refer to active break-out rooms or to an error when happened */ Result> getConnectionsInBreakoutRooms(Long examId); + void setCollectingRoomOpenFlag(Long roomId, boolean isOpen); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/AdditionalAttributesDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/AdditionalAttributesDAOImpl.java index ef6fa7c1..f191211d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/AdditionalAttributesDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/AdditionalAttributesDAOImpl.java @@ -12,6 +12,8 @@ import java.util.Collection; import java.util.Optional; import org.mybatis.dynamic.sql.SqlBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -29,6 +31,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO @WebServiceProfile public class AdditionalAttributesDAOImpl implements AdditionalAttributesDAO { + private static final Logger log = LoggerFactory.getLogger(AdditionalAttributesDAOImpl.class); + private final AdditionalAttributeRecordMapper additionalAttributeRecordMapper; protected AdditionalAttributesDAOImpl(final AdditionalAttributeRecordMapper additionalAttributeRecordMapper) { @@ -164,14 +168,21 @@ public class AdditionalAttributesDAOImpl implements AdditionalAttributesDAO { @Override @Transactional - public void deleteAll(final Long entityId) { - this.additionalAttributeRecordMapper - .deleteByExample() - .where( - AdditionalAttributeRecordDynamicSqlSupport.entityId, - SqlBuilder.isEqualTo(entityId)) - .build() - .execute(); + public void deleteAll(final EntityType type, final Long entityId) { + try { + this.additionalAttributeRecordMapper + .deleteByExample() + .where( + AdditionalAttributeRecordDynamicSqlSupport.entityType, + SqlBuilder.isEqualTo(type.name())) + .and( + AdditionalAttributeRecordDynamicSqlSupport.entityId, + SqlBuilder.isEqualTo(entityId)) + .build() + .execute(); + } catch (final Exception e) { + log.warn("Failed to delete all additional attributes for: {} cause: {}", entityId, e.getMessage()); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java index db5e6755..1c698528 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java @@ -635,7 +635,8 @@ public class ExamDAOImpl implements ExamDAO { .execute(); // delete all additional attributes - this.additionalAttributeRecordMapper.deleteByExample() + this.additionalAttributeRecordMapper + .deleteByExample() .where(AdditionalAttributeRecordDynamicSqlSupport.entityType, isEqualTo(EntityType.EXAM.name())) .and(AdditionalAttributeRecordDynamicSqlSupport.entityId, isIn(ids)) .build() diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java index b6f4858d..3f41bfd8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java @@ -35,6 +35,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.RemoteProctoringRoomRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.RemoteProctoringRoomRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.RemoteProctoringRoomRecord; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.NewRoom; @@ -49,11 +50,14 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO { private static final Object RESERVE_ROOM_LOCK = new Object(); private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper; + private final AdditionalAttributesDAO additionalAttributesDAO; protected RemoteProctoringRoomDAOImpl( - final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper) { + final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper, + final AdditionalAttributesDAO additionalAttributesDAO) { this.remoteProctoringRoomRecordMapper = remoteProctoringRoomRecordMapper; + this.additionalAttributesDAO = additionalAttributesDAO; } @Override @@ -227,6 +231,10 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO { @Transactional public Result deleteRoom(final Long roomId) { return Result.tryCatch(() -> { + + this.additionalAttributesDAO + .deleteAll(EntityType.REMOTE_PROCTORING_ROOM, roomId); + this.remoteProctoringRoomRecordMapper .deleteByPrimaryKey(roomId); @@ -249,7 +257,15 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO { .execute(); return ids.stream() - .map(id -> new EntityKey(String.valueOf(id), EntityType.REMOTE_PROCTORING_ROOM)) + .map(roomId -> { + this.additionalAttributesDAO.deleteAll( + EntityType.REMOTE_PROCTORING_ROOM, + roomId); + return roomId; + }) + .map(roomId -> new EntityKey( + String.valueOf(roomId), + EntityType.REMOTE_PROCTORING_ROOM)) .collect(Collectors.toList()); }); @@ -340,6 +356,19 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO { .collect(Collectors.toList())); } + @Override + @Transactional + public void setCollectingRoomOpenFlag(final Long roomId, final boolean isOpen) { + this.additionalAttributesDAO.saveAdditionalAttribute( + EntityType.REMOTE_PROCTORING_ROOM, + roomId, + RemoteProctoringRoom.ATTR_IS_OPEN, + BooleanUtils.toStringTrueFalse(isOpen)) + .onError(error -> log.error("Failed to set open flag for proctoring room: {} : {}", + roomId, + error.getMessage())); + } + private RemoteProctoringRoom toDomainModel(final RemoteProctoringRoomRecord record) { final String breakOutConnections = record.getBreakOutConnections(); final Collection connections = StringUtils.isNotBlank(breakOutConnections) @@ -355,7 +384,25 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO { BooleanUtils.toBooleanObject(record.getTownhallRoom()), connections, record.getJoinKey(), - record.getRoomData()); + record.getRoomData(), + isOpen(record)); + } + + private boolean isOpen(final RemoteProctoringRoomRecord record) { + if (record.getTownhallRoom() != 0 || !StringUtils.isBlank(record.getBreakOutConnections())) { + return false; + } else { + return BooleanUtils.toBoolean(this.additionalAttributesDAO + .getAdditionalAttribute( + EntityType.REMOTE_PROCTORING_ROOM, + record.getId(), + RemoteProctoringRoom.ATTR_IS_OPEN) + .map(rec -> rec.getValue()) + .onError(error -> log.error("Failed to get open flag for proctoring room: {} : {}", + record.getName(), + error.getMessage())) + .getOrElse(() -> Constants.FALSE_STRING)); + } } private RemoteProctoringRoomRecord createNewCollectingRoom( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java index 6112c865..58bd19b0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java @@ -394,6 +394,9 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService examProctoringService .notifyCollectingRoomOpened(proctoringSettings, room, clientConnections) .getOrThrow(); + + this.remoteProctoringRoomDAO + .setCollectingRoomOpenFlag(room.id, true); } }); } @@ -446,6 +449,19 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .map(cc -> cc.connectionToken) .collect(Collectors.toList()); + final RemoteProctoringRoom room = this.remoteProctoringRoomDAO + .getRoom(examId, roomName) + .onError(error -> log.error("Failed to get room for setting closed: {} {} {}", + examId, + roomName, + error.getMessage())) + .getOr(null); + + if (room != null) { + this.remoteProctoringRoomDAO + .setCollectingRoomOpenFlag(room.id, false); + } + // Send default settings to clients if feature is enabled if (this.sendBroadcastReset) { this.sendReconfigurationInstructions(