SEBSERV-139 single used town-hall and code-cleanup

This commit is contained in:
anhefti 2021-03-29 13:50:10 +02:00
parent 9d5ed34ec6
commit 8470e3b160
10 changed files with 290 additions and 136 deletions

View file

@ -186,6 +186,7 @@ public final class API {
public static final String EXAM_PROCTORING_ACTIVATE_TOWNHALL_ROOM = "activate-towhall-room";
public static final String EXAM_PROCTORING_DEACTIVATE_TOWNHALL_ROOM = "deactivate-towhall-room";
public static final String EXAM_PROCTORING_TOWNHALL_ROOM_DATA = "towhall-room-data";
public static final String EXAM_PROCTORING_TOWNHALL_ROOM_AVAILABLE = "towhall-available";
public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection";

View file

@ -17,6 +17,7 @@ import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.swt.SWT;
@ -78,6 +79,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClient
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProcotringRooms;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnectionData;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetTownhallRoom;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.IsTownhallRoomAvailable;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
@ -123,6 +125,7 @@ public class MonitoringRunningExam implements TemplateComposer {
private final ServerPushService serverPushService;
private final PageService pageService;
private final RestService restService;
private final ResourceService resourceService;
private final AsyncRunner asyncRunner;
private final InstructionProcessor instructionProcessor;
@ -147,6 +150,7 @@ public class MonitoringRunningExam implements TemplateComposer {
this.serverPushService = serverPushService;
this.pageService = pageService;
this.restService = pageService.getRestService();
this.resourceService = pageService.getResourceService();
this.asyncRunner = asyncRunner;
this.instructionProcessor = instructionProcessor;
@ -214,13 +218,14 @@ public class MonitoringRunningExam implements TemplateComposer {
this.serverPushService.runServerPush(
new ServerPushContext(
content, Utils.truePredicate(),
content,
Utils.truePredicate(),
createServerPushUpdateErrorHandler(this.pageService, pageContext)),
this.pollInterval,
context -> clientTable.updateValues(),
updateTableGUI(clientTable));
final BooleanSupplier privilege = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
final BooleanSupplier isExamSupporter = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
actionBuilder
@ -242,20 +247,20 @@ public class MonitoringRunningExam implements TemplateComposer {
return copyOfPageAction;
})
.publishIf(privilege, false)
.publishIf(isExamSupporter, false)
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL)
.withEntityKey(entityKey)
.withConfirm(() -> CONFIRM_QUIT_ALL)
.withExec(action -> this.quitSEBClients(action, clientTable, true))
.noEventPropagation()
.publishIf(privilege)
.publishIf(isExamSupporter)
.newAction(ActionDefinition.MONITORING_EXAM_SEARCH_CONNECTIONS)
.withEntityKey(entityKey)
.withExec(this::openSearchPopup)
.noEventPropagation()
.publishIf(privilege)
.publishIf(isExamSupporter)
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
.withEntityKey(entityKey)
@ -265,7 +270,7 @@ public class MonitoringRunningExam implements TemplateComposer {
action -> this.quitSEBClients(action, clientTable, false),
EMPTY_ACTIVE_SELECTION_TEXT_KEY)
.noEventPropagation()
.publishIf(privilege, false)
.publishIf(isExamSupporter, false)
.newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION)
.withEntityKey(entityKey)
@ -275,81 +280,26 @@ public class MonitoringRunningExam implements TemplateComposer {
action -> this.disableSEBClients(action, clientTable, false),
EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation()
.publishIf(privilege, false);
if (privilege.getAsBoolean()) {
if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
.noEventPropagation()
.create())
.publish();
} else {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
.noEventPropagation()
.create())
.publish();
}
if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
.withExec(
hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
.noEventPropagation()
.create())
.publish();
} else {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
.withExec(
showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
.noEventPropagation()
.create())
.publish();
}
if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
.noEventPropagation()
.create())
.publish();
} else {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
.noEventPropagation()
.create())
.publish();
}
.publishIf(isExamSupporter, false);
if (isExamSupporter.getAsBoolean()) {
addFilterActions(actionBuilder, clientTable, isExamSupporter);
addProctoringActions(
currentUser.getProctoringGUIService(),
pageContext,
content,
actionBuilder);
}
}
final ProctoringSettings proctoringSettings = restService
private void addProctoringActions(
final ProctoringGUIService proctoringGUIService,
final PageContext pageContext,
final Composite parent,
final PageActionBuilder actionBuilder) {
final EntityKey entityKey = pageContext.getEntityKey();
final ProctoringSettings proctoringSettings = this.restService
.getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
@ -359,7 +309,7 @@ public class MonitoringRunningExam implements TemplateComposer {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)
.withEntityKey(entityKey)
.withExec(this::toggleTownhallRoom)
.withExec(action -> this.toggleTownhallRoom(proctoringGUIService, action))
.noEventPropagation()
.publish();
@ -375,34 +325,126 @@ public class MonitoringRunningExam implements TemplateComposer {
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>();
updateRoomActions(
entityKey,
pageContext,
availableRooms,
actionBuilder,
proctoringSettings);
proctoringSettings,
proctoringGUIService);
this.serverPushService.runServerPush(
new ServerPushContext(
content,
parent,
Utils.truePredicate(),
createServerPushUpdateErrorHandler(this.pageService, pageContext)),
this.proctoringRoomUpdateInterval,
context -> updateRoomActions(
entityKey,
pageContext,
availableRooms,
actionBuilder,
proctoringSettings));
proctoringSettings,
proctoringGUIService));
}
}
private void addFilterActions(
final PageActionBuilder actionBuilder,
final ClientConnectionTable clientTable,
final BooleanSupplier isExamSupporter) {
addClosedFilterAction(actionBuilder, clientTable);
addRequestedFilterAction(actionBuilder, clientTable);
addDisabledFilterAction(actionBuilder, clientTable);
}
private void addDisabledFilterAction(
final PageActionBuilder actionBuilder,
final ClientConnectionTable clientTable) {
if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
.noEventPropagation()
.create())
.publish();
} else {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
.noEventPropagation()
.create())
.publish();
}
}
private void addRequestedFilterAction(
final PageActionBuilder actionBuilder,
final ClientConnectionTable clientTable) {
if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
.withExec(
hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
.noEventPropagation()
.create())
.publish();
} else {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
.withExec(
showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
.noEventPropagation()
.create())
.publish();
}
}
private void addClosedFilterAction(
final PageActionBuilder actionBuilder,
final ClientConnectionTable clientTable) {
if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
.noEventPropagation()
.create())
.publish();
} else {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
.noEventPropagation()
.create())
.publish();
}
}
private boolean isTownhallRoomActive(final String examModelId) {
final RemoteProctoringRoom townhall = this.pageService.getRestService()
.getBuilder(GetTownhallRoom.class)
return !BooleanUtils.toBoolean(this.pageService
.getRestService()
.getBuilder(IsTownhallRoomAvailable.class)
.withURIVariable(API.PARAM_MODEL_ID, examModelId)
.call()
.getOr(null);
return townhall != null && townhall.id != null;
.getOr(Constants.FALSE_STRING));
}
private PageAction openSearchPopup(final PageAction action) {
@ -410,9 +452,12 @@ public class MonitoringRunningExam implements TemplateComposer {
return action;
}
private PageAction toggleTownhallRoom(final PageAction action) {
private PageAction toggleTownhallRoom(
final ProctoringGUIService proctoringGUIService,
final PageAction action) {
if (isTownhallRoomActive(action.getEntityKey().modelId)) {
closeTownhallRoom(action);
closeTownhallRoom(proctoringGUIService, action);
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
@ -422,7 +467,7 @@ public class MonitoringRunningExam implements TemplateComposer {
action.pageContext());
return action;
} else {
openTownhallRoom(action);
openTownhallRoom(proctoringGUIService, action);
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
@ -434,14 +479,12 @@ public class MonitoringRunningExam implements TemplateComposer {
}
}
private PageAction openTownhallRoom(final PageAction action) {
private PageAction openTownhallRoom(
final ProctoringGUIService proctoringGUIService,
final PageAction action) {
try {
final EntityKey examId = action.getEntityKey();
final ProctoringGUIService proctoringGUIService = this.pageService
.getCurrentUser()
.getProctoringGUIService();
String activeAllRoomName = proctoringGUIService.getTownhallRoom(examId.modelId);
if (activeAllRoomName == null) {
@ -476,7 +519,10 @@ public class MonitoringRunningExam implements TemplateComposer {
return action;
}
private PageAction closeTownhallRoom(final PageAction action) {
private PageAction closeTownhallRoom(
final ProctoringGUIService proctoringGUIService,
final PageAction action) {
final String examId = action.getEntityKey().modelId;
final RemoteProctoringRoom townhall = this.pageService.getRestService()
.getBuilder(GetTownhallRoom.class)
@ -492,45 +538,59 @@ public class MonitoringRunningExam implements TemplateComposer {
}
try {
final ProctoringGUIService proctoringGUIService = this.pageService
.getCurrentUser()
.getProctoringGUIService();
proctoringGUIService.closeRoom(townhall.name);
} catch (final Exception e) {
log.error("Failed to close procotring townhall room for exam: {}", examId);
log.error("Failed to close proctoring townhall room for exam: {}", examId);
}
return action;
}
private void updateTownhallButton(final EntityKey entityKey, final PageContext pageContext) {
private void updateTownhallButton(
final ProctoringGUIService proctoringGUIService,
final PageContext pageContext) {
final EntityKey entityKey = pageContext.getEntityKey();
if (isTownhallRoomActive(entityKey.modelId)) {
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
pageContext);
final boolean townhallRoomFromThisUser = proctoringGUIService
.getTownhallRoom(entityKey.modelId) != null;
if (townhallRoomFromThisUser) {
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
pageContext);
} else {
this.pageService.firePageEvent(
new ActionActivationEvent(
false,
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
pageContext);
}
} else {
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)),
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM),
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
pageContext);
}
}
private void updateRoomActions(
final EntityKey entityKey,
final PageContext pageContext,
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms,
final PageActionBuilder actionBuilder,
final ProctoringSettings proctoringSettings) {
final ProctoringSettings proctoringSettings,
final ProctoringGUIService proctoringGUIService) {
updateTownhallButton(entityKey, pageContext);
final EntityKey entityKey = pageContext.getEntityKey();
updateTownhallButton(proctoringGUIService, pageContext);
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
this.pageService.getRestService().getBuilder(GetProcotringRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
@ -571,7 +631,7 @@ public class MonitoringRunningExam implements TemplateComposer {
this.pageService.publishAction(
action,
_treeItem -> rooms.put(room.name, new Pair<>(room, _treeItem)));
addRoomConnectionsPopupListener(entityKey, pageContext, rooms);
addRoomConnectionsPopupListener(pageContext, rooms);
processProctorRoomActionActivation(rooms.get(room.name).b, room, pageContext);
}
});
@ -596,11 +656,11 @@ public class MonitoringRunningExam implements TemplateComposer {
}
private void addRoomConnectionsPopupListener(
final EntityKey entityKey,
final PageContext pageContext,
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms) {
if (!rooms.isEmpty()) {
final EntityKey entityKey = pageContext.getEntityKey();
final TreeItem treeItem = rooms.values().iterator().next().b;
final Tree tree = treeItem.getParent();
if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) {

View file

@ -80,17 +80,18 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
public void compose(final PageContext pageContext) {
final ProctoringWindowData proctoringWindowData = ProctoringGUIService.getCurrentProctoringWindowData();
final Composite parent = pageContext.getParent();
final Composite content = new Composite(parent, SWT.NONE | SWT.NO_SCROLL);
final GridLayout gridLayout = new GridLayout();
final ProctoringGUIService proctoringGUIService = this.pageService
.getCurrentUser()
.getProctoringGUIService();
content.setLayout(gridLayout);
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
content.setLayoutData(headerCell);
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringWindowData));
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
final String url = this.guiServiceInfo
.getExternalServerURIBuilder()
@ -120,7 +121,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
final Button closeAction = widgetFactory.buttonLocalized(footer, CLOSE_WINDOW_TEXT_KEY);
closeAction.setLayoutData(new RowData(150, 30));
closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringWindowData));
closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringGUIService, proctoringWindowData));
final BroadcastActionState broadcastActionState = new BroadcastActionState();
final String connectionTokens = getConnectionTokens(proctoringWindowData);
@ -256,11 +257,15 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
boolean chat = false;
}
private void closeRoom(final ProctoringWindowData proctoringWindowData) {
this.pageService
.getCurrentUser()
.getProctoringGUIService()
.closeRoom(proctoringWindowData.connectionData.roomName);
private void closeRoom(
final ProctoringGUIService proctoringGUIService,
final ProctoringWindowData proctoringWindowData) {
try {
proctoringGUIService.closeRoom(proctoringWindowData.connectionData.roomName);
} catch (final Exception e) {
log.error("Failed to close proctoring window properly: ", e);
}
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class IsTownhallRoomAvailable extends RestCall<String> {
public IsTownhallRoomAvailable() {
super(new TypeKey<>(
CallType.GET_SINGLE,
EntityType.REMOTE_PROCTORING_ROOM,
new TypeReference<String>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_TOWNHALL_ROOM_AVAILABLE);
}
}

View file

@ -195,7 +195,7 @@ public class ProctoringGUIService {
closeWindow(name);
final RoomConnectionData roomConnectionData = this.rooms.remove(name);
if (roomConnectionData != null) {
// send reset of broadcast attributes to all in the room
// Send reset of broadcast attributes to all in the room
this.restService.getBuilder(SendProctoringBroadcastAttributes.class)
.withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomConnectionData.roomName)
@ -209,7 +209,7 @@ public class ProctoringGUIService {
"Failed to send reset broadcast attribute instruction call for room: {}, cause: {}",
roomConnectionData.roomName,
error.getMessage()));
// send instruction to leave this room and join the own exam collecting room
// Send instruction to leave this room and join the own exam collecting room
if (!roomConnectionData.connections.isEmpty()) {
this.restService.getBuilder(SendRejoinExamCollectionRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId)
@ -221,6 +221,7 @@ public class ProctoringGUIService {
name,
error.getMessage()));
} else {
// Close town-hall room
this.restService.getBuilder(DisposeTownhallRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId)
.call()

View file

@ -23,6 +23,8 @@ public interface RemoteProctoringRoomDAO {
Result<String> getRoomName(Long roomId);
boolean isTownhallRoomActive(Long examId);
Result<RemoteProctoringRoom> getTownhallRoom(Long examId);
Result<RemoteProctoringRoom> createTownhallRoom(Long examId, String subject);

View file

@ -19,6 +19,8 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
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;
@ -39,6 +41,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@WebServiceProfile
public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
private static final Logger log = LoggerFactory.getLogger(RemoteProctoringRoomDAOImpl.class);
private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper;
protected RemoteProctoringRoomDAOImpl(
@ -94,14 +98,9 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
@Transactional
public Result<RemoteProctoringRoom> createTownhallRoom(final Long examId, final String subject) {
return Result.tryCatch(() -> {
// check first if town-hall room is not already active
final long active = this.remoteProctoringRoomRecordMapper.countByExample()
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
.and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isNotEqualTo(0))
.build()
.execute();
if (active > 0) {
// Check first if town-hall room is not already active
if (isTownhallRoomActive(examId)) {
throw new IllegalStateException("Townhall, for exam: " + examId + " already exists");
}
@ -122,6 +121,24 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
.onError(TransactionHandler::rollback);
}
@Override
@Transactional(readOnly = true)
public boolean isTownhallRoomActive(final Long examId) {
try {
final long active = this.remoteProctoringRoomRecordMapper.countByExample()
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
.and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isNotEqualTo(0))
.build()
.execute();
return (active > 0);
} catch (final Exception e) {
log.error(
"Failed to verify town-hall room activity for exam: {}. Mark it as active to avoid double openings",
examId, e);
return true;
}
}
@Override
@Transactional
public Result<RemoteProctoringRoom> saveRoom(final Long examId, final RemoteProctoringRoom room) {

View file

@ -44,6 +44,12 @@ public interface ExamProctoringRoomService {
* name of an exam. */
void updateProctoringCollectingRooms();
/** Indicates whether the town-hall room for a specified exam is active or not.
*
* @param examId the exam identifier
* @return true if the town-hall room for specified exam is active, false if not. */
boolean isTownhallRoomActive(Long examId);
/** This creates a town-hall room for a specific exam. The exam must be active and running
* and there must be no other town-hall room already be active. An unique room name will be
* created and returned.

View file

@ -95,6 +95,11 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
}
}
@Override
public boolean isTownhallRoomActive(final Long examId) {
return this.remoteProctoringRoomDAO.isTownhallRoomActive(examId);
}
@Override
public Result<RemoteProctoringRoom> createTownhallRoom(final Long examId, final String subject) {
if (!this.examSessionService.isExamRunning(examId)) {

View file

@ -304,6 +304,22 @@ public class ExamProctoringController {
Utils.toByteArray(connectionToken));
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_TOWNHALL_ROOM_AVAILABLE,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String isTownhallRoomAvialbale(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId) {
checkExamReadAccess(institutionId);
return String.valueOf(!this.examProcotringRoomService.isTownhallRoomActive(examId));
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_TOWNHALL_ROOM_DATA,