Merge remote-tracking branch 'origin/dev-1.2' into development
Conflicts: src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
This commit is contained in:
commit
f161587f6a
14 changed files with 196 additions and 84 deletions
3
pom.xml
3
pom.xml
|
@ -23,6 +23,9 @@
|
||||||
<revision>${sebserver-version}</revision>
|
<revision>${sebserver-version}</revision>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
|
||||||
|
<!-- Fix CVE-2021-44228 : https://spring.io/blog/2021/12/10/log4j2-vulnerability-and-spring-boot -->
|
||||||
|
<log4j2.version>2.15.0</log4j2.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,8 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.text");
|
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.text");
|
||||||
private static final LocTextKey CONFIRM_QUIT =
|
private static final LocTextKey CONFIRM_QUIT =
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
|
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 ServerPushService serverPushService;
|
||||||
private final PageService pageService;
|
private final PageService pageService;
|
||||||
|
@ -391,6 +393,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
actionBuilder
|
actionBuilder
|
||||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING)
|
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING)
|
||||||
.withEntityKey(parentEntityKey)
|
.withEntityKey(parentEntityKey)
|
||||||
|
.withConfirm(() -> CONFIRM_OPEN_SINGLE_ROOM)
|
||||||
.withExec(action -> this.monitoringProctoringService.openOneToOneRoom(
|
.withExec(action -> this.monitoringProctoringService.openOneToOneRoom(
|
||||||
action,
|
action,
|
||||||
connectionData,
|
connectionData,
|
||||||
|
|
|
@ -147,6 +147,9 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
|
||||||
SEBServerInit.INIT_LOGGER.info("----> *** Webservice successfully started up! ***");
|
SEBServerInit.INIT_LOGGER.info("----> *** Webservice successfully started up! ***");
|
||||||
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
|
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
|
||||||
|
|
||||||
|
SEBServerInit.INIT_LOGGER.info("----> log4j2.formatMsgNoLookups = {}",
|
||||||
|
this.environment.getProperty("log4j2.formatMsgNoLookups", "none"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
|
|
|
@ -45,6 +45,14 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
||||||
* happened */
|
* happened */
|
||||||
Result<Collection<Long>> allIdsOfInstitution(Long institutionId);
|
Result<Collection<Long>> 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<Collection<Long>> allIdsOfRunning(final Long institutionId);
|
||||||
|
|
||||||
/** Get all institution ids for that a specified exam for given quiz id already exists
|
/** 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)
|
* @param quizId The quiz or external identifier of the exam (LMS)
|
||||||
|
|
|
@ -132,6 +132,18 @@ public interface RemoteProctoringRoomDAO {
|
||||||
* @return Result refer to active break-out rooms or to an error when happened */
|
* @return Result refer to active break-out rooms or to an error when happened */
|
||||||
Result<Collection<String>> getConnectionsInBreakoutRooms(Long examId);
|
Result<Collection<String>> 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);
|
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<Long> updateRoomSize(Long remoteProctoringRoomId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,7 +191,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForRoomUpdateActive() {
|
public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForRoomUpdateActive() {
|
||||||
return Result.tryCatch(() -> {
|
return Result.<Collection<ClientConnectionRecord>> tryCatch(() -> {
|
||||||
|
|
||||||
final Collection<ClientConnectionRecord> records = this.clientConnectionRecordMapper
|
final Collection<ClientConnectionRecord> records = this.clientConnectionRecordMapper
|
||||||
.selectByExample()
|
.selectByExample()
|
||||||
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
|
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
|
||||||
|
@ -215,7 +216,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
});
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientConnectionRecord createProctoringRoomUpdateRecord(final int remoteProctoringRoomUpdate) {
|
private ClientConnectionRecord createProctoringRoomUpdateRecord(final int remoteProctoringRoomUpdate) {
|
||||||
|
@ -255,7 +257,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForRoomUpdateInactive() {
|
public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForRoomUpdateInactive() {
|
||||||
return Result.tryCatch(() -> {
|
return Result.<Collection<ClientConnectionRecord>> tryCatch(() -> {
|
||||||
final Collection<ClientConnectionRecord> records = this.clientConnectionRecordMapper
|
final Collection<ClientConnectionRecord> records = this.clientConnectionRecordMapper
|
||||||
.selectByExample()
|
.selectByExample()
|
||||||
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
|
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
|
||||||
|
@ -279,13 +281,17 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
});
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void setNeedsRoomUpdate(final Long connectionId) {
|
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);
|
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,8 +373,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
data.vdiPairToken,
|
data.vdiPairToken,
|
||||||
null,
|
null,
|
||||||
millisecondsNow,
|
millisecondsNow,
|
||||||
data.remoteProctoringRoomId,
|
null,
|
||||||
BooleanUtils.toIntegerObject(data.remoteProctoringRoomUpdate));
|
null);
|
||||||
|
|
||||||
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
|
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
|
||||||
return this.clientConnectionRecordMapper.selectByPrimaryKey(data.id);
|
return this.clientConnectionRecordMapper.selectByPrimaryKey(data.id);
|
||||||
|
@ -391,7 +397,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
null, null, null, null, null, null,
|
null, null, null, null, null, null,
|
||||||
roomId,
|
roomId,
|
||||||
0));
|
0));
|
||||||
});
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -404,7 +411,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
null, null, null, null, null, null,
|
null, null, null, null, null, null,
|
||||||
null,
|
null,
|
||||||
1));
|
1));
|
||||||
});
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -431,7 +439,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
} else {
|
} else {
|
||||||
throw new ResourceNotFoundException(EntityType.CLIENT_CONNECTION, String.valueOf(connectionId));
|
throw new ResourceNotFoundException(EntityType.CLIENT_CONNECTION, String.valueOf(connectionId));
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -472,7 +481,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
|
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.<Collection<EntityKey>> tryCatch(() -> {
|
||||||
|
|
||||||
final List<Long> ids = extractListOfPKs(all);
|
final List<Long> ids = extractListOfPKs(all);
|
||||||
if (ids == null || ids.isEmpty()) {
|
if (ids == null || ids.isEmpty()) {
|
||||||
|
@ -527,7 +536,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
return ids.stream()
|
return ids.stream()
|
||||||
.map(id -> new EntityKey(id, EntityType.CLIENT_CONNECTION))
|
.map(id -> new EntityKey(id, EntityType.CLIENT_CONNECTION))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
});
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -122,6 +122,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
|
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
|
||||||
return this.examRecordDAO.allInstitutionIdsByQuizId(quizId);
|
return this.examRecordDAO.allInstitutionIdsByQuizId(quizId);
|
||||||
}
|
}
|
||||||
|
@ -165,6 +166,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional
|
||||||
public Result<Exam> updateState(final Long examId, final ExamStatus status, final String updateId) {
|
public Result<Exam> updateState(final Long examId, final ExamStatus status, final String updateId) {
|
||||||
return this.examRecordDAO
|
return this.examRecordDAO
|
||||||
.updateState(examId, status, updateId)
|
.updateState(examId, status, updateId)
|
||||||
|
@ -568,6 +570,23 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<Long>> 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<Collection<EntityDependency>> allIdsOfInstitution(final EntityKey institutionKey) {
|
private Result<Collection<EntityDependency>> allIdsOfInstitution(final EntityKey institutionKey) {
|
||||||
return Result.tryCatch(() -> toDependencies(
|
return Result.tryCatch(() -> toDependencies(
|
||||||
this.examRecordMapper.selectByExample()
|
this.examRecordMapper.selectByExample()
|
||||||
|
|
|
@ -29,9 +29,12 @@ import org.springframework.transaction.annotation.Transactional;
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
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.model.session.RemoteProctoringRoom;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
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.RemoteProctoringRoomRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.RemoteProctoringRoomRecordMapper;
|
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.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 Logger log = LoggerFactory.getLogger(RemoteProctoringRoomDAOImpl.class);
|
||||||
|
|
||||||
private static final Object RESERVE_ROOM_LOCK = new Object();
|
|
||||||
|
|
||||||
private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper;
|
private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper;
|
||||||
|
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
|
||||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||||
|
|
||||||
protected RemoteProctoringRoomDAOImpl(
|
protected RemoteProctoringRoomDAOImpl(
|
||||||
final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper,
|
final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper,
|
||||||
|
final ClientConnectionRecordMapper clientConnectionRecordMapper,
|
||||||
final AdditionalAttributesDAO additionalAttributesDAO) {
|
final AdditionalAttributesDAO additionalAttributesDAO) {
|
||||||
|
|
||||||
this.remoteProctoringRoomRecordMapper = remoteProctoringRoomRecordMapper;
|
this.remoteProctoringRoomRecordMapper = remoteProctoringRoomRecordMapper;
|
||||||
|
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
|
||||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +200,8 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
.map(room -> {
|
.map(room -> {
|
||||||
this.remoteProctoringRoomRecordMapper.deleteByPrimaryKey(room.id);
|
this.remoteProctoringRoomRecordMapper.deleteByPrimaryKey(room.id);
|
||||||
return new EntityKey(room.id, EntityType.REMOTE_PROCTORING_ROOM);
|
return new EntityKey(room.id, EntityType.REMOTE_PROCTORING_ROOM);
|
||||||
});
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -239,7 +244,8 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
.deleteByPrimaryKey(roomId);
|
.deleteByPrimaryKey(roomId);
|
||||||
|
|
||||||
return new EntityKey(roomId, EntityType.REMOTE_PROCTORING_ROOM);
|
return new EntityKey(roomId, EntityType.REMOTE_PROCTORING_ROOM);
|
||||||
});
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -286,23 +292,21 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
final Function<Long, Result<NewRoom>> newRoomFunction) {
|
final Function<Long, Result<NewRoom>> newRoomFunction) {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
synchronized (RESERVE_ROOM_LOCK) {
|
final Optional<RemoteProctoringRoomRecord> room =
|
||||||
final Optional<RemoteProctoringRoomRecord> room =
|
this.remoteProctoringRoomRecordMapper.selectByExample()
|
||||||
this.remoteProctoringRoomRecordMapper.selectByExample()
|
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
|
||||||
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
|
.and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isEqualTo(0))
|
||||||
.and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isEqualTo(0))
|
.and(RemoteProctoringRoomRecordDynamicSqlSupport.breakOutConnections, isNull())
|
||||||
.and(RemoteProctoringRoomRecordDynamicSqlSupport.breakOutConnections, isNull())
|
.build()
|
||||||
.build()
|
.execute()
|
||||||
.execute()
|
.stream()
|
||||||
.stream()
|
.filter(r -> r.getSize() < roomMaxSize)
|
||||||
.filter(r -> r.getSize() < roomMaxSize)
|
.findFirst();
|
||||||
.findFirst();
|
|
||||||
|
|
||||||
if (room.isPresent()) {
|
if (room.isPresent()) {
|
||||||
return updateCollectingRoom(room.get());
|
return updateCollectingRoom(room.get());
|
||||||
} else {
|
} else {
|
||||||
return createNewCollectingRoom(examId, newRoomFunction);
|
return createNewCollectingRoom(examId, newRoomFunction);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(this::toDomainModel)
|
.map(this::toDomainModel)
|
||||||
|
@ -313,18 +317,21 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<RemoteProctoringRoom> releasePlaceInCollectingRoom(final Long examId, final Long roomId) {
|
public Result<RemoteProctoringRoom> releasePlaceInCollectingRoom(final Long examId, final Long roomId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
synchronized (RESERVE_ROOM_LOCK) {
|
final RemoteProctoringRoomRecord record = this.remoteProctoringRoomRecordMapper
|
||||||
final RemoteProctoringRoomRecord record = this.remoteProctoringRoomRecordMapper
|
.selectByPrimaryKey(roomId);
|
||||||
.selectByPrimaryKey(roomId);
|
|
||||||
|
|
||||||
final RemoteProctoringRoomRecord remoteProctoringRoomRecord = new RemoteProctoringRoomRecord(
|
final int size = record.getSize() - 1;
|
||||||
record.getId(), null, null,
|
if (size < 0) {
|
||||||
record.getSize() - 1, null, null, null, null, null);
|
throw new IllegalStateException("Room size mismatch, cannot be negative");
|
||||||
|
|
||||||
this.remoteProctoringRoomRecordMapper.updateByPrimaryKeySelective(remoteProctoringRoomRecord);
|
|
||||||
return this.remoteProctoringRoomRecordMapper
|
|
||||||
.selectByPrimaryKey(remoteProctoringRoomRecord.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
.map(this::toDomainModel)
|
||||||
.onError(TransactionHandler::rollback);
|
.onError(TransactionHandler::rollback);
|
||||||
|
@ -372,7 +379,34 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
BooleanUtils.toStringTrueFalse(isOpen))
|
BooleanUtils.toStringTrueFalse(isOpen))
|
||||||
.onError(error -> log.error("Failed to set open flag for proctoring room: {} : {}",
|
.onError(error -> log.error("Failed to set open flag for proctoring room: {} : {}",
|
||||||
roomId,
|
roomId,
|
||||||
error.getMessage()));
|
error.getMessage()))
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Result<Long> 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) {
|
private RemoteProctoringRoom toDomainModel(final RemoteProctoringRoomRecord record) {
|
||||||
|
|
|
@ -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.model.user.UserLogActivityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
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.InstitutionRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordMapper;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordMapper;
|
||||||
|
@ -586,6 +587,10 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
||||||
} catch (final JsonProcessingException e) {
|
} catch (final JsonProcessingException e) {
|
||||||
entityAsString = entity.toString();
|
entityAsString = entity.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entityAsString != null && entityAsString.length() > 4000) {
|
||||||
|
return Utils.truncateText(entityAsString, 4000);
|
||||||
|
}
|
||||||
return entityAsString;
|
return entityAsString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ class ExamSessionControlTask implements DisposableBean {
|
||||||
this.examDAO.releaseAgedLocks();
|
this.examDAO.releaseAgedLocks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedRateString = "${sebserver.webservice.api.exam.update-ping:5000}")
|
@Scheduled(fixedDelayString = "${sebserver.webservice.api.seb.lostping.update:5000}")
|
||||||
public void examSessionUpdateTask() {
|
public void examSessionUpdateTask() {
|
||||||
|
|
||||||
this.sebClientConnectionService.updatePingEvents();
|
this.sebClientConnectionService.updatePingEvents();
|
||||||
|
|
|
@ -231,7 +231,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Collection<Exam>> getRunningExamsForInstitution(final Long institutionId) {
|
public Result<Collection<Exam>> getRunningExamsForInstitution(final Long institutionId) {
|
||||||
return this.examDAO.allIdsOfInstitution(institutionId)
|
return this.examDAO.allIdsOfRunning(institutionId)
|
||||||
.map(col -> col.stream()
|
.map(col -> col.stream()
|
||||||
.map(this::getRunningExam)
|
.map(this::getRunningExam)
|
||||||
.filter(Result::hasValue)
|
.filter(Result::hasValue)
|
||||||
|
|
|
@ -56,7 +56,11 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
|
||||||
|
|
||||||
super.init(indicatorDefinition, connectionId, active, cachingEnabled);
|
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 {
|
try {
|
||||||
indicatorDefinition
|
indicatorDefinition
|
||||||
|
|
|
@ -52,6 +52,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ExamProctoringRoomServiceImpl.class);
|
private static final Logger log = LoggerFactory.getLogger(ExamProctoringRoomServiceImpl.class);
|
||||||
|
|
||||||
|
private static final Object RESERVE_ROOM_LOCK = new Object();
|
||||||
|
|
||||||
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
||||||
private final ClientConnectionDAO clientConnectionDAO;
|
private final ClientConnectionDAO clientConnectionDAO;
|
||||||
private final ExamAdminService examAdminService;
|
private final ExamAdminService examAdminService;
|
||||||
|
@ -280,53 +282,61 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assignToCollectingRoom(final ClientConnectionRecord cc) {
|
private void assignToCollectingRoom(final ClientConnectionRecord cc) {
|
||||||
try {
|
synchronized (RESERVE_ROOM_LOCK) {
|
||||||
|
try {
|
||||||
|
|
||||||
if (cc.getRemoteProctoringRoomId() == null) {
|
if (cc.getRemoteProctoringRoomId() == null) {
|
||||||
|
|
||||||
final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
|
final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
|
||||||
cc.getExamId(),
|
cc.getExamId(),
|
||||||
cc.getConnectionToken());
|
cc.getConnectionToken());
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Assigning new SEB client to proctoring room: {}, connection: {}",
|
log.debug("Assigning new SEB client to proctoring room: {}, connection: {}",
|
||||||
proctoringRoom.id,
|
proctoringRoom.id,
|
||||||
cc);
|
cc);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clientConnectionDAO
|
||||||
|
.assignToProctoringRoom(
|
||||||
|
cc.getId(),
|
||||||
|
cc.getConnectionToken(),
|
||||||
|
proctoringRoom.id)
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
applyProcotringInstruction(cc)
|
||||||
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clientConnectionDAO
|
} catch (final Exception e) {
|
||||||
.assignToProctoringRoom(
|
log.error("Failed to assign connection to collecting room: {}", cc, e);
|
||||||
cc.getId(),
|
|
||||||
cc.getConnectionToken(),
|
|
||||||
proctoringRoom.id)
|
|
||||||
.getOrThrow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyProcotringInstruction(cc)
|
|
||||||
.getOrThrow();
|
|
||||||
|
|
||||||
} catch (final Exception e) {
|
|
||||||
log.error("Failed to assign connection to collecting room: {}", cc, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromRoom(final ClientConnectionRecord cc) {
|
private void removeFromRoom(final ClientConnectionRecord cc) {
|
||||||
try {
|
synchronized (RESERVE_ROOM_LOCK) {
|
||||||
|
try {
|
||||||
|
|
||||||
this.remoteProctoringRoomDAO.releasePlaceInCollectingRoom(
|
this.remoteProctoringRoomDAO.releasePlaceInCollectingRoom(
|
||||||
cc.getExamId(),
|
cc.getExamId(),
|
||||||
cc.getRemoteProctoringRoomId());
|
cc.getRemoteProctoringRoomId());
|
||||||
|
|
||||||
this.cleanupBreakOutRooms(cc);
|
this.cleanupBreakOutRooms(cc);
|
||||||
|
|
||||||
this.clientConnectionDAO
|
this.clientConnectionDAO
|
||||||
.removeFromProctoringRoom(cc.getId(), cc.getConnectionToken())
|
.removeFromProctoringRoom(cc.getId(), cc.getConnectionToken())
|
||||||
.onError(error -> log.error("Failed to remove client connection form room: ", error))
|
.onError(error -> log.error("Failed to remove client connection from room: ", error))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to update client connection for proctoring room: ", e);
|
log.error("Failed to update client connection for proctoring room: ", e);
|
||||||
this.clientConnectionDAO.setNeedsRoomUpdate(cc.getId());
|
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();
|
.getOrThrow();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sendJoinInstruction(
|
registerJoinInstruction(
|
||||||
examId,
|
examId,
|
||||||
connectionToken,
|
connectionToken,
|
||||||
roomConnection,
|
roomConnection,
|
||||||
|
@ -688,7 +698,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
error.getMessage()))
|
error.getMessage()))
|
||||||
.get();
|
.get();
|
||||||
if (proctoringConnection != null) {
|
if (proctoringConnection != null) {
|
||||||
sendJoinInstruction(
|
registerJoinInstruction(
|
||||||
proctoringSettings.examId,
|
proctoringSettings.examId,
|
||||||
connectionToken,
|
connectionToken,
|
||||||
proctoringConnection,
|
proctoringConnection,
|
||||||
|
@ -753,7 +763,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
remoteProctoringRoom.subject)
|
remoteProctoringRoom.subject)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
sendJoinInstruction(
|
registerJoinInstruction(
|
||||||
proctoringSettings.examId,
|
proctoringSettings.examId,
|
||||||
clientConnection.clientConnection.connectionToken,
|
clientConnection.clientConnection.connectionToken,
|
||||||
proctoringConnection,
|
proctoringConnection,
|
||||||
|
@ -765,14 +775,14 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendJoinInstruction(
|
private void registerJoinInstruction(
|
||||||
final Long examId,
|
final Long examId,
|
||||||
final String connectionToken,
|
final String connectionToken,
|
||||||
final ProctoringRoomConnection proctoringConnection,
|
final ProctoringRoomConnection proctoringConnection,
|
||||||
final ExamProctoringService examProctoringService) {
|
final ExamProctoringService examProctoringService) {
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Send proctoring join instruction to connection: {}, room: {}",
|
log.debug("Register proctoring join instruction for connection: {}, room: {}",
|
||||||
connectionToken,
|
connectionToken,
|
||||||
proctoringConnection.roomName);
|
proctoringConnection.roomName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.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.<br/>Are you sure to open the town-hall?
|
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.<br/>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.<br/>Are you sure to close 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.<br/>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.<br/>Are you sure you want to open the single room?
|
||||||
|
|
||||||
sebserver.monitoring.exam.connection.notificationlist.actions=
|
sebserver.monitoring.exam.connection.notificationlist.actions=
|
||||||
sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification
|
sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification
|
||||||
|
|
Loading…
Reference in a new issue