diff --git a/pom.xml b/pom.xml
index 07f409f7..a53abaef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,9 @@
${sebserver-version}
UTF-8
UTF-8
+
+
+ 2.15.0
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java
index 01657e54..638e04de 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java
@@ -109,6 +109,8 @@ public class MonitoringClientConnection implements TemplateComposer {
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.text");
private static final LocTextKey CONFIRM_QUIT =
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
+ private static final LocTextKey CONFIRM_OPEN_SINGLE_ROOM =
+ new LocTextKey("sebserver.monitoring.exam.connection.action.singleroom.confirm");
private final ServerPushService serverPushService;
private final PageService pageService;
@@ -391,6 +393,7 @@ public class MonitoringClientConnection implements TemplateComposer {
actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING)
.withEntityKey(parentEntityKey)
+ .withConfirm(() -> CONFIRM_OPEN_SINGLE_ROOM)
.withExec(action -> this.monitoringProctoringService.openOneToOneRoom(
action,
connectionData,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java
index f1d49a8e..db5302cb 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java
@@ -147,6 +147,9 @@ public class WebserviceInit implements ApplicationListener *** Webservice successfully started up! ***");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
+ SEBServerInit.INIT_LOGGER.info("----> log4j2.formatMsgNoLookups = {}",
+ this.environment.getProperty("log4j2.formatMsgNoLookups", "none"));
+
}
@PreDestroy
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java
index 1bbd1a26..16a01d44 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java
@@ -45,6 +45,14 @@ public interface ExamDAO extends ActivatableEntityDAO, BulkActionSup
* happened */
Result> allIdsOfInstitution(Long institutionId);
+ /** Get all active and running Exams for a given institution.
+ *
+ * @param institutionId the identifier of the institution
+ * @return Result refer to a collection of all active and running exams of the given institution or refer to an
+ * error if
+ * happened */
+ Result> allIdsOfRunning(final Long institutionId);
+
/** Get all institution ids for that a specified exam for given quiz id already exists
*
* @param quizId The quiz or external identifier of the exam (LMS)
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 b78d596f..8a4245b5 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,6 +132,18 @@ public interface RemoteProctoringRoomDAO {
* @return Result refer to active break-out rooms or to an error when happened */
Result> getConnectionsInBreakoutRooms(Long examId);
+ /** Mark a specified collecting room as opened or closed by a proctor.
+ *
+ * @param roomId The collecting room identifier
+ * @param isOpen mark open or not */
void setCollectingRoomOpenFlag(Long roomId, boolean isOpen);
+ /** Use this to update the current room size of for a proctoring collecting room
+ * by its real number of attached SEB connections. This can be used on error case to
+ * recover and set the re calc the number of participants in a room
+ *
+ * @param remoteProctoringRoomId The proctoring room identifier
+ * @return The newly calculated number of participants in the room. */
+ Result updateRoomSize(Long remoteProctoringRoomId);
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java
index f061ac83..64dc7760 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java
@@ -191,7 +191,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
@Override
@Transactional
public Result> getAllConnectionIdsForRoomUpdateActive() {
- return Result.tryCatch(() -> {
+ return Result.> tryCatch(() -> {
+
final Collection records = this.clientConnectionRecordMapper
.selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
@@ -215,7 +216,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.execute();
return records;
- });
+ })
+ .onError(TransactionHandler::rollback);
}
private ClientConnectionRecord createProctoringRoomUpdateRecord(final int remoteProctoringRoomUpdate) {
@@ -255,7 +257,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
@Override
@Transactional
public Result> getAllConnectionIdsForRoomUpdateInactive() {
- return Result.tryCatch(() -> {
+ return Result.> tryCatch(() -> {
final Collection records = this.clientConnectionRecordMapper
.selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
@@ -279,13 +281,17 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.execute();
return records;
- });
+ })
+ .onError(TransactionHandler::rollback);
}
@Override
@Transactional
public void setNeedsRoomUpdate(final Long connectionId) {
- final ClientConnectionRecord updateRecord = createProctoringRoomUpdateRecord(1);
+ final ClientConnectionRecord updateRecord = new ClientConnectionRecord(
+ connectionId, null, null, null, null, null,
+ null, null, null, null, null, null, null,
+ 1);
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
}
@@ -367,8 +373,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
data.vdiPairToken,
null,
millisecondsNow,
- data.remoteProctoringRoomId,
- BooleanUtils.toIntegerObject(data.remoteProctoringRoomUpdate));
+ null,
+ null);
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
return this.clientConnectionRecordMapper.selectByPrimaryKey(data.id);
@@ -391,7 +397,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
null, null, null, null, null, null,
roomId,
0));
- });
+ })
+ .onError(TransactionHandler::rollback);
}
@Override
@@ -404,7 +411,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
null, null, null, null, null, null,
null,
1));
- });
+ })
+ .onError(TransactionHandler::rollback);
}
@Override
@@ -431,7 +439,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
} else {
throw new ResourceNotFoundException(EntityType.CLIENT_CONNECTION, String.valueOf(connectionId));
}
- });
+ })
+ .onError(TransactionHandler::rollback);
}
@Override
@@ -472,7 +481,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
@Override
@Transactional
public Result> delete(final Set all) {
- return Result.tryCatch(() -> {
+ return Result.> tryCatch(() -> {
final List ids = extractListOfPKs(all);
if (ids == null || ids.isEmpty()) {
@@ -527,7 +536,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
return ids.stream()
.map(id -> new EntityKey(id, EntityType.CLIENT_CONNECTION))
.collect(Collectors.toList());
- });
+ })
+ .onError(TransactionHandler::rollback);
}
@Override
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 3cc651c3..cfe5aad8 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
@@ -122,6 +122,7 @@ public class ExamDAOImpl implements ExamDAO {
}
@Override
+ @Transactional(readOnly = true)
public Result> allInstitutionIdsByQuizId(final String quizId) {
return this.examRecordDAO.allInstitutionIdsByQuizId(quizId);
}
@@ -165,6 +166,7 @@ public class ExamDAOImpl implements ExamDAO {
}
@Override
+ @Transactional
public Result updateState(final Long examId, final ExamStatus status, final String updateId) {
return this.examRecordDAO
.updateState(examId, status, updateId)
@@ -568,6 +570,23 @@ public class ExamDAOImpl implements ExamDAO {
});
}
+ @Override
+ @Transactional(readOnly = true)
+ public Result> allIdsOfRunning(final Long institutionId) {
+ return Result.tryCatch(() -> this.examRecordMapper.selectIdsByExample()
+ .where(
+ ExamRecordDynamicSqlSupport.institutionId,
+ isEqualTo(institutionId))
+ .and(
+ ExamRecordDynamicSqlSupport.active,
+ isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
+ .and(
+ ExamRecordDynamicSqlSupport.status,
+ isEqualTo(ExamStatus.RUNNING.name()))
+ .build()
+ .execute());
+ }
+
private Result> allIdsOfInstitution(final EntityKey institutionKey) {
return Result.tryCatch(() -> toDependencies(
this.examRecordMapper.selectByExample()
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 2d4fef85..b3771986 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
@@ -29,9 +29,12 @@ import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
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;
@@ -47,16 +50,17 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
private static final Logger log = LoggerFactory.getLogger(RemoteProctoringRoomDAOImpl.class);
- private static final Object RESERVE_ROOM_LOCK = new Object();
-
private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper;
+ private final ClientConnectionRecordMapper clientConnectionRecordMapper;
private final AdditionalAttributesDAO additionalAttributesDAO;
protected RemoteProctoringRoomDAOImpl(
final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper,
+ final ClientConnectionRecordMapper clientConnectionRecordMapper,
final AdditionalAttributesDAO additionalAttributesDAO) {
this.remoteProctoringRoomRecordMapper = remoteProctoringRoomRecordMapper;
+ this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.additionalAttributesDAO = additionalAttributesDAO;
}
@@ -196,7 +200,8 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
.map(room -> {
this.remoteProctoringRoomRecordMapper.deleteByPrimaryKey(room.id);
return new EntityKey(room.id, EntityType.REMOTE_PROCTORING_ROOM);
- });
+ })
+ .onError(TransactionHandler::rollback);
}
@Override
@@ -239,7 +244,8 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
.deleteByPrimaryKey(roomId);
return new EntityKey(roomId, EntityType.REMOTE_PROCTORING_ROOM);
- });
+ })
+ .onError(TransactionHandler::rollback);
}
@Override
@@ -286,23 +292,21 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
final Function> newRoomFunction) {
return Result.tryCatch(() -> {
- synchronized (RESERVE_ROOM_LOCK) {
- final Optional room =
- this.remoteProctoringRoomRecordMapper.selectByExample()
- .where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
- .and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isEqualTo(0))
- .and(RemoteProctoringRoomRecordDynamicSqlSupport.breakOutConnections, isNull())
- .build()
- .execute()
- .stream()
- .filter(r -> r.getSize() < roomMaxSize)
- .findFirst();
+ final Optional room =
+ this.remoteProctoringRoomRecordMapper.selectByExample()
+ .where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
+ .and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isEqualTo(0))
+ .and(RemoteProctoringRoomRecordDynamicSqlSupport.breakOutConnections, isNull())
+ .build()
+ .execute()
+ .stream()
+ .filter(r -> r.getSize() < roomMaxSize)
+ .findFirst();
- if (room.isPresent()) {
- return updateCollectingRoom(room.get());
- } else {
- return createNewCollectingRoom(examId, newRoomFunction);
- }
+ if (room.isPresent()) {
+ return updateCollectingRoom(room.get());
+ } else {
+ return createNewCollectingRoom(examId, newRoomFunction);
}
})
.map(this::toDomainModel)
@@ -313,18 +317,21 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
@Transactional
public Result releasePlaceInCollectingRoom(final Long examId, final Long roomId) {
return Result.tryCatch(() -> {
- synchronized (RESERVE_ROOM_LOCK) {
- final RemoteProctoringRoomRecord record = this.remoteProctoringRoomRecordMapper
- .selectByPrimaryKey(roomId);
+ final RemoteProctoringRoomRecord record = this.remoteProctoringRoomRecordMapper
+ .selectByPrimaryKey(roomId);
- final RemoteProctoringRoomRecord remoteProctoringRoomRecord = new RemoteProctoringRoomRecord(
- record.getId(), null, null,
- record.getSize() - 1, null, null, null, null, null);
-
- this.remoteProctoringRoomRecordMapper.updateByPrimaryKeySelective(remoteProctoringRoomRecord);
- return this.remoteProctoringRoomRecordMapper
- .selectByPrimaryKey(remoteProctoringRoomRecord.getId());
+ final int size = record.getSize() - 1;
+ if (size < 0) {
+ throw new IllegalStateException("Room size mismatch, cannot be negative");
}
+
+ final RemoteProctoringRoomRecord remoteProctoringRoomRecord = new RemoteProctoringRoomRecord(
+ record.getId(), null, null,
+ size, null, null, null, null, null);
+
+ this.remoteProctoringRoomRecordMapper.updateByPrimaryKeySelective(remoteProctoringRoomRecord);
+ return this.remoteProctoringRoomRecordMapper
+ .selectByPrimaryKey(remoteProctoringRoomRecord.getId());
})
.map(this::toDomainModel)
.onError(TransactionHandler::rollback);
@@ -372,7 +379,34 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
BooleanUtils.toStringTrueFalse(isOpen))
.onError(error -> log.error("Failed to set open flag for proctoring room: {} : {}",
roomId,
- error.getMessage()));
+ error.getMessage()))
+ .onError(TransactionHandler::rollback);
+ }
+
+ @Override
+ @Transactional
+ public Result updateRoomSize(final Long remoteProctoringRoomId) {
+ return Result.tryCatch(() -> {
+ final Long size = this.clientConnectionRecordMapper
+ .countByExample()
+ .where(
+ ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomId,
+ isEqualTo(remoteProctoringRoomId))
+ .and(
+ ClientConnectionRecordDynamicSqlSupport.status,
+ isEqualTo(ConnectionStatus.ACTIVE.name()))
+ .build()
+ .execute();
+
+ this.remoteProctoringRoomRecordMapper.updateByPrimaryKeySelective(
+ new RemoteProctoringRoomRecord(
+ remoteProctoringRoomId, null, null,
+ size.intValue(), null, null,
+ null, null, null));
+
+ return size;
+ })
+ .onError(TransactionHandler::rollback);
}
private RemoteProctoringRoom toDomainModel(final RemoteProctoringRoomRecord record) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java
index 344f7d80..a511c5df 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java
@@ -45,6 +45,7 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.InstitutionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordMapper;
@@ -586,6 +587,10 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
} catch (final JsonProcessingException e) {
entityAsString = entity.toString();
}
+
+ if (entityAsString != null && entityAsString.length() > 4000) {
+ return Utils.truncateText(entityAsString, 4000);
+ }
return entityAsString;
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
index 94887e7c..9f5fa5f8 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
@@ -109,7 +109,7 @@ class ExamSessionControlTask implements DisposableBean {
this.examDAO.releaseAgedLocks();
}
- @Scheduled(fixedRateString = "${sebserver.webservice.api.exam.update-ping:5000}")
+ @Scheduled(fixedDelayString = "${sebserver.webservice.api.seb.lostping.update:5000}")
public void examSessionUpdateTask() {
this.sebClientConnectionService.updatePingEvents();
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
index 090d802f..0f38d8ab 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
@@ -231,7 +231,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
@Override
public Result> getRunningExamsForInstitution(final Long institutionId) {
- return this.examDAO.allIdsOfInstitution(institutionId)
+ return this.examDAO.allIdsOfRunning(institutionId)
.map(col -> col.stream()
.map(this::getRunningExam)
.filter(Result::hasValue)
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java
index 1377de38..c119e988 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java
@@ -56,7 +56,11 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
super.init(indicatorDefinition, connectionId, active, cachingEnabled);
- this.currentValue = computeValueAt(DateTimeUtils.currentTimeMillis());
+ final long now = DateTimeUtils.currentTimeMillis();
+ this.currentValue = computeValueAt(now);
+ if (Double.isNaN(this.currentValue)) {
+ this.currentValue = now;
+ }
try {
indicatorDefinition
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 57c798fc..a14265d2 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
@@ -52,6 +52,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
private static final Logger log = LoggerFactory.getLogger(ExamProctoringRoomServiceImpl.class);
+ private static final Object RESERVE_ROOM_LOCK = new Object();
+
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
private final ClientConnectionDAO clientConnectionDAO;
private final ExamAdminService examAdminService;
@@ -280,53 +282,61 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
}
private void assignToCollectingRoom(final ClientConnectionRecord cc) {
- try {
+ synchronized (RESERVE_ROOM_LOCK) {
+ try {
- if (cc.getRemoteProctoringRoomId() == null) {
+ if (cc.getRemoteProctoringRoomId() == null) {
- final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
- cc.getExamId(),
- cc.getConnectionToken());
+ final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
+ cc.getExamId(),
+ cc.getConnectionToken());
- if (log.isDebugEnabled()) {
- log.debug("Assigning new SEB client to proctoring room: {}, connection: {}",
- proctoringRoom.id,
- cc);
+ if (log.isDebugEnabled()) {
+ log.debug("Assigning new SEB client to proctoring room: {}, connection: {}",
+ proctoringRoom.id,
+ cc);
+ }
+
+ this.clientConnectionDAO
+ .assignToProctoringRoom(
+ cc.getId(),
+ cc.getConnectionToken(),
+ proctoringRoom.id)
+ .getOrThrow();
+
+ applyProcotringInstruction(cc)
+ .getOrThrow();
}
- this.clientConnectionDAO
- .assignToProctoringRoom(
- cc.getId(),
- cc.getConnectionToken(),
- proctoringRoom.id)
- .getOrThrow();
+ } catch (final Exception e) {
+ log.error("Failed to assign connection to collecting room: {}", cc, e);
}
-
- applyProcotringInstruction(cc)
- .getOrThrow();
-
- } catch (final Exception e) {
- log.error("Failed to assign connection to collecting room: {}", cc, e);
}
}
private void removeFromRoom(final ClientConnectionRecord cc) {
- try {
+ synchronized (RESERVE_ROOM_LOCK) {
+ try {
- this.remoteProctoringRoomDAO.releasePlaceInCollectingRoom(
- cc.getExamId(),
- cc.getRemoteProctoringRoomId());
+ this.remoteProctoringRoomDAO.releasePlaceInCollectingRoom(
+ cc.getExamId(),
+ cc.getRemoteProctoringRoomId());
- this.cleanupBreakOutRooms(cc);
+ this.cleanupBreakOutRooms(cc);
- this.clientConnectionDAO
- .removeFromProctoringRoom(cc.getId(), cc.getConnectionToken())
- .onError(error -> log.error("Failed to remove client connection form room: ", error))
- .getOrThrow();
+ this.clientConnectionDAO
+ .removeFromProctoringRoom(cc.getId(), cc.getConnectionToken())
+ .onError(error -> log.error("Failed to remove client connection from room: ", error))
+ .getOrThrow();
- } catch (final Exception e) {
- log.error("Failed to update client connection for proctoring room: ", e);
- this.clientConnectionDAO.setNeedsRoomUpdate(cc.getId());
+ } catch (final Exception e) {
+ log.error("Failed to update client connection for proctoring room: ", e);
+ try {
+ this.remoteProctoringRoomDAO.updateRoomSize(cc.getRemoteProctoringRoomId());
+ } catch (final Exception ee) {
+ log.error("Failed to update room size: ", ee);
+ }
+ }
}
}
@@ -632,7 +642,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getOrThrow();
try {
- sendJoinInstruction(
+ registerJoinInstruction(
examId,
connectionToken,
roomConnection,
@@ -688,7 +698,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
error.getMessage()))
.get();
if (proctoringConnection != null) {
- sendJoinInstruction(
+ registerJoinInstruction(
proctoringSettings.examId,
connectionToken,
proctoringConnection,
@@ -753,7 +763,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
remoteProctoringRoom.subject)
.getOrThrow();
- sendJoinInstruction(
+ registerJoinInstruction(
proctoringSettings.examId,
clientConnection.clientConnection.connectionToken,
proctoringConnection,
@@ -765,14 +775,14 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
}
}
- private void sendJoinInstruction(
+ private void registerJoinInstruction(
final Long examId,
final String connectionToken,
final ProctoringRoomConnection proctoringConnection,
final ExamProctoringService examProctoringService) {
if (log.isDebugEnabled()) {
- log.debug("Send proctoring join instruction to connection: {}, room: {}",
+ log.debug("Register proctoring join instruction for connection: {}, room: {}",
connectionToken,
proctoringConnection.roomName);
}
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index cdcd53da..61571eb5 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -1809,6 +1809,7 @@ sebserver.monitoring.exam.connection.action.proctoring=Single Room Proctoring
sebserver.monitoring.exam.connection.action.proctoring.examroom=Exam Room Proctoring
sebserver.monitoring.exam.connection.action.openTownhall.confirm=You are about to open the town-hall room and force all SEB clients to join the town-hall room.
Are you sure to open the town-hall?
sebserver.monitoring.exam.connection.action.closeTownhall.confirm=You are about to close the town-hall room and force all SEB clients to join it's proctoring room.
Are you sure to close the town-hall?
+sebserver.monitoring.exam.connection.action.singleroom.confirm=You are about to open the single/one to one room for this participant.
Are you sure you want to open the single room?
sebserver.monitoring.exam.connection.notificationlist.actions=
sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification