diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index eb740ac0..6b8bec79 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gbl; import java.util.Collection; import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.RGBA; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.springframework.core.ParameterizedTypeReference; @@ -124,6 +125,7 @@ public final class Constants { public static final RGB WHITE_RGB = new RGB(255, 255, 255); public static final RGB BLACK_RGB = new RGB(0, 0, 0); + public static final RGBA GREY_DISABLED = new RGBA(150, 150, 150, 50); public static final String IMPORTED_PASSWORD_MARKER = "_IMPORTED_PASSWORD"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java index 6cddb8a4..f5d1f9db 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java @@ -286,28 +286,30 @@ public class MonitoringClientConnection implements TemplateComposer { .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER) && connectionData.clientConnection.status == ConnectionStatus.ACTIVE); - final ProctoringSettings procotringSettings = restService - .getBuilder(GetProctoringSettings.class) - .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId) - .call() - .onError(error -> log.error("Failed to get ProctoringSettings", error)) - .getOr(null); + // TODO if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) { + if (connectionData.clientConnection.status != ConnectionStatus.DISABLED) { + final ProctoringSettings procotringSettings = restService + .getBuilder(GetProctoringSettings.class) + .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId) + .call() + .onError(error -> log.error("Failed to get ProctoringSettings", error)) + .getOr(null); - if (procotringSettings != null && procotringSettings.enableProctoring) { - actionBuilder - .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING) - .withEntityKey(parentEntityKey) - .withExec(action -> this.openSingleProctorScreen(action, connectionData)) - .noEventPropagation() - .publish() + if (procotringSettings != null && procotringSettings.enableProctoring) { + actionBuilder + .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING) + .withEntityKey(parentEntityKey) + .withExec(action -> this.openSingleProctorScreen(action, connectionData)) + .noEventPropagation() + .publish() - .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_EXAM_ROOM_PROCTORING) - .withEntityKey(parentEntityKey) - .withExec(action -> this.openExamCollectionProctorScreen(action, connectionData)) - .noEventPropagation() - .publish(); + .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_EXAM_ROOM_PROCTORING) + .withEntityKey(parentEntityKey) + .withExec(action -> this.openExamCollectionProctorScreen(action, connectionData)) + .noEventPropagation() + .publish(); + } } - } private PageAction openExamCollectionProctorScreen( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java index 5a47389e..02fbddb4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java @@ -20,9 +20,12 @@ import java.util.function.Function; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.slf4j.Logger; @@ -31,6 +34,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; @@ -49,6 +53,7 @@ import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gui.GuiServiceInfo; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.content.action.ActionPane; import ch.ethz.seb.sebserver.gui.service.ResourceService; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; @@ -361,13 +366,13 @@ public class MonitoringRunningExam implements TemplateComposer { room.subject, room.roomSize, proctoringSettings.collectingRoomSize))); - + processProctorRoomActionActivation(treeItem, room, pageContext); } else { // create new action final PageAction action = actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM) .withEntityKey(entityKey) - .withExec(a -> showExamProctoringRoom(proctoringSettings, room, a)) + .withExec(a -> showExamProctoringRoom(proctoringSettings, room, rooms, a)) .withNameAttributes( room.subject, room.roomSize, @@ -376,42 +381,89 @@ public class MonitoringRunningExam implements TemplateComposer { .create(); this.pageService.publishAction(action, treeItem -> rooms.put(room, treeItem)); - - // check and add show connections in room action adaptation - if (!rooms.isEmpty()) { - final TreeItem treeItem = rooms.values().iterator().next(); - final Tree tree = treeItem.getParent(); - if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) { - tree.addListener(SWT.Selection, event -> { - final TreeItem item = (TreeItem) event.item; - treeItem.getParent().deselectAll(); - if (event.button == 3) { - rooms.entrySet() - .stream() - .filter(e -> e.getValue().equals(item)) - .findFirst() - .ifPresent(e -> { - final PageContext pc = pageContext.copy() - .clearAttributes() - .withEntityKey(new EntityKey(e.getKey().getName(), - EntityType.REMOTE_PROCTORING_ROOM)) - .withParentEntityKey(entityKey); - this.proctorRoomConnectionsPopup.show(pc, e.getKey().getSubject()); - }); - } - }); - tree.setData(SHOW_CONNECTION_ACTION_APPLIED, true); - } - } + addRoomConnectionsPopupListener(entityKey, pageContext, rooms); + rooms.entrySet().stream() + .filter(entry -> entry.getKey().equals(room)) + .findFirst() + .ifPresent(entry -> processProctorRoomActionActivation( + entry.getValue(), + room, + pageContext)); } }); } + private void processProctorRoomActionActivation( + final TreeItem treeItem, + final RemoteProctoringRoom room, + final PageContext pageContext) { + + try { + final Display display = pageContext.getRoot().getDisplay(); + final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY); + final Image image = room.roomSize > 0 + ? 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)); + } catch (final Exception e) { + log.warn("Failed to set Proctor-Room-Activation: ", e.getMessage()); + } + } + + private void addRoomConnectionsPopupListener( + final EntityKey entityKey, + final PageContext pageContext, + final Map rooms) { + + if (!rooms.isEmpty()) { + final TreeItem treeItem = rooms.values().iterator().next(); + final Tree tree = treeItem.getParent(); + if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) { + tree.addListener(SWT.Selection, event -> { + final TreeItem item = (TreeItem) event.item; + item.getParent().deselectAll(); + if (event.button == 3) { + rooms.entrySet() + .stream() + .filter(e -> e.getValue().equals(item)) + .findFirst() + .ifPresent(e -> { + if (e.getKey().roomSize > 0) { + final PageContext pc = pageContext.copy() + .clearAttributes() + .withEntityKey(new EntityKey(e.getKey().getName(), + EntityType.REMOTE_PROCTORING_ROOM)) + .withParentEntityKey(entityKey); + this.proctorRoomConnectionsPopup.show(pc, e.getKey().getSubject()); + } + }); + } + }); + tree.setData(SHOW_CONNECTION_ACTION_APPLIED, true); + } + } + } + + private int getActualRoomSize(final RemoteProctoringRoom room, final Map rooms) { + return rooms.entrySet().stream() + .filter(entry -> entry.getKey().equals(room)) + .findFirst() + .map(entry -> entry.getKey().roomSize) + .orElseGet(() -> 1); + } + private PageAction showExamProctoringRoom( final ProctoringSettings proctoringSettings, final RemoteProctoringRoom room, + final Map rooms, final PageAction action) { + final int actualRoomSize = getActualRoomSize(room, rooms); + if (actualRoomSize <= 0) { + return action; + } + final SEBProctoringConnectionData proctoringConnectionData = this.pageService .getRestService() .getBuilder(GetProctorRoomConnectionData.class) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java index 881d9fd5..bd2ac5a0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java @@ -47,7 +47,7 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; @Component public class ActionPane implements TemplateComposer { - private static final String ACTION_EVENT_CALL_KEY = "ACTION_EVENT_CALL"; + public static final String ACTION_EVENT_CALL_KEY = "ACTION_EVENT_CALL"; private static final LocTextKey TITLE_KEY = new LocTextKey("sebserver.actionpane.title"); private final PageService pageService; @@ -226,7 +226,7 @@ public class ActionPane implements TemplateComposer { final Tree actions = this.widgetFactory.treeLocalized( composite, SWT.SINGLE | SWT.FULL_SELECTION | SWT.NO_SCROLL); - actions.setData(RWT.CUSTOM_VARIANT, "actions"); + actions.setData(RWT.CUSTOM_VARIANT, CustomVariant.ACTIONS.key); final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false); actions.setLayoutData(gridData); final Template template = new Template(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java index 88c0e0d1..2638d0b3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java @@ -99,7 +99,6 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); browser.setLayoutData(gridData); browser.setUrl(url); - //browser.layout(); browser.setBackground(new Color(parent.getDisplay(), 100, 100, 100)); final Composite footer = new Composite(content, SWT.NONE | SWT.NO_SCROLL); @@ -118,12 +117,14 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { .closeRoom(proctoringWindowData.connectionData.roomName)); final BroadcastActionState broadcastActionState = new BroadcastActionState(); + final String connectionTokens = getConnectionTokens(proctoringWindowData); final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY); broadcastAudioAction.setLayoutData(new RowData(150, 30)); broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio( proctoringWindowData.examId, proctoringWindowData.connectionData.roomName, + connectionTokens, broadcastAudioAction)); broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); @@ -132,6 +133,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo( proctoringWindowData.examId, proctoringWindowData.connectionData.roomName, + connectionTokens, broadcastVideoAction, broadcastAudioAction)); broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); @@ -141,12 +143,26 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { chatAction.addListener(SWT.Selection, event -> toggleChat( proctoringWindowData.examId, proctoringWindowData.connectionData.roomName, + connectionTokens, chatAction)); chatAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); } - private void toggleBroadcastAudio(final String examId, final String roomName, final Button broadcastAction) { + private String getConnectionTokens(final ProctoringWindowData proctoringWindowData) { + final String connectionTokens = this.pageService + .getCurrentUser() + .getProctoringGUIService() + .getRoomConnectionTokens(proctoringWindowData.connectionData.roomName); + return connectionTokens == null ? "" : connectionTokens; + } + + private void toggleBroadcastAudio( + final String examId, + final String roomName, + final String connectionTokens, + final Button broadcastAction) { + final BroadcastActionState state = (BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME); if (state.audio) { @@ -154,6 +170,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { this.pageService.getRestService().getBuilder(SendProctoringBroadcastOffInstruction.class) .withURIVariable(API.PARAM_MODEL_ID, examId) .withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName) + .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens) .withFormParam( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, Constants.TRUE_STRING) @@ -164,6 +181,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { this.pageService.getRestService().getBuilder(SendProctoringBroadcastOnInstruction.class) .withURIVariable(API.PARAM_MODEL_ID, examId) .withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName) + .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens) .withFormParam( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, Constants.TRUE_STRING) @@ -176,6 +194,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { private void toggleBroadcastVideo( final String examId, final String roomName, + final String connectionTokens, final Button videoAction, final Button audioAction) { final BroadcastActionState state = @@ -187,6 +206,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { this.pageService.getRestService().getBuilder(SendProctoringBroadcastOffInstruction.class) .withURIVariable(API.PARAM_MODEL_ID, examId) .withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName) + .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens) .withFormParam( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, Constants.TRUE_STRING) @@ -201,6 +221,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { this.pageService.getRestService().getBuilder(SendProctoringBroadcastOnInstruction.class) .withURIVariable(API.PARAM_MODEL_ID, examId) .withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName) + .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens) .withFormParam( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, Constants.TRUE_STRING) @@ -214,7 +235,12 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { state.audio = state.video; } - private void toggleChat(final String examId, final String roomName, final Button broadcastAction) { + private void toggleChat( + final String examId, + final String roomName, + final String connectionTokens, + final Button broadcastAction) { + final BroadcastActionState state = (BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME); if (state.chat) { @@ -222,6 +248,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { this.pageService.getRestService().getBuilder(SendProctoringBroadcastOffInstruction.class) .withURIVariable(API.PARAM_MODEL_ID, examId) .withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName) + .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens) .withFormParam( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT, Constants.TRUE_STRING) @@ -232,6 +259,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { this.pageService.getRestService().getBuilder(SendProctoringBroadcastOnInstruction.class) .withURIVariable(API.PARAM_MODEL_ID, examId) .withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName) + .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens) .withFormParam( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT, Constants.TRUE_STRING) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java index 6c80cab2..da08e983 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java @@ -61,13 +61,24 @@ public class ProctoringGUIService { return this.rooms.keySet(); } + public String getRoomConnectionTokens(final String roomName) { + if (this.rooms.containsKey(roomName)) { + return StringUtils.join(this.rooms.get(roomName).connections, Constants.COMMA); + } else { + return null; + } + } + public static ProctoringWindowData getCurrentProctoringWindowData() { return (ProctoringWindowData) RWT.getUISession() .getHttpSession() .getAttribute(SESSION_ATTR_PROCTORING_DATA); } - public static void setCurrentProctoringWindowData(final String examId, final SEBProctoringConnectionData data) { + public static void setCurrentProctoringWindowData( + final String examId, + final SEBProctoringConnectionData data) { + RWT.getUISession().getHttpSession().setAttribute( SESSION_ATTR_PROCTORING_DATA, new ProctoringWindowData(examId, data)); @@ -220,8 +231,9 @@ public class ProctoringGUIService { public final String examId; public final SEBProctoringConnectionData connectionData; - protected ProctoringWindowData(final String examId, final SEBProctoringConnectionData connectionData) { - super(); + protected ProctoringWindowData( + final String examId, + final SEBProctoringConnectionData connectionData) { this.examId = examId; this.connectionData = connectionData; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index ec1d4201..adc7862c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -174,6 +174,8 @@ public class WidgetFactory { TEXT_ACTION("action"), TEXT_READONLY("readonlyText"), + ACTIONS("actions"), + FORM_CENTER("form-center"), SELECTION("selection"), SELECTED("selected"), diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringService.java index 12cd4be0..ffc1fa57 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringService.java @@ -72,36 +72,6 @@ public class ExamJITSIProctoringService implements ExamProctoringService { return null; } -// @Override -// public Result createProctorPrivateRoomConnection( -// final ProctoringSettings proctoringSettings, -// final String connectionToken) { -// -// return Result.tryCatch(() -> { -// -// final ClientConnectionData clientConnection = this.examSessionService.getConnectionData(connectionToken) -// .getOrThrow(); -// -// final long expTime = forExam(proctoringSettings); -// final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); -// final String roomName = urlEncoder.encodeToString( -// Utils.toByteArray(clientConnection.clientConnection.connectionToken)); -// -// return createProctoringConnectionData( -// proctoringSettings.serverType, -// connectionToken, -// proctoringSettings.serverURL, -// proctoringSettings.appKey, -// proctoringSettings.getAppSecret(), -// this.authorizationService.getUserService().getCurrentUser().getUsername(), -// "seb-server", -// roomName, -// clientConnection.clientConnection.userSessionId, -// expTime) -// .getOrThrow(); -// }); -// } - @Override public Result createProctorPublicRoomConnection( final ProctoringSettings proctoringSettings, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index b49b1579..5045803f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -388,7 +388,10 @@ public class ExamMonitoringController { @PathVariable(name = API.PARAM_MODEL_ID) final Long examId, @RequestParam( name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, - required = true) final String roomName, + required = false) final String roomName, + @RequestParam( + name = API.EXAM_API_SEB_CONNECTION_TOKEN, + required = false) final String connectionTokens, @RequestParam( name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, required = false) final Boolean sendReceiveAudio, @@ -429,21 +432,46 @@ public class ExamMonitoringController { return; } - this.examProcotringRoomService.getRoomConnections(examId, roomName) - .getOrThrow() - .stream() - .forEach(connection -> { - this.sebInstructionService.registerInstruction( - examId, - InstructionType.SEB_RECONFIGURE_SETTINGS, - attributes, - connection.connectionToken, - true) - .onError(error -> log.error( - "Failed to register reconfiguring instruction for connection: {}", - connection.connectionToken, - error)); - }); + if (StringUtils.isNotBlank(connectionTokens)) { + final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR); + (single + ? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR)) + : Arrays.asList(connectionTokens)) + .stream() + .forEach(connectionToken -> { + this.sebInstructionService.registerInstruction( + examId, + InstructionType.SEB_RECONFIGURE_SETTINGS, + attributes, + connectionToken, + true) + .onError(error -> log.error( + "Failed to register reconfiguring instruction for connection: {}", + connectionToken, + error)); + + }); + } else if (StringUtils.isNotBlank(roomName)) { + this.examProcotringRoomService.getRoomConnections(examId, roomName) + .getOrThrow() + .stream() + .forEach(connection -> { + this.sebInstructionService.registerInstruction( + examId, + InstructionType.SEB_RECONFIGURE_SETTINGS, + attributes, + connection.connectionToken, + true) + .onError(error -> log.error( + "Failed to register reconfiguring instruction for connection: {}", + connection.connectionToken, + error)); + }); + } else { + throw new RuntimeException("API attribute validation error: missing " + + Domain.REMOTE_PROCTORING_ROOM.ATTR_ID + " and/or" + + API.EXAM_API_SEB_CONNECTION_TOKEN + " attribute"); + } } @RequestMapping( @@ -461,6 +489,9 @@ public class ExamMonitoringController { @RequestParam( name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, required = true) final String roomName, + @RequestParam( + name = API.EXAM_API_SEB_CONNECTION_TOKEN, + required = true) final String connectionTokens, @RequestParam( name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, required = false) final Boolean sendReceiveAudio, @@ -501,21 +532,46 @@ public class ExamMonitoringController { return; } - this.examProcotringRoomService.getRoomConnections(examId, roomName) - .getOrThrow() - .stream() - .forEach(connection -> { - this.sebInstructionService.registerInstruction( - examId, - InstructionType.SEB_RECONFIGURE_SETTINGS, - attributes, - connection.connectionToken, - true) - .onError(error -> log.error( - "Failed to register reconfiguring instruction for connection: {}", - connection.connectionToken, - error)); - }); + if (StringUtils.isNotBlank(connectionTokens)) { + final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR); + (single + ? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR)) + : Arrays.asList(connectionTokens)) + .stream() + .forEach(connectionToken -> { + this.sebInstructionService.registerInstruction( + examId, + InstructionType.SEB_RECONFIGURE_SETTINGS, + attributes, + connectionToken, + true) + .onError(error -> log.error( + "Failed to register reconfiguring instruction for connection: {}", + connectionToken, + error)); + }); + } else if (StringUtils.isNotBlank(roomName)) { + + this.examProcotringRoomService.getRoomConnections(examId, roomName) + .getOrThrow() + .stream() + .forEach(connection -> { + this.sebInstructionService.registerInstruction( + examId, + InstructionType.SEB_RECONFIGURE_SETTINGS, + attributes, + connection.connectionToken, + true) + .onError(error -> log.error( + "Failed to register reconfiguring instruction for connection: {}", + connection.connectionToken, + error)); + }); + } else { + throw new RuntimeException("API attribute validation error: missing " + + Domain.REMOTE_PROCTORING_ROOM.ATTR_ID + " and/or" + + API.EXAM_API_SEB_CONNECTION_TOKEN + " attribute"); + } } @RequestMapping(