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_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction"; | ||||||
|     public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported"; |     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 EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters"; | ||||||
|     public static final String PROCTORING_PATH_SEGMENT = "/proctoring"; |     public static final String EXAM_ADMINISTRATION_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_INDICATOR_ENDPOINT = "/indicator"; |     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 = |     public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = | ||||||
|             "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; |             "/{" + 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_CONNECTION_ENDPOINT = "/seb-client-connection"; | ||||||
| 
 | 
 | ||||||
|     public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event"; |     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 roomName = urlEncoder.encodeToString(Utils.toByteArray(connectionToken)); | ||||||
|         final String examId = action.getEntityKey().modelId; |         final String examId = action.getEntityKey().modelId; | ||||||
| 
 | 
 | ||||||
|         final SEBProctoringConnectionData proctoringConnectionData = this.pageService.getCurrentUser() |         final ProctoringGUIService proctoringGUIService = this.pageService | ||||||
|                 .getProctoringGUIService() |                 .getCurrentUser() | ||||||
|                 .registerNewSingleProcotringRoom( |                 .getProctoringGUIService(); | ||||||
|                         examId, | 
 | ||||||
|                         roomName, |         if (!proctoringGUIService.hasRoom(roomName)) { | ||||||
|                         connectionData.clientConnection.userSessionId, |             final SEBProctoringConnectionData proctoringConnectionData = proctoringGUIService | ||||||
|                         connectionToken) |                     .registerNewSingleProcotringRoom( | ||||||
|                 .getOrThrow(); |                             examId, | ||||||
|  |                             roomName, | ||||||
|  |                             connectionData.clientConnection.userSessionId, | ||||||
|  |                             connectionToken) | ||||||
|  |                     .onError(error -> log.error( | ||||||
|  |                             "Failed to open single proctoring room for connection {} {}", | ||||||
|  |                             connectionToken, | ||||||
|  |                             error.getMessage())) | ||||||
|  |                     .getOr(null); | ||||||
|  |             ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData); |  | ||||||
|         final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class); |         final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class); | ||||||
|         final String script = String.format( |         final String script = String.format( | ||||||
|                 OPEN_SINGEL_ROOM_SCRIPT, |                 OPEN_SINGEL_ROOM_SCRIPT, | ||||||
|  | @ -389,9 +398,7 @@ public class MonitoringClientConnection implements TemplateComposer { | ||||||
|                 this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), |                 this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), | ||||||
|                 this.remoteProctoringEndpoint); |                 this.remoteProctoringEndpoint); | ||||||
|         javaScriptExecutor.execute(script); |         javaScriptExecutor.execute(script); | ||||||
|         this.pageService.getCurrentUser() |         proctoringGUIService.registerProctoringWindow(roomName); | ||||||
|                 .getProctoringGUIService() |  | ||||||
|                 .registerProctoringWindow(roomName); |  | ||||||
|         return action; |         return action; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -108,6 +108,8 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm"); |             new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm"); | ||||||
|     private static final LocTextKey CONFIRM_DISABLE_SELECTED = |     private static final LocTextKey CONFIRM_DISABLE_SELECTED = | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm"); |             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 ServerPushService serverPushService; | ||||||
|     private final PageService pageService; |     private final PageService pageService; | ||||||
|  | @ -329,6 +331,13 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|                 .getOr(null); |                 .getOr(null); | ||||||
| 
 | 
 | ||||||
|         if (proctoringSettings != null && proctoringSettings.enableProctoring) { |         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<>(); |             final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>(); | ||||||
|             updateRoomActions( |             updateRoomActions( | ||||||
|                     entityKey, |                     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( |     private void updateRoomActions( | ||||||
|             final EntityKey entityKey, |             final EntityKey entityKey, | ||||||
|             final PageContext pageContext, |             final PageContext pageContext, | ||||||
|  | @ -359,7 +403,8 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|         this.pageService.getRestService().getBuilder(GetProcotringRooms.class) |         this.pageService.getRestService().getBuilder(GetProcotringRooms.class) | ||||||
|                 .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) |                 .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) | ||||||
|                 .call() |                 .call() | ||||||
|                 .getOrThrow() |                 .onError(error -> log.error("Failed to update proctoring rooms on GUI {}", error.getMessage())) | ||||||
|  |                 .getOr(Collections.emptyList()) | ||||||
|                 .stream() |                 .stream() | ||||||
|                 .forEach(room -> { |                 .forEach(room -> { | ||||||
|                     if (rooms.containsKey(room.name)) { |                     if (rooms.containsKey(room.name)) { | ||||||
|  |  | ||||||
|  | @ -704,6 +704,11 @@ public enum ActionDefinition { | ||||||
|             ImageIcon.PROCTOR_ROOM, |             ImageIcon.PROCTOR_ROOM, | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.PROCTORING), |             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( |     LOGS_USER_ACTIVITY_LIST( | ||||||
|             new LocTextKey("sebserver.logs.activity.userlogs"), |             new LocTextKey("sebserver.logs.activity.userlogs"), | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ public class GetProctoringSettings extends RestCall<ProctoringSettings> { | ||||||
|                 MediaType.APPLICATION_JSON_UTF8, |                 MediaType.APPLICATION_JSON_UTF8, | ||||||
|                 API.EXAM_ADMINISTRATION_ENDPOINT |                 API.EXAM_ADMINISTRATION_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + 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, |                 MediaType.APPLICATION_JSON_UTF8, | ||||||
|                 API.EXAM_ADMINISTRATION_ENDPOINT |                 API.EXAM_ADMINISTRATION_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + 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; | package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session; | ||||||
| 
 | 
 | ||||||
| import java.util.List; |  | ||||||
| 
 |  | ||||||
| import org.springframework.context.annotation.Lazy; | import org.springframework.context.annotation.Lazy; | ||||||
| import org.springframework.http.HttpMethod; | import org.springframework.http.HttpMethod; | ||||||
| import org.springframework.http.MediaType; | import org.springframework.http.MediaType; | ||||||
|  | @ -26,19 +24,19 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; | ||||||
| @Lazy | @Lazy | ||||||
| @Component | @Component | ||||||
| @GuiProfile | @GuiProfile | ||||||
| public class LeaveRemoteProctoringRoom extends RestCall<List<SEBProctoringConnectionData>> { | public class CreateCollectingAllProctoringRoom extends RestCall<SEBProctoringConnectionData> { | ||||||
| 
 | 
 | ||||||
|     public LeaveRemoteProctoringRoom() { |     public CreateCollectingAllProctoringRoom() { | ||||||
|         super(new TypeKey<>( |         super(new TypeKey<>( | ||||||
|                 CallType.UNDEFINED, |                 CallType.UNDEFINED, | ||||||
|                 EntityType.EXAM_PROCTOR_DATA, |                 EntityType.EXAM_PROCTOR_DATA, | ||||||
|                 new TypeReference<List<SEBProctoringConnectionData>>() { |                 new TypeReference<SEBProctoringConnectionData>() { | ||||||
|                 }), |                 }), | ||||||
|                 HttpMethod.POST, |                 HttpMethod.POST, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_PROCTORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                         + API.PROCTORING_PATH_SEGMENT |                         + API.EXAM_PROCTORING_JON_ALL_COLLECTING_ROOM); | ||||||
|                         + API.PROCTORING_LEAVE_ROOM_PATH_SEGMENT); |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | @ -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, |                 HttpMethod.GET, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_PROCTORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                         + API.PROCTORING_PATH_SEGMENT |                         + API.EXAM_PROCTORING_ROOMS_SEGMENT); | ||||||
|                         + API.PROCTORING_ROOMS_SEGMENT); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -34,9 +34,8 @@ public class GetProctorRoomConnectionData extends RestCall<SEBProctoringConnecti | ||||||
|                 }), |                 }), | ||||||
|                 HttpMethod.GET, |                 HttpMethod.GET, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_PROCTORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.MODEL_ID_VAR_PATH_SEGMENT); | ||||||
|                         + API.PROCTORING_PATH_SEGMENT); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -36,10 +36,9 @@ public class GetProctorRoomConnections extends RestCall<Collection<ClientConnect | ||||||
|                 }), |                 }), | ||||||
|                 HttpMethod.GET, |                 HttpMethod.GET, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_PROCTORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                         + API.PROCTORING_PATH_SEGMENT |                         + API.EXAM_PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT); | ||||||
|                         + API.PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -34,10 +34,9 @@ public class SendJoinRemoteProctoringRoom extends RestCall<SEBProctoringConnecti | ||||||
|                 }), |                 }), | ||||||
|                 HttpMethod.POST, |                 HttpMethod.POST, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_PROCTORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                         + API.PROCTORING_PATH_SEGMENT |                         + API.EXAM_PROCTORING_JOIN_ROOM_PATH_SEGMENT); | ||||||
|                         + API.PROCTORING_JOIN_ROOM_PATH_SEGMENT); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -33,10 +33,9 @@ public class SendProctoringBroadcastOffInstruction extends RestCall<Void> { | ||||||
|                 }), |                 }), | ||||||
|                 HttpMethod.POST, |                 HttpMethod.POST, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_PROCTORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                         + API.PROCTORING_PATH_SEGMENT |                         + API.EXAM_PROCTORING_BROADCAST_OFF_PATH_SEGMENT); | ||||||
|                         + API.PROCTORING_BROADCAST_OFF_PATH_SEGMENT); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -33,10 +33,9 @@ public class SendProctoringBroadcastOnInstruction extends RestCall<Void> { | ||||||
|                 }), |                 }), | ||||||
|                 HttpMethod.POST, |                 HttpMethod.POST, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_PROCTORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                         + API.PROCTORING_PATH_SEGMENT |                         + API.EXAM_PROCTORING_BROADCAST_ON_PATH_SEGMENT); | ||||||
|                         + API.PROCTORING_BROADCAST_ON_PATH_SEGMENT); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -33,10 +33,9 @@ public class SendRejoinExamCollectionRoom extends RestCall<Void> { | ||||||
|                 }), |                 }), | ||||||
|                 HttpMethod.POST, |                 HttpMethod.POST, | ||||||
|                 MediaType.APPLICATION_FORM_URLENCODED, |                 MediaType.APPLICATION_FORM_URLENCODED, | ||||||
|                 API.EXAM_MONITORING_ENDPOINT |                 API.EXAM_PROCTORING_ENDPOINT | ||||||
|                         + API.MODEL_ID_VAR_PATH_SEGMENT |                         + API.MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                         + API.PROCTORING_PATH_SEGMENT |                         + API.EXAM_PROCTORING_REJOIN_COLLECTING_ROOM_PATH_SEGMENT); | ||||||
|                         + API.PROCTORING_REJOIN_EXAM_ROOM_PATH_SEGMENT); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ import java.util.Collection; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  | import java.util.Objects; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import java.util.concurrent.atomic.AtomicInteger; | 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.model.exam.SEBProctoringConnectionData; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | 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.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.SendJoinRemoteProctoringRoom; | ||||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendRejoinExamCollectionRoom; | import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendRejoinExamCollectionRoom; | ||||||
| 
 | 
 | ||||||
|  | @ -84,26 +87,54 @@ public class ProctoringGUIService { | ||||||
|                 new ProctoringWindowData(examId, data)); |                 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( |     public Result<SEBProctoringConnectionData> registerNewSingleProcotringRoom( | ||||||
|             final String examId, |             final String examId, | ||||||
|             final String roomName, |             final String roomName, | ||||||
|             final String subject, |             final String subject, | ||||||
|             final String connectionToken) { |             final String connectionToken) { | ||||||
| 
 | 
 | ||||||
|         return Result.tryCatch(() -> { |         return this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) | ||||||
|             final SEBProctoringConnectionData connection = |                 .withURIVariable(API.PARAM_MODEL_ID, examId) | ||||||
|                     this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) |                 .withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName) | ||||||
|                             .withURIVariable(API.PARAM_MODEL_ID, examId) |                 .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) | ||||||
|                             .withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName) |                 .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken) | ||||||
|                             .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) |                 .call() | ||||||
|                             .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken) |                 .map(connection -> { | ||||||
|                             .call() |                     this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionToken)); | ||||||
|                             .getOrThrow(); |                     this.openWindows.add(roomName); | ||||||
|  |                     return connection; | ||||||
|  |                 }); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|             this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionToken)); |     public Result<SEBProctoringConnectionData> registerAllProcotringRoom( | ||||||
|             this.openWindows.add(roomName); |             final String examId, | ||||||
|             return connection; |             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( |     public Result<SEBProctoringConnectionData> registerNewProcotringRoom( | ||||||
|  | @ -112,22 +143,19 @@ public class ProctoringGUIService { | ||||||
|             final String subject, |             final String subject, | ||||||
|             final Collection<String> connectionTokens) { |             final Collection<String> connectionTokens) { | ||||||
| 
 | 
 | ||||||
|         return Result.tryCatch(() -> { |         return this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) | ||||||
|             final SEBProctoringConnectionData connection = |                 .withURIVariable(API.PARAM_MODEL_ID, examId) | ||||||
|                     this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) |                 .withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName) | ||||||
|                             .withURIVariable(API.PARAM_MODEL_ID, examId) |                 .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) | ||||||
|                             .withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName) |                 .withFormParam( | ||||||
|                             .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) |                         API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||||
|                             .withFormParam( |                         StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR)) | ||||||
|                                     API.EXAM_API_SEB_CONNECTION_TOKEN, |                 .call() | ||||||
|                                     StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR)) |                 .map(connection -> { | ||||||
|                             .call() |                     this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionTokens)); | ||||||
|                             .getOrThrow(); |                     this.openWindows.add(roomName); | ||||||
| 
 |                     return connection; | ||||||
