SEBSERV-139 added collecting all room feature
This commit is contained in:
		
							parent
							
								
									6e3549dbb7
								
							
						
					
					
						commit
						057275ba49
					
				
					 24 changed files with 863 additions and 561 deletions
				
			
		|  | @ -128,14 +128,7 @@ public final class API { | |||
|     public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction"; | ||||
|     public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported"; | ||||
|     public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters"; | ||||
|     public static final String PROCTORING_PATH_SEGMENT = "/proctoring"; | ||||
|     public static final String PROCTORING_ROOMS_SEGMENT = "/rooms"; | ||||
|     public static final String PROCTORING_JOIN_ROOM_PATH_SEGMENT = "/join"; | ||||
|     public static final String PROCTORING_LEAVE_ROOM_PATH_SEGMENT = "/leave"; | ||||
|     public static final String PROCTORING_REJOIN_EXAM_ROOM_PATH_SEGMENT = "/rejoin-exam-room"; | ||||
|     public static final String PROCTORING_BROADCAST_ON_PATH_SEGMENT = "/broadcast-on"; | ||||
|     public static final String PROCTORING_BROADCAST_OFF_PATH_SEGMENT = "/broadcast-off"; | ||||
|     public static final String PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT = "/room-connections"; | ||||
|     public static final String EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT = "/proctoring"; | ||||
| 
 | ||||
|     public static final String EXAM_INDICATOR_ENDPOINT = "/indicator"; | ||||
| 
 | ||||
|  | @ -177,6 +170,16 @@ public final class API { | |||
|     public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = | ||||
|             "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; | ||||
| 
 | ||||
|     public static final String EXAM_PROCTORING_ENDPOINT = EXAM_MONITORING_ENDPOINT + "/proctoring"; | ||||
|     public static final String EXAM_PROCTORING_ROOMS_SEGMENT = "/rooms"; | ||||
|     public static final String EXAM_PROCTORING_JOIN_ROOM_PATH_SEGMENT = "/join"; | ||||
|     public static final String EXAM_PROCTORING_REJOIN_COLLECTING_ROOM_PATH_SEGMENT = "/rejoin-collecting-room"; | ||||
|     public static final String EXAM_PROCTORING_BROADCAST_ON_PATH_SEGMENT = "/broadcast-on"; | ||||
|     public static final String EXAM_PROCTORING_BROADCAST_OFF_PATH_SEGMENT = "/broadcast-off"; | ||||
|     public static final String EXAM_PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT = "/room-connections"; | ||||
|     public static final String EXAM_PROCTORING_JON_ALL_COLLECTING_ROOM = "join-all-collecting-room"; | ||||
|     public static final String EXAM_PROCTORING_REJON_ALL_COLLECTING_ROOM = "rejoin-all-collecting-room"; | ||||
| 
 | ||||
|     public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection"; | ||||
| 
 | ||||
|     public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event"; | ||||
|  |  | |||
|  | @ -372,16 +372,25 @@ public class MonitoringClientConnection implements TemplateComposer { | |||
|         final String roomName = urlEncoder.encodeToString(Utils.toByteArray(connectionToken)); | ||||
|         final String examId = action.getEntityKey().modelId; | ||||
| 
 | ||||
|         final SEBProctoringConnectionData proctoringConnectionData = this.pageService.getCurrentUser() | ||||
|                 .getProctoringGUIService() | ||||
|         final ProctoringGUIService proctoringGUIService = this.pageService | ||||
|                 .getCurrentUser() | ||||
|                 .getProctoringGUIService(); | ||||
| 
 | ||||
|         if (!proctoringGUIService.hasRoom(roomName)) { | ||||
|             final SEBProctoringConnectionData proctoringConnectionData = proctoringGUIService | ||||
|                     .registerNewSingleProcotringRoom( | ||||
|                             examId, | ||||
|                             roomName, | ||||
|                             connectionData.clientConnection.userSessionId, | ||||
|                             connectionToken) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|                     .onError(error -> log.error( | ||||
|                             "Failed to open single proctoring room for connection {} {}", | ||||
|                             connectionToken, | ||||
|                             error.getMessage())) | ||||
|                     .getOr(null); | ||||
|             ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData); | ||||
|         } | ||||
| 
 | ||||
|         final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class); | ||||
|         final String script = String.format( | ||||
|                 OPEN_SINGEL_ROOM_SCRIPT, | ||||
|  | @ -389,9 +398,7 @@ public class MonitoringClientConnection implements TemplateComposer { | |||
|                 this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), | ||||
|                 this.remoteProctoringEndpoint); | ||||
|         javaScriptExecutor.execute(script); | ||||
|         this.pageService.getCurrentUser() | ||||
|                 .getProctoringGUIService() | ||||
|                 .registerProctoringWindow(roomName); | ||||
|         proctoringGUIService.registerProctoringWindow(roomName); | ||||
|         return action; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -108,6 +108,8 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm"); | ||||
|     private static final LocTextKey CONFIRM_DISABLE_SELECTED = | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm"); | ||||
|     private static final LocTextKey EXAM_ROOM_NAME = | ||||
|             new LocTextKey("sebserver.monitoring.exam.proctoring.room.all.name"); | ||||
| 
 | ||||
|     private final ServerPushService serverPushService; | ||||
|     private final PageService pageService; | ||||
|  | @ -329,6 +331,13 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|                 .getOr(null); | ||||
| 
 | ||||
|         if (proctoringSettings != null && proctoringSettings.enableProctoring) { | ||||
| 
 | ||||
|             actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_CREATE_ALL_PROCTOR_ROOM) | ||||
|                     .withEntityKey(entityKey) | ||||
|                     .withExec(this::createCollectingAllRoom) | ||||
|                     .noEventPropagation() | ||||
|                     .publish(); | ||||
| 
 | ||||
|             final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>(); | ||||
|             updateRoomActions( | ||||
|                     entityKey, | ||||
|  | @ -348,6 +357,41 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private PageAction createCollectingAllRoom(final PageAction action) { | ||||
|         final EntityKey examId = action.getEntityKey(); | ||||
| 
 | ||||
|         final ProctoringGUIService proctoringGUIService = this.pageService | ||||
|                 .getCurrentUser() | ||||
|                 .getProctoringGUIService(); | ||||
| 
 | ||||
|         String activeAllRoomName = proctoringGUIService.getActiveAllRoom(examId.modelId); | ||||
| 
 | ||||
|         if (activeAllRoomName == null) { | ||||
|             final SEBProctoringConnectionData proctoringConnectionData = proctoringGUIService | ||||
|                     .registerAllProcotringRoom( | ||||
|                             examId.modelId, | ||||
|                             this.pageService.getI18nSupport().getText(EXAM_ROOM_NAME)) | ||||
|                     .onError(error -> log.error( | ||||
|                             "Failed to open all collecting room for exam {} {}", examId.modelId, error.getMessage())) | ||||
|                     .getOrThrow(); | ||||
|             ProctoringGUIService.setCurrentProctoringWindowData( | ||||
|                     examId.modelId, | ||||
|                     proctoringConnectionData); | ||||
|             activeAllRoomName = proctoringConnectionData.roomName; | ||||
|         } | ||||
| 
 | ||||
|         final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class); | ||||
|         final String script = String.format( | ||||
|                 OPEN_EXAM_COLLECTION_ROOM_SCRIPT, | ||||
|                 activeAllRoomName, | ||||
|                 this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), | ||||
|                 this.remoteProctoringEndpoint); | ||||
|         javaScriptExecutor.execute(script); | ||||
|         proctoringGUIService.registerProctoringWindow(activeAllRoomName); | ||||
| 
 | ||||
|         return action; | ||||
|     } | ||||
| 
 | ||||
|     private void updateRoomActions( | ||||
|             final EntityKey entityKey, | ||||
|             final PageContext pageContext, | ||||
|  | @ -359,7 +403,8 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|         this.pageService.getRestService().getBuilder(GetProcotringRooms.class) | ||||
|                 .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) | ||||
|                 .call() | ||||
|                 .getOrThrow() | ||||
|                 .onError(error -> log.error("Failed to update proctoring rooms on GUI {}", error.getMessage())) | ||||
|                 .getOr(Collections.emptyList()) | ||||
|                 .stream() | ||||
|                 .forEach(room -> { | ||||
|                     if (rooms.containsKey(room.name)) { | ||||
|  |  | |||
|  | @ -704,6 +704,11 @@ public enum ActionDefinition { | |||
|             ImageIcon.PROCTOR_ROOM, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.PROCTORING), | ||||
|     MONITOR_EXAM_CREATE_ALL_PROCTOR_ROOM( | ||||
|             new LocTextKey("sebserver.monitoring.exam.action.proctoring.allRoom"), | ||||
|             ImageIcon.PROCTOR_ROOM, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.PROCTORING), | ||||
| 
 | ||||
|     LOGS_USER_ACTIVITY_LIST( | ||||
|             new LocTextKey("sebserver.logs.activity.userlogs"), | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ public class GetProctoringSettings extends RestCall<ProctoringSettings> { | |||
|                 MediaType.APPLICATION_JSON_UTF8, | ||||
|                 API.EXAM_ADMINISTRATION_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT); | ||||
|                         + API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ public class SaveProctoringSettings extends RestCall<Exam> { | |||
|                 MediaType.APPLICATION_JSON_UTF8, | ||||
|                 API.EXAM_ADMINISTRATION_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT); | ||||
|                         + API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -8,8 +8,6 @@ | |||
| 
 | ||||
| package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.springframework.context.annotation.Lazy; | ||||
| import org.springframework.http.HttpMethod; | ||||
| import org.springframework.http.MediaType; | ||||
|  | @ -26,19 +24,19 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; | |||
| @Lazy | ||||
| @Component | ||||
| @GuiProfile | ||||
| public class LeaveRemoteProctoringRoom extends RestCall<List<SEBProctoringConnectionData>> { | ||||
| public class CreateCollectingAllProctoringRoom extends RestCall<SEBProctoringConnectionData> { | ||||
| 
 | ||||
|     public LeaveRemoteProctoringRoom() { | ||||
|     public CreateCollectingAllProctoringRoom() { | ||||
|         super(new TypeKey<>( | ||||
|                 CallType.UNDEFINED, | ||||
|                 EntityType.EXAM_PROCTOR_DATA, | ||||
|                 new TypeReference<List<SEBProctoringConnectionData>>() { | ||||
|                 new TypeReference<SEBProctoringConnectionData>() { | ||||
|                 }), | ||||
|                 HttpMethod.POST, | ||||
|                 MediaType.APPLICATION_FORM_URLENCODED, | ||||
|                 API.EXAM_MONITORING_ENDPOINT | ||||
|                 API.EXAM_PROCTORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT | ||||
|                         + API.PROCTORING_LEAVE_ROOM_PATH_SEGMENT); | ||||
|                         + API.EXAM_PROCTORING_JON_ALL_COLLECTING_ROOM); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,41 @@ | |||
| /* | ||||
|  * Copyright (c) 2020 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 DisposeCollectingAllProctoringRoom extends RestCall<Void> { | ||||
| 
 | ||||
|     public DisposeCollectingAllProctoringRoom() { | ||||
|         super(new TypeKey<>( | ||||
|                 CallType.UNDEFINED, | ||||
|                 EntityType.EXAM_PROCTOR_DATA, | ||||
|                 new TypeReference<Void>() { | ||||
|                 }), | ||||
|                 HttpMethod.POST, | ||||
|                 MediaType.APPLICATION_FORM_URLENCODED, | ||||
|                 API.EXAM_PROCTORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.EXAM_PROCTORING_REJON_ALL_COLLECTING_ROOM); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -36,9 +36,8 @@ public class GetProcotringRooms extends RestCall<Collection<RemoteProctoringRoom | |||
|                 }), | ||||
|                 HttpMethod.GET, | ||||
|                 MediaType.APPLICATION_FORM_URLENCODED, | ||||
|                 API.EXAM_MONITORING_ENDPOINT | ||||
|                 API.EXAM_PROCTORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT | ||||
|                         + API.PROCTORING_ROOMS_SEGMENT); | ||||
|                         + API.EXAM_PROCTORING_ROOMS_SEGMENT); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -34,9 +34,8 @@ public class GetProctorRoomConnectionData extends RestCall<SEBProctoringConnecti | |||
|                 }), | ||||
|                 HttpMethod.GET, | ||||
|                 MediaType.APPLICATION_FORM_URLENCODED, | ||||
|                 API.EXAM_MONITORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT); | ||||
|                 API.EXAM_PROCTORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -36,10 +36,9 @@ public class GetProctorRoomConnections extends RestCall<Collection<ClientConnect | |||
|                 }), | ||||
|                 HttpMethod.GET, | ||||
|                 MediaType.APPLICATION_FORM_URLENCODED, | ||||
|                 API.EXAM_MONITORING_ENDPOINT | ||||
|                 API.EXAM_PROCTORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT | ||||
|                         + API.PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT); | ||||
|                         + API.EXAM_PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -34,10 +34,9 @@ public class SendJoinRemoteProctoringRoom extends RestCall<SEBProctoringConnecti | |||
|                 }), | ||||
|                 HttpMethod.POST, | ||||
|                 MediaType.APPLICATION_FORM_URLENCODED, | ||||
|                 API.EXAM_MONITORING_ENDPOINT | ||||
|                 API.EXAM_PROCTORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT | ||||
|                         + API.PROCTORING_JOIN_ROOM_PATH_SEGMENT); | ||||
|                         + API.EXAM_PROCTORING_JOIN_ROOM_PATH_SEGMENT); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -33,10 +33,9 @@ public class SendProctoringBroadcastOffInstruction extends RestCall<Void> { | |||
|                 }), | ||||
|                 HttpMethod.POST, | ||||
|                 MediaType.APPLICATION_FORM_URLENCODED, | ||||
|                 API.EXAM_MONITORING_ENDPOINT | ||||
|                 API.EXAM_PROCTORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT | ||||
|                         + API.PROCTORING_BROADCAST_OFF_PATH_SEGMENT); | ||||
|                         + API.EXAM_PROCTORING_BROADCAST_OFF_PATH_SEGMENT); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -33,10 +33,9 @@ public class SendProctoringBroadcastOnInstruction extends RestCall<Void> { | |||
|                 }), | ||||
|                 HttpMethod.POST, | ||||
|                 MediaType.APPLICATION_FORM_URLENCODED, | ||||
|                 API.EXAM_MONITORING_ENDPOINT | ||||
|                 API.EXAM_PROCTORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT | ||||
|                         + API.PROCTORING_BROADCAST_ON_PATH_SEGMENT); | ||||
|                         + API.EXAM_PROCTORING_BROADCAST_ON_PATH_SEGMENT); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -33,10 +33,9 @@ public class SendRejoinExamCollectionRoom extends RestCall<Void> { | |||
|                 }), | ||||
|                 HttpMethod.POST, | ||||
|                 MediaType.APPLICATION_FORM_URLENCODED, | ||||
|                 API.EXAM_MONITORING_ENDPOINT | ||||
|                 API.EXAM_PROCTORING_ENDPOINT | ||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                         + API.PROCTORING_PATH_SEGMENT | ||||
|                         + API.PROCTORING_REJOIN_EXAM_ROOM_PATH_SEGMENT); | ||||
|                         + API.EXAM_PROCTORING_REJOIN_COLLECTING_ROOM_PATH_SEGMENT); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import java.util.Collection; | |||
| import java.util.HashMap; | ||||
| import java.util.HashSet; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| 
 | ||||
|  | @ -28,6 +29,8 @@ import ch.ethz.seb.sebserver.gbl.api.API; | |||
| import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; | ||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | ||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; | ||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.CreateCollectingAllProctoringRoom; | ||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.DisposeCollectingAllProctoringRoom; | ||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendJoinRemoteProctoringRoom; | ||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendRejoinExamCollectionRoom; | ||||
| 
 | ||||
|  | @ -84,37 +87,63 @@ public class ProctoringGUIService { | |||
|                 new ProctoringWindowData(examId, data)); | ||||
|     } | ||||
| 
 | ||||
|     public boolean hasRoom(final String roomName) { | ||||
|         return this.rooms.containsKey(roomName); | ||||
|     } | ||||
| 
 | ||||
|     public String getActiveAllRoom(final String examId) { | ||||
|         return this.rooms | ||||
|                 .values() | ||||
|                 .stream() | ||||
|                 .filter(data -> Objects.equals(data.examId, examId) && data.connections.isEmpty()) | ||||
|                 .findFirst() | ||||
|                 .map(data -> data.roomName) | ||||
|                 .orElseGet(() -> null); | ||||
|     } | ||||
| 
 | ||||
|     public Result<SEBProctoringConnectionData> registerNewSingleProcotringRoom( | ||||
|             final String examId, | ||||
|             final String roomName, | ||||
|             final String subject, | ||||
|             final String connectionToken) { | ||||
| 
 | ||||
|         return Result.tryCatch(() -> { | ||||
|             final SEBProctoringConnectionData connection = | ||||
|                     this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) | ||||
|         return this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) | ||||
|                 .withURIVariable(API.PARAM_MODEL_ID, examId) | ||||
|                 .withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName) | ||||
|                 .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) | ||||
|                 .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken) | ||||
|                 .call() | ||||
|                             .getOrThrow(); | ||||
| 
 | ||||
|                 .map(connection -> { | ||||
|                     this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionToken)); | ||||
|                     this.openWindows.add(roomName); | ||||
|                     return connection; | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     public Result<SEBProctoringConnectionData> registerAllProcotringRoom( | ||||
|             final String examId, | ||||
|             final String subject) { | ||||
| 
 | ||||
|         return this.restService.getBuilder(CreateCollectingAllProctoringRoom.class) | ||||
|                 .withURIVariable(API.PARAM_MODEL_ID, examId) | ||||
|                 .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) | ||||
|                 .call() | ||||
|                 .map(connection -> { | ||||
|                     this.rooms.put( | ||||
|                             connection.roomName, | ||||
|                             new RoomConnectionData(connection.roomName, examId)); | ||||
|                     this.openWindows.add(connection.roomName); | ||||
|                     return connection; | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     public Result<SEBProctoringConnectionData> registerNewProcotringRoom( | ||||
|             final String examId, | ||||
|             final String roomName, | ||||
|             final String subject, | ||||
|             final Collection<String> connectionTokens) { | ||||
| 
 | ||||
|         return Result.tryCatch(() -> { | ||||
|             final SEBProctoringConnectionData connection = | ||||
|                     this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) | ||||
|         return this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) | ||||
|                 .withURIVariable(API.PARAM_MODEL_ID, examId) | ||||
|                 .withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName) | ||||
|                 .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) | ||||
|  | @ -122,8 +151,7 @@ public class ProctoringGUIService { | |||
|                         API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||
|                         StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR)) | ||||
|                 .call() | ||||
|                             .getOrThrow(); | ||||
| 
 | ||||
|                 .map(connection -> { | ||||
|                     this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionTokens)); | ||||
|                     this.openWindows.add(roomName); | ||||
|                     return connection; | ||||
|  | @ -147,7 +175,9 @@ public class ProctoringGUIService { | |||
|                             API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||
|                             StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR)) | ||||
|                     .call() | ||||
|                     .getOrThrow(); | ||||
|                     .onError(error -> log.error("Failed to add connection to proctoring room: {} {}", | ||||
|                             room, | ||||
|                             error.getMessage())); | ||||
|             roomConnectionData.connections.addAll(connectionTokens); | ||||
|         } | ||||
|     } | ||||
|  | @ -164,15 +194,24 @@ public class ProctoringGUIService { | |||
|         final RoomConnectionData roomConnectionData = this.rooms.remove(name); | ||||
|         if (roomConnectionData != null) { | ||||
|             // send instruction to leave this room and join the own exam collection room | ||||
| 
 | ||||
|             if (!roomConnectionData.connections.isEmpty()) { | ||||
|                 this.restService.getBuilder(SendRejoinExamCollectionRoom.class) | ||||
|                         .withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId) | ||||
|                         .withFormParam( | ||||
|                                 API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||
|                                 StringUtils.join(roomConnectionData.connections, Constants.LIST_SEPARATOR_CHAR)) | ||||
|                         .call() | ||||
|                     .getOrThrow(); | ||||
| 
 | ||||
|                         .onError(error -> log.error("Failed to close proctoring room: {} {}", | ||||
|                                 name, | ||||
|                                 error.getMessage())); | ||||
|             } else { | ||||
|                 this.restService.getBuilder(DisposeCollectingAllProctoringRoom.class) | ||||
|                         .withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId) | ||||
|                         .call() | ||||
|                         .onError(error -> log.error("Failed to close proctoring room: {} {}", | ||||
|                                 name, | ||||
|                                 error.getMessage())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; | |||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | ||||
| 
 | ||||
| public interface ExamProctoringService { | ||||
|  | @ -20,14 +21,18 @@ public interface ExamProctoringService { | |||
|     Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring); | ||||
| 
 | ||||
|     Result<SEBProctoringConnectionData> createProctorPublicRoomConnection( | ||||
|             final ProctoringSettings examProctoring, | ||||
|             final ProctoringSettings proctoringSettings, | ||||
|             final String roomName, | ||||
|             final String subject); | ||||
| 
 | ||||
|     Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( | ||||
|             final ProctoringSettings examProctoring, | ||||
|             final ProctoringSettings proctoringSettings, | ||||
|             final String connectionToken); | ||||
| 
 | ||||
|     Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( | ||||
|             final ProctoringSettings proctoringSettings, | ||||
|             final ClientConnection connection); | ||||
| 
 | ||||
|     Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( | ||||
|             final ProctoringSettings proctoringSettings, | ||||
|             final String connectionToken, | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import java.security.InvalidKeyException; | |||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.util.Base64; | ||||
| import java.util.Base64.Encoder; | ||||
| import java.util.Collection; | ||||
| 
 | ||||
| import javax.crypto.Mac; | ||||
| import javax.crypto.spec.SecretKeySpec; | ||||
|  | @ -26,6 +27,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam; | |||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | ||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||
|  | @ -99,15 +101,27 @@ public class ExamJITSIProctoringService implements ExamProctoringService { | |||
|             final ProctoringSettings proctoringSettings, | ||||
|             final String connectionToken) { | ||||
| 
 | ||||
|         return Result.tryCatch(() -> { | ||||
|             final ClientConnectionData clientConnection = this.examSessionService | ||||
|         return this.examSessionService | ||||
|                 .getConnectionData(connectionToken) | ||||
|                     .getOrThrow(); | ||||
|             final RemoteProctoringRoom room = this.examSessionService | ||||
|                 .flatMap(connection -> getClientExamCollectionRoomConnectionData( | ||||
|                         proctoringSettings, | ||||
|                         connection.clientConnection)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( | ||||
|             final ProctoringSettings proctoringSettings, | ||||
|             final ClientConnection connection) { | ||||
| 
 | ||||
|         return Result.tryCatch(() -> { | ||||
| 
 | ||||
|             final Collection<RemoteProctoringRoom> remoteProctoringRooms = this.examSessionService | ||||
|                     .getExamSessionCacheService() | ||||
|                     .getRemoteProctoringRooms(clientConnection.clientConnection.examId) | ||||
|                     .getRemoteProctoringRooms(connection.examId); | ||||
| 
 | ||||
|             final RemoteProctoringRoom room = remoteProctoringRooms | ||||
|                     .stream() | ||||
|                     .filter(r -> r.id.equals(clientConnection.clientConnection.remoteProctoringRoomId)) | ||||
|                     .filter(r -> r.id.equals(connection.remoteProctoringRoomId)) | ||||
|                     .findFirst() | ||||
|                     .orElseGet(() -> { | ||||
|                         throw new RuntimeException("No exam proctoring room found for clientConnection"); | ||||
|  | @ -119,10 +133,10 @@ public class ExamJITSIProctoringService implements ExamProctoringService { | |||
|                     proctoringSettings.serverURL, | ||||
|                     proctoringSettings.appKey, | ||||
|                     proctoringSettings.getAppSecret(), | ||||
|                     clientConnection.clientConnection.userSessionId, | ||||
|                     connection.userSessionId, | ||||
|                     "seb-client", | ||||
|                     room.name, | ||||
|                     clientConnection.clientConnection.userSessionId, | ||||
|                     connection.userSessionId, | ||||
|                     forExam(proctoringSettings)) | ||||
|                             .getOrThrow(); | ||||
|         }); | ||||
|  |  | |||
|  | @ -124,7 +124,8 @@ public class ExamProcotringRoomServiceImpl implements ExamProcotringRoomService | |||
|                 .build() | ||||
|                 .execute(); | ||||
| 
 | ||||
|         flagUpdated(toUpdate).stream() | ||||
|         flagUpdated(toUpdate) | ||||
|                 .stream() | ||||
|                 .forEach(cc -> { | ||||
|                     if (ConnectionStatus.ACTIVE.name().equals(cc.getStatus())) { | ||||
|                         assignToRoom(cc); | ||||
|  | @ -156,13 +157,14 @@ public class ExamProcotringRoomServiceImpl implements ExamProcotringRoomService | |||
|                 ACTIVE_COLLECTING_ALL_ROOM_ATTRIBUTE_NAME); | ||||
|     } | ||||
| 
 | ||||
|     private Result<String> getActiveCollectingAllRoom(final Long examId) { | ||||
|     private String getActiveCollectingAllRoom(final Long examId) { | ||||
|         return this.additionalAttributesDAO | ||||
|                 .getAdditionalAttribute( | ||||
|                         EntityType.EXAM, | ||||
|                         examId, | ||||
|                         ACTIVE_COLLECTING_ALL_ROOM_ATTRIBUTE_NAME) | ||||
|                 .map(attr -> attr.getValue()); | ||||
|                 .map(attr -> attr.getValue()) | ||||
|                 .getOr(null); | ||||
|     } | ||||
| 
 | ||||
|     // TODO considering doing bulk update here | ||||
|  | @ -202,8 +204,14 @@ public class ExamProcotringRoomServiceImpl implements ExamProcotringRoomService | |||
|                         proctoringRoom.id, | ||||
|                         0)); | ||||
|                 this.examSessionCacheService.evictRemoteProctoringRooms(cc.getExamId()); | ||||
|                 this.examSessionCacheService.evictClientConnection(cc.getConnectionToken()); | ||||
|                 final String activeCollectingAllRoom = getActiveCollectingAllRoom(cc.getExamId()); | ||||
|                 if (activeCollectingAllRoom != null) { | ||||
|                     applyProcotringInstruction(cc.getExamId(), cc.getConnectionToken(), activeCollectingAllRoom); | ||||
|                 } else { | ||||
|                     applyProcotringInstruction(cc.getExamId(), cc.getConnectionToken(), proctoringRoom.name); | ||||
|                 } | ||||
|             } | ||||
|         } catch (final Exception e) { | ||||
|             log.error("Failed to process proctoring room update for client connection: {}", cc.getConnectionToken(), e); | ||||
|             try { | ||||
|  |  | |||
|  | @ -21,7 +21,6 @@ import java.util.stream.Collectors; | |||
| import org.apache.commons.lang3.BooleanUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.cache.Cache; | ||||
| import org.springframework.cache.CacheManager; | ||||
| import org.springframework.context.annotation.Lazy; | ||||
| import org.springframework.security.access.AccessDeniedException; | ||||
|  | @ -314,9 +313,15 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
| 
 | ||||
|     @Override | ||||
|     public Result<ClientConnectionData> getConnectionData(final String connectionToken) { | ||||
| 
 | ||||
|         return Result.tryCatch(() -> { | ||||
|             final Cache cache = this.cacheManager.getCache(ExamSessionCacheService.CACHE_NAME_ACTIVE_CLIENT_CONNECTION); | ||||
|             return cache.get(connectionToken, ClientConnectionData.class); | ||||
|             final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService | ||||
|                     .getActiveClientConnection(connectionToken); | ||||
|             if (activeClientConnection == null) { | ||||
|                 throw new NoSuchElementException("Client Connection with token: " + connectionToken); | ||||
|             } | ||||
| 
 | ||||
|             return activeClientConnection; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -383,7 +383,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> { | |||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.PROCTORING_PATH_SEGMENT, | ||||
|                     + API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT, | ||||
|             method = RequestMethod.GET, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public ProctoringSettings getExamProctoring( | ||||
|  | @ -402,7 +402,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> { | |||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.PROCTORING_PATH_SEGMENT, | ||||
|                     + API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public Exam saveExamProctoring( | ||||
|  |  | |||
|  | @ -9,19 +9,14 @@ | |||
| package ch.ethz.seb.sebserver.webservice.weblayer.api; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.EnumSet; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.validation.Valid; | ||||
| 
 | ||||
| import org.apache.commons.lang3.BooleanUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | @ -44,25 +39,16 @@ import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; | |||
| import ch.ethz.seb.sebserver.gbl.model.Domain; | ||||
| import ch.ethz.seb.sebserver.gbl.model.Page; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Exam; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | ||||
| import ch.ethz.seb.sebserver.gbl.model.user.UserRole; | ||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProcotringRoomService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService; | ||||
|  | @ -76,27 +62,21 @@ public class ExamMonitoringController { | |||
| 
 | ||||
|     private final SEBClientConnectionService sebClientConnectionService; | ||||
|     private final ExamSessionService examSessionService; | ||||
|     private final ExamAdminService examAdminService; | ||||
|     private final SEBInstructionService sebInstructionService; | ||||
|     private final AuthorizationService authorization; | ||||
|     private final PaginationService paginationService; | ||||
|     private final ExamProcotringRoomService examProcotringRoomService; | ||||
| 
 | ||||
|     public ExamMonitoringController( | ||||
|             final ExamAdminService examAdminService, | ||||
|             final SEBClientConnectionService sebClientConnectionService, | ||||
|             final SEBInstructionService sebInstructionService, | ||||
|             final AuthorizationService authorization, | ||||
|             final PaginationService paginationService, | ||||
|             final ExamProcotringRoomService examProcotringRoomService) { | ||||
|             final PaginationService paginationService) { | ||||
| 
 | ||||
|         this.examAdminService = examAdminService; | ||||
|         this.sebClientConnectionService = sebClientConnectionService; | ||||
|         this.examSessionService = sebClientConnectionService.getExamSessionService(); | ||||
|         this.sebInstructionService = sebInstructionService; | ||||
|         this.authorization = authorization; | ||||
|         this.paginationService = paginationService; | ||||
|         this.examProcotringRoomService = examProcotringRoomService; | ||||
|     } | ||||
| 
 | ||||
|     /** This is called by Spring to initialize the WebDataBinder and is used here to | ||||
|  | @ -289,387 +269,6 @@ public class ExamMonitoringController { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     //*********************************************************************************************** | ||||
|     //**** Proctoring | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.PROCTORING_PATH_SEGMENT | ||||
|                     + API.PROCTORING_ROOMS_SEGMENT, | ||||
|             method = RequestMethod.GET, | ||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public Collection<RemoteProctoringRoom> getDefaultProcotringRoomsOfExam(@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) { | ||||
| 
 | ||||
|         return this.examProcotringRoomService | ||||
|                 .getProctoringRooms(examId) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.PROCTORING_PATH_SEGMENT, | ||||
|             method = RequestMethod.GET, | ||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public SEBProctoringConnectionData getProctorRoomData( | ||||
|             @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, | ||||
|             @RequestParam(name = SEBProctoringConnectionData.ATTR_ROOM_NAME, required = true) final String roomName, | ||||
|             @RequestParam(name = SEBProctoringConnectionData.ATTR_SUBJECT, required = false) final String subject) { | ||||
| 
 | ||||
|         this.authorization.check( | ||||
|                 PrivilegeType.READ, | ||||
|                 EntityType.EXAM, | ||||
|                 institutionId); | ||||
| 
 | ||||
|         this.authorization.checkRead( | ||||
|                 this.examSessionService.getExamDAO().byPK(examId).getOrThrow()); | ||||
| 
 | ||||
|         return this.examSessionService.getRunningExam(examId) | ||||
|                 .flatMap(this.authorization::checkRead) | ||||
|                 .flatMap(this.examAdminService::getExamProctoring) | ||||
|                 .flatMap(proc -> this.examAdminService | ||||
|                         .getExamProctoringService(proc.serverType) | ||||
|                         .flatMap(s -> s.createProctorPublicRoomConnection( | ||||
|                                 proc, | ||||
|                                 roomName, | ||||
|                                 StringUtils.isNoneBlank(subject) ? subject : roomName))) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.PROCTORING_PATH_SEGMENT | ||||
|                     + API.PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT, | ||||
|             method = RequestMethod.GET, | ||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public Collection<ClientConnection> getProctorRoomConnectionData( | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, | ||||
|                     required = true) final String roomName) { | ||||
| 
 | ||||
|         this.authorization.check( | ||||
|                 PrivilegeType.READ, | ||||
|                 EntityType.EXAM, | ||||
|                 institutionId); | ||||
| 
 | ||||
|         this.authorization.checkRead( | ||||
|                 this.examSessionService.getExamDAO().byPK(examId).getOrThrow()); | ||||
| 
 | ||||
|         return this.examProcotringRoomService.getRoomConnections(examId, roomName) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.PROCTORING_PATH_SEGMENT | ||||
|                     + API.PROCTORING_BROADCAST_ON_PATH_SEGMENT, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public void sendBroadcastOn( | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, | ||||
|                     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, | ||||
|             @RequestParam( | ||||
|                     name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO, | ||||
|                     required = false) final Boolean sendReceiveVideo, | ||||
|             @RequestParam( | ||||
|                     name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT, | ||||
|                     required = false) final Boolean sendAllowChat) { | ||||
| 
 | ||||
|         this.authorization.check( | ||||
|                 PrivilegeType.READ, | ||||
|                 EntityType.EXAM, | ||||
|                 institutionId); | ||||
| 
 | ||||
|         this.authorization.checkRead( | ||||
|                 this.examSessionService.getExamDAO().byPK(examId).getOrThrow()); | ||||
| 
 | ||||
|         final Map<String, String> attributes = createProctorInstructionAttributes( | ||||
|                 sendReceiveAudio, | ||||
|                 sendReceiveVideo, | ||||
|                 sendAllowChat, | ||||
|                 Constants.TRUE_STRING); | ||||
| 
 | ||||
|         sendProctoringInstructions(examId, roomName, connectionTokens, attributes); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.PROCTORING_PATH_SEGMENT | ||||
|                     + API.PROCTORING_BROADCAST_OFF_PATH_SEGMENT, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public void sendBroadcastOff( | ||||
|             @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, | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO, | ||||
|                     required = false) final Boolean sendReceiveVideo, | ||||
|             @RequestParam( | ||||
|                     name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT, | ||||
|                     required = false) final Boolean sendAllowChat) { | ||||
| 
 | ||||
|         this.authorization.check( | ||||
|                 PrivilegeType.READ, | ||||
|                 EntityType.EXAM, | ||||
|                 institutionId); | ||||
| 
 | ||||
|         this.authorization.checkRead( | ||||
|                 this.examSessionService.getExamDAO().byPK(examId).getOrThrow()); | ||||
| 
 | ||||
|         final Map<String, String> attributes = createProctorInstructionAttributes( | ||||
|                 sendReceiveAudio, | ||||
|                 sendReceiveVideo, | ||||
|                 sendAllowChat, | ||||
|                 Constants.FALSE_STRING); | ||||
| 
 | ||||
|         if (attributes.isEmpty()) { | ||||
|             log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         sendProctoringInstructions(examId, roomName, connectionTokens, attributes); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.PROCTORING_PATH_SEGMENT | ||||
|                     + API.PROCTORING_REJOIN_EXAM_ROOM_PATH_SEGMENT, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public void sendRejoinExamCollectionRoomToClients( | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||
|                     required = true) final String connectionTokens) { | ||||
| 
 | ||||
|         this.authorization.check( | ||||
|                 PrivilegeType.READ, | ||||
|                 EntityType.EXAM, | ||||
|                 institutionId); | ||||
| 
 | ||||
|         this.authorization.checkRead( | ||||
|                 this.examSessionService.getExamDAO().byPK(examId).getOrThrow()); | ||||
| 
 | ||||
|         final ProctoringSettings settings = this.examSessionService | ||||
|                 .getRunningExam(examId) | ||||
|                 .flatMap(this.authorization::checkRead) | ||||
|                 .flatMap(this.examAdminService::getExamProctoring) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         final ExamProctoringService examProctoringService = this.examAdminService | ||||
|                 .getExamProctoringService(settings.serverType) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR)) | ||||
|                 .stream() | ||||
|                 .forEach(connectionToken -> { | ||||
|                     final Result<Void> result = examProctoringService | ||||
|                             .getClientExamCollectionRoomConnectionData( | ||||
|                                     settings, | ||||
|                                     connectionToken) | ||||
|                             .flatMap(data -> this.sendJoinInstruction(examId, connectionTokens, data)); | ||||
|                     if (result.hasError()) { | ||||
|                         // TODO log | ||||
|                     } | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.PROCTORING_PATH_SEGMENT | ||||
|                     + API.PROCTORING_JOIN_ROOM_PATH_SEGMENT, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public SEBProctoringConnectionData sendJoinProctoringRoomToClients( | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = SEBProctoringConnectionData.ATTR_ROOM_NAME, | ||||
|                     required = true) final String roomName, | ||||
|             @RequestParam( | ||||
|                     name = SEBProctoringConnectionData.ATTR_SUBJECT, | ||||
|                     required = false) final String subject, | ||||
|             @RequestParam( | ||||
|                     name = API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||
|                     required = true) final String connectionTokens) { | ||||
| 
 | ||||
|         this.authorization.check( | ||||
|                 PrivilegeType.READ, | ||||
|                 EntityType.EXAM, | ||||
|                 institutionId); | ||||
| 
 | ||||
|         this.authorization.checkRead( | ||||
|                 this.examSessionService.getExamDAO().byPK(examId).getOrThrow()); | ||||
| 
 | ||||
|         final ProctoringSettings settings = this.examSessionService | ||||
|                 .getRunningExam(examId) | ||||
|                 .flatMap(this.authorization::checkRead) | ||||
|                 .flatMap(this.examAdminService::getExamProctoring) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         final ExamProctoringService examProctoringService = this.examAdminService | ||||
|                 .getExamProctoringService(settings.serverType) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         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() | ||||
|                             .map(connectionToken -> { | ||||
|                                 final SEBProctoringConnectionData data = (single) | ||||
|                                         ? examProctoringService | ||||
|                                                 .getClientRoomConnectionData(settings, connectionToken) | ||||
|                                                 .getOrThrow() | ||||
|                                         : examProctoringService | ||||
|                                                 .getClientRoomConnectionData( | ||||
|                                                         settings, | ||||
|                                                         connectionToken, | ||||
|                                                         roomName, | ||||
|                                                         (StringUtils.isNotBlank(subject)) ? subject : roomName) | ||||
|                                                 .getOrThrow(); | ||||
|                                 sendJoinInstruction(examId, connectionToken, data) | ||||
|                                         .onError(error -> log.error( | ||||
|                                                 "Failed to send proctoring leave instruction to client: {} ", | ||||
|                                                 connectionToken, error)); | ||||
|                                 return data; | ||||
|                             }).collect(Collectors.toList()); | ||||
|         } | ||||
| 
 | ||||
|         return examProctoringService.createProctorPublicRoomConnection( | ||||
|                 settings, | ||||
|                 roomName, | ||||
|                 (StringUtils.isNotBlank(subject)) ? subject : roomName) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|     private void sendProctoringInstructions( | ||||
|             final Long examId, | ||||
|             final String roomName, | ||||
|             final String connectionTokens, | ||||
|             final Map<String, String> attributes) { | ||||
| 
 | ||||
|         if (attributes.isEmpty()) { | ||||
|             log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         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"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private Map<String, String> createProctorInstructionAttributes( | ||||
|             final Boolean sendReceiveAudio, | ||||
|             final Boolean sendReceiveVideo, | ||||
|             final Boolean sendAllowChat, | ||||
|             final String flagValue) { | ||||
|         final Map<String, String> attributes = new HashMap<>(); | ||||
|         if (BooleanUtils.isTrue(sendReceiveAudio)) { | ||||
|             attributes.put( | ||||
|                     ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, | ||||
|                     flagValue); | ||||
|         } | ||||
|         if (BooleanUtils.isTrue(sendReceiveVideo)) { | ||||
|             attributes.put( | ||||
|                     ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO, | ||||
|                     flagValue); | ||||
|         } | ||||
|         if (BooleanUtils.isTrue(sendAllowChat)) { | ||||
|             attributes.put( | ||||
|                     ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT, | ||||
|                     flagValue); | ||||
|         } | ||||
|         return attributes; | ||||
|     } | ||||
| 
 | ||||
|     //**** Proctoring | ||||
|     //*********************************************************************************************** | ||||
| 
 | ||||
|     private boolean hasRunningExamPrivilege(final Long examId, final Long institution) { | ||||
|         return hasRunningExamPrivilege( | ||||
|                 this.examSessionService.getRunningExam(examId).getOr(null), | ||||
|  | @ -685,53 +284,4 @@ public class ExamMonitoringController { | |||
|         return exam.institutionId.equals(institution) && exam.isOwner(userId); | ||||
|     } | ||||
| 
 | ||||
|     private Result<Void> sendJoinInstruction( | ||||
|             final Long examId, | ||||
|             final String connectionToken, | ||||
|             final SEBProctoringConnectionData data) { | ||||
| 
 | ||||
|         final Map<String, String> attributes = new HashMap<>(); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.SERVICE_TYPE, | ||||
|                 ProctoringSettings.ProctoringServerType.JITSI_MEET.name()); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.METHOD, | ||||
|                 ClientInstruction.ProctoringInstructionMethod.JOIN.name()); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_URL, | ||||
|                 data.serverURL); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_ROOM, | ||||
|                 data.roomName); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN, | ||||
|                 data.accessToken); | ||||
|         return this.sebInstructionService.registerInstruction( | ||||
|                 examId, | ||||
|                 InstructionType.SEB_PROCTORING, | ||||
|                 attributes, | ||||
|                 connectionToken, | ||||
|                 true); | ||||
|     } | ||||
| 
 | ||||
| //    private Result<Void> sendLeaveInstruction( | ||||
| //            final Long examId, | ||||
| //            final String connectionToken, | ||||
| //            final SEBProctoringConnectionData data) { | ||||
| // | ||||
| //        return sendProctorInstruction( | ||||
| //                examId, | ||||
| //                connectionToken, | ||||
| //                data, | ||||
| //                ClientInstruction.ProctoringInstructionMethod.LEAVE.name()); | ||||
| //    } | ||||
| 
 | ||||
| //    PRIVATE RESULT<VOID> SENDPROCTORINSTRUCTION( | ||||
| //            FINAL LONG EXAMID, | ||||
| //            FINAL STRING CONNECTIONTOKEN, | ||||
| //            FINAL SEBPROCTORINGCONNECTIONDATA DATA, | ||||
| //            FINAL STRING METHOD) { | ||||
| // | ||||
| //    } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,587 @@ | |||
| /* | ||||
|  * Copyright (c) 2020 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.webservice.weblayer.api; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import org.apache.commons.lang3.BooleanUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.web.bind.WebDataBinder; | ||||
| import org.springframework.web.bind.annotation.InitBinder; | ||||
| import org.springframework.web.bind.annotation.PathVariable; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMethod; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| 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.api.authorization.PrivilegeType; | ||||
| import ch.ethz.seb.sebserver.gbl.model.Domain; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType; | ||||
| 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.servicelayer.authorization.AuthorizationService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProcotringRoomService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService; | ||||
| 
 | ||||
| @WebServiceProfile | ||||
| @RestController | ||||
| @RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_PROCTORING_ENDPOINT) | ||||
| public class ExamProctoringController { | ||||
| 
 | ||||
|     private static final Logger log = LoggerFactory.getLogger(ExamProctoringController.class); | ||||
| 
 | ||||
|     private final ExamProcotringRoomService examProcotringRoomService; | ||||
|     private final ExamAdminService examAdminService; | ||||
|     private final SEBInstructionService sebInstructionService; | ||||
|     private final AuthorizationService authorization; | ||||
|     private final ExamSessionService examSessionService; | ||||
| 
 | ||||
|     public ExamProctoringController( | ||||
|             final ExamProcotringRoomService examProcotringRoomService, | ||||
|             final ExamAdminService examAdminService, | ||||
|             final SEBInstructionService sebInstructionService, | ||||
|             final AuthorizationService authorization, | ||||
|             final ExamSessionService examSessionService) { | ||||
| 
 | ||||
|         this.examProcotringRoomService = examProcotringRoomService; | ||||
|         this.examAdminService = examAdminService; | ||||
|         this.sebInstructionService = sebInstructionService; | ||||
|         this.authorization = authorization; | ||||
|         this.examSessionService = examSessionService; | ||||
|     } | ||||
| 
 | ||||
|     /** This is called by Spring to initialize the WebDataBinder and is used here to | ||||
|      * initialize the default value binding for the institutionId request-parameter | ||||
|      * that has the current users insitutionId as default. | ||||
|      * | ||||
|      * See also UserService.addUsersInstitutionDefaultPropertySupport */ | ||||
|     @InitBinder | ||||
|     public void initBinder(final WebDataBinder binder) { | ||||
|         this.authorization | ||||
|                 .getUserService() | ||||
|                 .addUsersInstitutionDefaultPropertySupport(binder); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.EXAM_PROCTORING_ROOMS_SEGMENT, | ||||
|             method = RequestMethod.GET, | ||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public Collection<RemoteProctoringRoom> getDefaultProcotringRoomsOfExam( | ||||
|             @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) { | ||||
| 
 | ||||
|         return this.examProcotringRoomService | ||||
|                 .getProctoringRooms(examId) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT, | ||||
|             method = RequestMethod.GET, | ||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public SEBProctoringConnectionData getProctorRoomData( | ||||
|             @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, | ||||
|             @RequestParam(name = SEBProctoringConnectionData.ATTR_ROOM_NAME, required = true) final String roomName, | ||||
|             @RequestParam(name = SEBProctoringConnectionData.ATTR_SUBJECT, required = false) final String subject) { | ||||
| 
 | ||||
|         checkAccess(institutionId, examId); | ||||
| 
 | ||||
|         return this.examSessionService.getRunningExam(examId) | ||||
|                 .flatMap(this.authorization::checkRead) | ||||
|                 .flatMap(this.examAdminService::getExamProctoring) | ||||
|                 .flatMap(proc -> this.examAdminService | ||||
|                         .getExamProctoringService(proc.serverType) | ||||
|                         .flatMap(s -> s.createProctorPublicRoomConnection( | ||||
|                                 proc, | ||||
|                                 roomName, | ||||
|                                 StringUtils.isNoneBlank(subject) ? subject : roomName))) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.EXAM_PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT, | ||||
|             method = RequestMethod.GET, | ||||
|             consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public Collection<ClientConnection> getProctorRoomConnectionData( | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, | ||||
|                     required = true) final String roomName) { | ||||
| 
 | ||||
|         checkAccess(institutionId, examId); | ||||
| 
 | ||||
|         return this.examProcotringRoomService.getRoomConnections(examId, roomName) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.EXAM_PROCTORING_BROADCAST_ON_PATH_SEGMENT, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public void sendBroadcastOn( | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, | ||||
|                     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, | ||||
|             @RequestParam( | ||||
|                     name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO, | ||||
|                     required = false) final Boolean sendReceiveVideo, | ||||
|             @RequestParam( | ||||
|                     name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT, | ||||
|                     required = false) final Boolean sendAllowChat) { | ||||
| 
 | ||||
|         checkAccess(institutionId, examId); | ||||
| 
 | ||||
|         final Map<String, String> attributes = createProctorInstructionAttributes( | ||||
|                 sendReceiveAudio, | ||||
|                 sendReceiveVideo, | ||||
|                 sendAllowChat, | ||||
|                 Constants.TRUE_STRING); | ||||
| 
 | ||||
|         sendProctoringInstructions(examId, roomName, connectionTokens, attributes); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.EXAM_PROCTORING_BROADCAST_OFF_PATH_SEGMENT, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public void sendBroadcastOff( | ||||
|             @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, | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO, | ||||
|                     required = false) final Boolean sendReceiveVideo, | ||||
|             @RequestParam( | ||||
|                     name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT, | ||||
|                     required = false) final Boolean sendAllowChat) { | ||||
| 
 | ||||
|         checkAccess(institutionId, examId); | ||||
| 
 | ||||
|         final Map<String, String> attributes = createProctorInstructionAttributes( | ||||
|                 sendReceiveAudio, | ||||
|                 sendReceiveVideo, | ||||
|                 sendAllowChat, | ||||
|                 Constants.FALSE_STRING); | ||||
| 
 | ||||
|         if (attributes.isEmpty()) { | ||||
|             log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         sendProctoringInstructions(examId, roomName, connectionTokens, attributes); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.EXAM_PROCTORING_REJOIN_COLLECTING_ROOM_PATH_SEGMENT, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public void sendRejoinExamCollectionRoomToClients( | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||
|                     required = true) final String connectionTokens) { | ||||
| 
 | ||||
|         checkAccess(institutionId, examId); | ||||
| 
 | ||||
|         final ProctoringSettings settings = this.examSessionService | ||||
|                 .getRunningExam(examId) | ||||
|                 .flatMap(this.examAdminService::getExamProctoring) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         final ExamProctoringService examProctoringService = this.examAdminService | ||||
|                 .getExamProctoringService(settings.serverType) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR)) | ||||
|                 .stream() | ||||
|                 .forEach(connectionToken -> { | ||||
|                     examProctoringService | ||||
|                             .getClientExamCollectionRoomConnectionData( | ||||
|                                     settings, | ||||
|                                     connectionToken) | ||||
|                             .flatMap(data -> this.sendJoinInstruction(examId, connectionToken, data)) | ||||
|                             .onError(error -> log.error("Failed to send rejoin for: {} cause: {}", | ||||
|                                     connectionToken, | ||||
|                                     error.getMessage())); | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.EXAM_PROCTORING_JOIN_ROOM_PATH_SEGMENT, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public SEBProctoringConnectionData sendJoinProctoringRoomToClients( | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = SEBProctoringConnectionData.ATTR_ROOM_NAME, | ||||
|                     required = true) final String roomName, | ||||
|             @RequestParam( | ||||
|                     name = SEBProctoringConnectionData.ATTR_SUBJECT, | ||||
|                     required = false) final String subject, | ||||
|             @RequestParam( | ||||
|                     name = API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||
|                     required = true) final String connectionTokens) { | ||||
| 
 | ||||
|         checkAccess(institutionId, examId); | ||||
| 
 | ||||
|         final ProctoringSettings settings = this.examSessionService | ||||
|                 .getRunningExam(examId) | ||||
|                 .flatMap(this.examAdminService::getExamProctoring) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         final ExamProctoringService examProctoringService = this.examAdminService | ||||
|                 .getExamProctoringService(settings.serverType) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         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 -> { | ||||
|                                 final SEBProctoringConnectionData data = (single) | ||||
|                                         ? examProctoringService | ||||
|                                                 .getClientRoomConnectionData(settings, connectionToken) | ||||
|                                                 .onError(error -> log.error( | ||||
|                                                         "Failed to get client room connection data for {} cause: {}", | ||||
|                                                         connectionToken, | ||||
|                                                         error.getMessage())) | ||||
|                                                 .get() | ||||
|                                         : examProctoringService | ||||
|                                                 .getClientRoomConnectionData( | ||||
|                                                         settings, | ||||
|                                                         connectionToken, | ||||
|                                                         roomName, | ||||
|                                                         (StringUtils.isNotBlank(subject)) ? subject : roomName) | ||||
|                                                 .onError(error -> log.error( | ||||
|                                                         "Failed to get client room connection data for {} cause: {}", | ||||
|                                                         connectionToken, | ||||
|                                                         error.getMessage())) | ||||
|                                                 .get(); | ||||
|                                 if (data != null) { | ||||
|                                     sendJoinInstruction(examId, connectionToken, data) | ||||
|                                             .onError(error -> log.error( | ||||
|                                                     "Failed to send proctoring leave instruction to client: {}  cause: {}", | ||||
|                                                     connectionToken, | ||||
|                                                     error.getMessage())); | ||||
|                                 } | ||||
|                             }); | ||||
|         } | ||||
| 
 | ||||
|         return examProctoringService.createProctorPublicRoomConnection( | ||||
|                 settings, | ||||
|                 roomName, | ||||
|                 (StringUtils.isNotBlank(subject)) ? subject : roomName) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.EXAM_PROCTORING_JON_ALL_COLLECTING_ROOM, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public SEBProctoringConnectionData sendJoinAllCollectingRoomToClients( | ||||
|             @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, | ||||
|             @RequestParam( | ||||
|                     name = SEBProctoringConnectionData.ATTR_SUBJECT, | ||||
|                     required = false) final String subject) { | ||||
| 
 | ||||
|         checkAccess(institutionId, examId); | ||||
| 
 | ||||
|         final ProctoringSettings settings = this.examSessionService | ||||
|                 .getRunningExam(examId) | ||||
|                 .flatMap(this.examAdminService::getExamProctoring) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         final ExamProctoringService examProctoringService = this.examAdminService | ||||
|                 .getExamProctoringService(settings.serverType) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         // first create and register a room to collect all connection of the exam | ||||
|         // As long as the room exists new connections will join this room immediately | ||||
|         // after have been applied to the default collecting room | ||||
|         final String roomName = this.examProcotringRoomService.createCollectAllRoom(examId) | ||||
|                 .onError(error -> this.examProcotringRoomService.disposeCollectAllRoom(examId)) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         // get all active connections for the exam and send the join instruction | ||||
|         this.examSessionService.getConnectionData( | ||||
|                 examId, | ||||
|                 cData -> cData.clientConnection.status == ConnectionStatus.ACTIVE | ||||
|                         || cData.clientConnection.status == ConnectionStatus.CLOSED) | ||||
|                 .getOrThrow() | ||||
|                 .stream() | ||||
|                 .forEach(cc -> { | ||||
|                     final SEBProctoringConnectionData data = examProctoringService | ||||
|                             .getClientRoomConnectionData( | ||||
|                                     settings, | ||||
|                                     cc.clientConnection.connectionToken, | ||||
|                                     roomName, | ||||
|                                     (StringUtils.isNotBlank(subject)) ? subject : roomName) | ||||
|                             .onError(error -> log.error( | ||||
|                                     "Failed to get client room connection data for {} cause: {}", | ||||
|                                     cc.clientConnection.connectionToken, | ||||
|                                     error.getMessage())) | ||||
|                             .get(); | ||||
|                     if (data != null) { | ||||
|                         sendJoinInstruction(examId, cc.clientConnection.connectionToken, data) | ||||
|                                 .onError(error -> log.error( | ||||
|                                         "Failed to send proctoring leave instruction to client: {} ", | ||||
|                                         cc.clientConnection.connectionToken, error)); | ||||
|                     } | ||||
|                 }); | ||||
| 
 | ||||
|         return examProctoringService.createProctorPublicRoomConnection( | ||||
|                 settings, | ||||
|                 roomName, | ||||
|                 (StringUtils.isNotBlank(subject)) ? subject : roomName) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping( | ||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||
|                     + API.EXAM_PROCTORING_REJON_ALL_COLLECTING_ROOM, | ||||
|             method = RequestMethod.POST, | ||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
|     public void sendReJoinAllCollectingRoomToClients( | ||||
|             @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) { | ||||
| 
 | ||||
|         checkAccess(institutionId, examId); | ||||
| 
 | ||||
|         final ProctoringSettings settings = this.examSessionService | ||||
|                 .getRunningExam(examId) | ||||
|                 .flatMap(this.examAdminService::getExamProctoring) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         final ExamProctoringService examProctoringService = this.examAdminService | ||||
|                 .getExamProctoringService(settings.serverType) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         // first unregister the current room to collect all connection of the exam | ||||
|         this.examProcotringRoomService.disposeCollectAllRoom(examId); | ||||
| 
 | ||||
|         // get all active connections for the exam and send the join instruction | ||||
|         this.examSessionService.getConnectionData( | ||||
|                 examId, | ||||
|                 cData -> cData.clientConnection.status == ConnectionStatus.ACTIVE | ||||
|                         || cData.clientConnection.status == ConnectionStatus.CLOSED) | ||||
|                 .getOrThrow() | ||||
|                 .stream() | ||||
|                 .forEach(cc -> { | ||||
|                     examProctoringService | ||||
|                             .getClientExamCollectionRoomConnectionData( | ||||
|                                     settings, | ||||
|                                     cc.clientConnection) | ||||
|                             .flatMap(data -> this.sendJoinInstruction( | ||||
|                                     examId, | ||||
|                                     cc.clientConnection.connectionToken, | ||||
|                                     data)) | ||||
|                             .onError(error -> log.error("Failed to send rejoin for: {} cause: {}", | ||||
|                                     cc.clientConnection.connectionToken, | ||||
|                                     error.getMessage())); | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     private void sendProctoringInstructions( | ||||
|             final Long examId, | ||||
|             final String roomName, | ||||
|             final String connectionTokens, | ||||
|             final Map<String, String> attributes) { | ||||
| 
 | ||||
|         if (attributes.isEmpty()) { | ||||
|             log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         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"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private Map<String, String> createProctorInstructionAttributes( | ||||
|             final Boolean sendReceiveAudio, | ||||
|             final Boolean sendReceiveVideo, | ||||
|             final Boolean sendAllowChat, | ||||
|             final String flagValue) { | ||||
|         final Map<String, String> attributes = new HashMap<>(); | ||||
|         if (BooleanUtils.isTrue(sendReceiveAudio)) { | ||||
|             attributes.put( | ||||
|                     ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, | ||||
|                     flagValue); | ||||
|         } | ||||
|         if (BooleanUtils.isTrue(sendReceiveVideo)) { | ||||
|             attributes.put( | ||||
|                     ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO, | ||||
|                     flagValue); | ||||
|         } | ||||
|         if (BooleanUtils.isTrue(sendAllowChat)) { | ||||
|             attributes.put( | ||||
|                     ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT, | ||||
|                     flagValue); | ||||
|         } | ||||
|         return attributes; | ||||
|     } | ||||
| 
 | ||||
|     private Result<Void> sendJoinInstruction( | ||||
|             final Long examId, | ||||
|             final String connectionToken, | ||||
|             final SEBProctoringConnectionData data) { | ||||
| 
 | ||||
|         final Map<String, String> attributes = new HashMap<>(); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.SERVICE_TYPE, | ||||
|                 ProctoringSettings.ProctoringServerType.JITSI_MEET.name()); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.METHOD, | ||||
|                 ClientInstruction.ProctoringInstructionMethod.JOIN.name()); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_URL, | ||||
|                 data.serverURL); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_ROOM, | ||||
|                 data.roomName); | ||||
|         attributes.put( | ||||
|                 ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN, | ||||
|                 data.accessToken); | ||||
|         return this.sebInstructionService.registerInstruction( | ||||
|                 examId, | ||||
|                 InstructionType.SEB_PROCTORING, | ||||
|                 attributes, | ||||
|                 connectionToken, | ||||
|                 true); | ||||
|     } | ||||
| 
 | ||||
|     private void checkAccess(final Long institutionId, final Long examId) { | ||||
|         this.authorization.check( | ||||
|                 PrivilegeType.READ, | ||||
|                 EntityType.EXAM, | ||||
|                 institutionId); | ||||
| 
 | ||||
|         this.authorization.checkRead(this.examSessionService | ||||
|                 .getExamDAO() | ||||
|                 .byPK(examId) | ||||
|                 .getOrThrow()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1449,7 +1449,9 @@ sebserver.monitoring.exam.action.list.view=Monitoring | |||
| sebserver.monitoring.exam.action.viewroom=View {0}  ( {1} / {2} ) | ||||
| sebserver.exam.monitoring.action.category.filter=Filter | ||||
| sebserver.exam.overall.action.category.proctoring=Proctoring | ||||
| sebserver.monitoring.exam.action.proctoring.allRoom=View All | ||||
| 
 | ||||
| sebserver.monitoring.exam.proctoring.room.all.name=Exam Room | ||||
| sebserver.monitoring.exam.proctoring.action.close=Close Window | ||||
| sebserver.monitoring.exam.proctoring.action.broadcaston.audio=Start Audio Broadcast | ||||
| sebserver.monitoring.exam.proctoring.action.broadcastoff.audio=End Audio Broadcast | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti