SEBSERV-204 added collecting room restrictions (only one open at a time)

This commit is contained in:
anhefti 2021-07-08 13:08:47 +02:00
parent e88f5146ab
commit 181fb6d95e
10 changed files with 131 additions and 30 deletions

View file

@ -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<String> 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();
}

View file

@ -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());
}
});

View file

@ -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());
}

View file

@ -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<RemoteProctoringRoom, TreeItem> 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;
}
}

View file

@ -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);
}

View file

@ -132,4 +132,6 @@ public interface RemoteProctoringRoomDAO {
* @return Result refer to active break-out rooms or to an error when happened */
Result<Collection<String>> getConnectionsInBreakoutRooms(Long examId);
void setCollectingRoomOpenFlag(Long roomId, boolean isOpen);
}

View file

@ -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());
}
}
}

View file

@ -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()

View file

@ -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<EntityKey> 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<String> 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(

View file

@ -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(