|             this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionTokens)); |                 }); | ||||||
|             this.openWindows.add(roomName); |  | ||||||
|             return connection; |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void addConnectionsToRoom( |     public void addConnectionsToRoom( | ||||||
|  | @ -147,7 +175,9 @@ public class ProctoringGUIService { | ||||||
|                             API.EXAM_API_SEB_CONNECTION_TOKEN, |                             API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||||
|                             StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR)) |                             StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR)) | ||||||
|                     .call() |                     .call() | ||||||
|                     .getOrThrow(); |                     .onError(error -> log.error("Failed to add connection to proctoring room: {} {}", | ||||||
|  |                             room, | ||||||
|  |                             error.getMessage())); | ||||||
|             roomConnectionData.connections.addAll(connectionTokens); |             roomConnectionData.connections.addAll(connectionTokens); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -164,15 +194,24 @@ public class ProctoringGUIService { | ||||||
|         final RoomConnectionData roomConnectionData = this.rooms.remove(name); |         final RoomConnectionData roomConnectionData = this.rooms.remove(name); | ||||||
|         if (roomConnectionData != null) { |         if (roomConnectionData != null) { | ||||||
|             // send instruction to leave this room and join the own exam collection room |             // send instruction to leave this room and join the own exam collection room | ||||||
| 
 |             if (!roomConnectionData.connections.isEmpty()) { | ||||||
|             this.restService.getBuilder(SendRejoinExamCollectionRoom.class) |                 this.restService.getBuilder(SendRejoinExamCollectionRoom.class) | ||||||
|                     .withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId) |                         .withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId) | ||||||
|                     .withFormParam( |                         .withFormParam( | ||||||
|                             API.EXAM_API_SEB_CONNECTION_TOKEN, |                                 API.EXAM_API_SEB_CONNECTION_TOKEN, | ||||||
|                             StringUtils.join(roomConnectionData.connections, Constants.LIST_SEPARATOR_CHAR)) |                                 StringUtils.join(roomConnectionData.connections, Constants.LIST_SEPARATOR_CHAR)) | ||||||
|                     .call() |                         .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; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; | 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.exam.SEBProctoringConnectionData; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | import ch.ethz.seb.sebserver.gbl.util.Result; | ||||||
| 
 | 
 | ||||||
| public interface ExamProctoringService { | public interface ExamProctoringService { | ||||||
|  | @ -20,14 +21,18 @@ public interface ExamProctoringService { | ||||||
|     Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring); |     Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring); | ||||||
| 
 | 
 | ||||||
|     Result<SEBProctoringConnectionData> createProctorPublicRoomConnection( |     Result<SEBProctoringConnectionData> createProctorPublicRoomConnection( | ||||||
|             final ProctoringSettings examProctoring, |             final ProctoringSettings proctoringSettings, | ||||||
|             final String roomName, |             final String roomName, | ||||||
|             final String subject); |             final String subject); | ||||||
| 
 | 
 | ||||||
|     Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( |     Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( | ||||||
|             final ProctoringSettings examProctoring, |             final ProctoringSettings proctoringSettings, | ||||||
|             final String connectionToken); |             final String connectionToken); | ||||||
| 
 | 
 | ||||||
|  |     Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( | ||||||
|  |             final ProctoringSettings proctoringSettings, | ||||||
|  |             final ClientConnection connection); | ||||||
|  | 
 | ||||||
|     Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( |     Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( | ||||||
|             final ProctoringSettings proctoringSettings, |             final ProctoringSettings proctoringSettings, | ||||||
|             final String connectionToken, |             final String connectionToken, | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ import java.security.InvalidKeyException; | ||||||
| import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||||
| import java.util.Base64; | import java.util.Base64; | ||||||
| import java.util.Base64.Encoder; | import java.util.Base64.Encoder; | ||||||
|  | import java.util.Collection; | ||||||
| 
 | 
 | ||||||
| import javax.crypto.Mac; | import javax.crypto.Mac; | ||||||
| import javax.crypto.spec.SecretKeySpec; | 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; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; | 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.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.ClientConnectionData; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
|  | @ -99,15 +101,27 @@ public class ExamJITSIProctoringService implements ExamProctoringService { | ||||||
|             final ProctoringSettings proctoringSettings, |             final ProctoringSettings proctoringSettings, | ||||||
|             final String connectionToken) { |             final String connectionToken) { | ||||||
| 
 | 
 | ||||||
|  |         return this.examSessionService | ||||||
|  |                 .getConnectionData(connectionToken) | ||||||
|  |                 .flatMap(connection -> getClientExamCollectionRoomConnectionData( | ||||||
|  |                         proctoringSettings, | ||||||
|  |                         connection.clientConnection)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData( | ||||||
|  |             final ProctoringSettings proctoringSettings, | ||||||
|  |             final ClientConnection connection) { | ||||||
|  | 
 | ||||||
|         return Result.tryCatch(() -> { |         return Result.tryCatch(() -> { | ||||||
|             final ClientConnectionData clientConnection = this.examSessionService | 
 | ||||||
|                     .getConnectionData(connectionToken) |             final Collection<RemoteProctoringRoom> remoteProctoringRooms = this.examSessionService | ||||||
|                     .getOrThrow(); |  | ||||||
|             final RemoteProctoringRoom room = this.examSessionService |  | ||||||
|                     .getExamSessionCacheService() |                     .getExamSessionCacheService() | ||||||
|                     .getRemoteProctoringRooms(clientConnection.clientConnection.examId) |                     .getRemoteProctoringRooms(connection.examId); | ||||||
|  | 
 | ||||||
|  |             final RemoteProctoringRoom room = remoteProctoringRooms | ||||||
|                     .stream() |                     .stream() | ||||||
|                     .filter(r -> r.id.equals(clientConnection.clientConnection.remoteProctoringRoomId)) |                     .filter(r -> r.id.equals(connection.remoteProctoringRoomId)) | ||||||
|                     .findFirst() |                     .findFirst() | ||||||
|                     .orElseGet(() -> { |                     .orElseGet(() -> { | ||||||
|                         throw new RuntimeException("No exam proctoring room found for clientConnection"); |                         throw new RuntimeException("No exam proctoring room found for clientConnection"); | ||||||
|  | @ -119,10 +133,10 @@ public class ExamJITSIProctoringService implements ExamProctoringService { | ||||||
|                     proctoringSettings.serverURL, |                     proctoringSettings.serverURL, | ||||||
|                     proctoringSettings.appKey, |                     proctoringSettings.appKey, | ||||||
|                     proctoringSettings.getAppSecret(), |                     proctoringSettings.getAppSecret(), | ||||||
|                     clientConnection.clientConnection.userSessionId, |                     connection.userSessionId, | ||||||
|                     "seb-client", |                     "seb-client", | ||||||
|                     room.name, |                     room.name, | ||||||
|                     clientConnection.clientConnection.userSessionId, |                     connection.userSessionId, | ||||||
|                     forExam(proctoringSettings)) |                     forExam(proctoringSettings)) | ||||||
|                             .getOrThrow(); |                             .getOrThrow(); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  | @ -124,7 +124,8 @@ public class ExamProcotringRoomServiceImpl implements ExamProcotringRoomService | ||||||
|                 .build() |                 .build() | ||||||
|                 .execute(); |                 .execute(); | ||||||
| 
 | 
 | ||||||
|         flagUpdated(toUpdate).stream() |         flagUpdated(toUpdate) | ||||||
|  |                 .stream() | ||||||
|                 .forEach(cc -> { |                 .forEach(cc -> { | ||||||
|                     if (ConnectionStatus.ACTIVE.name().equals(cc.getStatus())) { |                     if (ConnectionStatus.ACTIVE.name().equals(cc.getStatus())) { | ||||||
|                         assignToRoom(cc); |                         assignToRoom(cc); | ||||||
|  | @ -156,13 +157,14 @@ public class ExamProcotringRoomServiceImpl implements ExamProcotringRoomService | ||||||
|                 ACTIVE_COLLECTING_ALL_ROOM_ATTRIBUTE_NAME); |                 ACTIVE_COLLECTING_ALL_ROOM_ATTRIBUTE_NAME); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private Result<String> getActiveCollectingAllRoom(final Long examId) { |     private String getActiveCollectingAllRoom(final Long examId) { | ||||||
|         return this.additionalAttributesDAO |         return this.additionalAttributesDAO | ||||||
|                 .getAdditionalAttribute( |                 .getAdditionalAttribute( | ||||||
|                         EntityType.EXAM, |                         EntityType.EXAM, | ||||||
|                         examId, |                         examId, | ||||||
|                         ACTIVE_COLLECTING_ALL_ROOM_ATTRIBUTE_NAME) |                         ACTIVE_COLLECTING_ALL_ROOM_ATTRIBUTE_NAME) | ||||||
|                 .map(attr -> attr.getValue()); |                 .map(attr -> attr.getValue()) | ||||||
|  |                 .getOr(null); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // TODO considering doing bulk update here |     // TODO considering doing bulk update here | ||||||
|  | @ -202,7 +204,13 @@ public class ExamProcotringRoomServiceImpl implements ExamProcotringRoomService | ||||||
|                         proctoringRoom.id, |                         proctoringRoom.id, | ||||||
|                         0)); |                         0)); | ||||||
|                 this.examSessionCacheService.evictRemoteProctoringRooms(cc.getExamId()); |                 this.examSessionCacheService.evictRemoteProctoringRooms(cc.getExamId()); | ||||||
|                 applyProcotringInstruction(cc.getExamId(), cc.getConnectionToken(), proctoringRoom.name); |                 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) { |         } catch (final Exception e) { | ||||||
|             log.error("Failed to process proctoring room update for client connection: {}", cc.getConnectionToken(), e); |             log.error("Failed to process proctoring room update for client connection: {}", cc.getConnectionToken(), e); | ||||||
|  |  | ||||||
|  | @ -21,7 +21,6 @@ import java.util.stream.Collectors; | ||||||
| import org.apache.commons.lang3.BooleanUtils; | import org.apache.commons.lang3.BooleanUtils; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| import org.springframework.cache.Cache; |  | ||||||
| import org.springframework.cache.CacheManager; | import org.springframework.cache.CacheManager; | ||||||
| import org.springframework.context.annotation.Lazy; | import org.springframework.context.annotation.Lazy; | ||||||
| import org.springframework.security.access.AccessDeniedException; | import org.springframework.security.access.AccessDeniedException; | ||||||
|  | @ -314,9 +313,15 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Result<ClientConnectionData> getConnectionData(final String connectionToken) { |     public Result<ClientConnectionData> getConnectionData(final String connectionToken) { | ||||||
|  | 
 | ||||||
|         return Result.tryCatch(() -> { |         return Result.tryCatch(() -> { | ||||||
|             final Cache cache = this.cacheManager.getCache(ExamSessionCacheService.CACHE_NAME_ACTIVE_CLIENT_CONNECTION); |             final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService | ||||||
|             return cache.get(connectionToken, ClientConnectionData.class); |                     .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( |     @RequestMapping( | ||||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT |             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                     + API.PROCTORING_PATH_SEGMENT, |                     + API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT, | ||||||
|             method = RequestMethod.GET, |             method = RequestMethod.GET, | ||||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) |             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||||
|     public ProctoringSettings getExamProctoring( |     public ProctoringSettings getExamProctoring( | ||||||
|  | @ -402,7 +402,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> { | ||||||
| 
 | 
 | ||||||
|     @RequestMapping( |     @RequestMapping( | ||||||
|             path = API.MODEL_ID_VAR_PATH_SEGMENT |             path = API.MODEL_ID_VAR_PATH_SEGMENT | ||||||
|                     + API.PROCTORING_PATH_SEGMENT, |                     + API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT, | ||||||
|             method = RequestMethod.POST, |             method = RequestMethod.POST, | ||||||
|             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) |             produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||||
|     public Exam saveExamProctoring( |     public Exam saveExamProctoring( | ||||||
|  |  | ||||||
|  | @ -9,19 +9,14 @@ | ||||||
| package ch.ethz.seb.sebserver.webservice.weblayer.api; | package ch.ethz.seb.sebserver.webservice.weblayer.api; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.EnumSet; | import java.util.EnumSet; | ||||||
| import java.util.HashMap; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; |  | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.stream.Collectors; |  | ||||||
| 
 | 
 | ||||||
| import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||||
| import javax.validation.Valid; | import javax.validation.Valid; | ||||||
| 
 | 
 | ||||||
| import org.apache.commons.lang3.BooleanUtils; |  | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | 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.Domain; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Page; | 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.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.ClientConnection.ConnectionStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | 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; | ||||||
| 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.model.user.UserRole; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; |  | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; | 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.AuthorizationService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; | 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.authorization.UserService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | 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.ExamSessionService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; | import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService; | import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService; | ||||||
|  | @ -76,27 +62,21 @@ public class ExamMonitoringController { | ||||||
| 
 | 
 | ||||||
|     private final SEBClientConnectionService sebClientConnectionService; |     private final SEBClientConnectionService sebClientConnectionService; | ||||||
|     private final ExamSessionService examSessionService; |     private final ExamSessionService examSessionService; | ||||||
|     private final ExamAdminService examAdminService; |  | ||||||
|     private final SEBInstructionService sebInstructionService; |     private final SEBInstructionService sebInstructionService; | ||||||
|     private final AuthorizationService authorization; |     private final AuthorizationService authorization; | ||||||
|     private final PaginationService paginationService; |     private final PaginationService paginationService; | ||||||
|     private final ExamProcotringRoomService examProcotringRoomService; |  | ||||||
| 
 | 
 | ||||||
|     public ExamMonitoringController( |     public ExamMonitoringController( | ||||||
|             final ExamAdminService examAdminService, |  | ||||||
|             final SEBClientConnectionService sebClientConnectionService, |             final SEBClientConnectionService sebClientConnectionService, | ||||||
|             final SEBInstructionService sebInstructionService, |             final SEBInstructionService sebInstructionService, | ||||||
|             final AuthorizationService authorization, |             final AuthorizationService authorization, | ||||||
|             final PaginationService paginationService, |             final PaginationService paginationService) { | ||||||
|             final ExamProcotringRoomService examProcotringRoomService) { |  | ||||||
| 
 | 
 | ||||||
|         this.examAdminService = examAdminService; |  | ||||||
|         this.sebClientConnectionService = sebClientConnectionService; |         this.sebClientConnectionService = sebClientConnectionService; | ||||||
|         this.examSessionService = sebClientConnectionService.getExamSessionService(); |         this.examSessionService = sebClientConnectionService.getExamSessionService(); | ||||||
|         this.sebInstructionService = sebInstructionService; |         this.sebInstructionService = sebInstructionService; | ||||||
|         this.authorization = authorization; |         this.authorization = authorization; | ||||||
|         this.paginationService = paginationService; |         this.paginationService = paginationService; | ||||||
|         this.examProcotringRoomService = examProcotringRoomService; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** This is called by Spring to initialize the WebDataBinder and is used here to |     /** 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) { |     private boolean hasRunningExamPrivilege(final Long examId, final Long institution) { | ||||||
|         return hasRunningExamPrivilege( |         return hasRunningExamPrivilege( | ||||||
|                 this.examSessionService.getRunningExam(examId).getOr(null), |                 this.examSessionService.getRunningExam(examId).getOr(null), | ||||||
|  | @ -685,53 +284,4 @@ public class ExamMonitoringController { | ||||||
|         return exam.institutionId.equals(institution) && exam.isOwner(userId); |         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.monitoring.exam.action.viewroom=View {0}  ( {1} / {2} ) | ||||||
| sebserver.exam.monitoring.action.category.filter=Filter | sebserver.exam.monitoring.action.category.filter=Filter | ||||||
| sebserver.exam.overall.action.category.proctoring=Proctoring | 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.close=Close Window | ||||||
| sebserver.monitoring.exam.proctoring.action.broadcaston.audio=Start Audio Broadcast | sebserver.monitoring.exam.proctoring.action.broadcaston.audio=Start Audio Broadcast | ||||||
| sebserver.monitoring.exam.proctoring.action.broadcastoff.audio=End Audio Broadcast | sebserver.monitoring.exam.proctoring.action.broadcastoff.audio=End Audio Broadcast | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti