SEBSERV-139 single used town-hall and code-cleanup
This commit is contained in:
		
							parent
							
								
									9d5ed34ec6
								
							
						
					
					
						commit
						8470e3b160
					
				
					 10 changed files with 290 additions and 136 deletions
				
			
		| 
						 | 
				
			
			@ -186,6 +186,7 @@ public final class API {
 | 
			
		|||
    public static final String EXAM_PROCTORING_ACTIVATE_TOWNHALL_ROOM = "activate-towhall-room";
 | 
			
		||||
    public static final String EXAM_PROCTORING_DEACTIVATE_TOWNHALL_ROOM = "deactivate-towhall-room";
 | 
			
		||||
    public static final String EXAM_PROCTORING_TOWNHALL_ROOM_DATA = "towhall-room-data";
 | 
			
		||||
    public static final String EXAM_PROCTORING_TOWNHALL_ROOM_AVAILABLE = "towhall-available";
 | 
			
		||||
 | 
			
		||||
    public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ import java.util.function.BooleanSupplier;
 | 
			
		|||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.lang3.BooleanUtils;
 | 
			
		||||
import org.eclipse.rap.rwt.RWT;
 | 
			
		||||
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
 | 
			
		||||
import org.eclipse.swt.SWT;
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +79,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClient
 | 
			
		|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProcotringRooms;
 | 
			
		||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnectionData;
 | 
			
		||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetTownhallRoom;
 | 
			
		||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.IsTownhallRoomAvailable;
 | 
			
		||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
 | 
			
		||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
 | 
			
		||||
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
 | 
			
		||||
| 
						 | 
				
			
			@ -123,6 +125,7 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
 | 
			
		||||
    private final ServerPushService serverPushService;
 | 
			
		||||
    private final PageService pageService;
 | 
			
		||||
    private final RestService restService;
 | 
			
		||||
    private final ResourceService resourceService;
 | 
			
		||||
    private final AsyncRunner asyncRunner;
 | 
			
		||||
    private final InstructionProcessor instructionProcessor;
 | 
			
		||||
| 
						 | 
				
			
			@ -147,6 +150,7 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
 | 
			
		||||
        this.serverPushService = serverPushService;
 | 
			
		||||
        this.pageService = pageService;
 | 
			
		||||
        this.restService = pageService.getRestService();
 | 
			
		||||
        this.resourceService = pageService.getResourceService();
 | 
			
		||||
        this.asyncRunner = asyncRunner;
 | 
			
		||||
        this.instructionProcessor = instructionProcessor;
 | 
			
		||||
| 
						 | 
				
			
			@ -214,13 +218,14 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
 | 
			
		||||
        this.serverPushService.runServerPush(
 | 
			
		||||
                new ServerPushContext(
 | 
			
		||||
                        content, Utils.truePredicate(),
 | 
			
		||||
                        content,
 | 
			
		||||
                        Utils.truePredicate(),
 | 
			
		||||
                        createServerPushUpdateErrorHandler(this.pageService, pageContext)),
 | 
			
		||||
                this.pollInterval,
 | 
			
		||||
                context -> clientTable.updateValues(),
 | 
			
		||||
                updateTableGUI(clientTable));
 | 
			
		||||
 | 
			
		||||
        final BooleanSupplier privilege = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
 | 
			
		||||
        final BooleanSupplier isExamSupporter = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
 | 
			
		||||
 | 
			
		||||
        actionBuilder
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -242,20 +247,20 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
 | 
			
		||||
                    return copyOfPageAction;
 | 
			
		||||
                })
 | 
			
		||||
                .publishIf(privilege, false)
 | 
			
		||||
                .publishIf(isExamSupporter, false)
 | 
			
		||||
 | 
			
		||||
                .newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL)
 | 
			
		||||
                .withEntityKey(entityKey)
 | 
			
		||||
                .withConfirm(() -> CONFIRM_QUIT_ALL)
 | 
			
		||||
                .withExec(action -> this.quitSEBClients(action, clientTable, true))
 | 
			
		||||
                .noEventPropagation()
 | 
			
		||||
                .publishIf(privilege)
 | 
			
		||||
                .publishIf(isExamSupporter)
 | 
			
		||||
 | 
			
		||||
                .newAction(ActionDefinition.MONITORING_EXAM_SEARCH_CONNECTIONS)
 | 
			
		||||
                .withEntityKey(entityKey)
 | 
			
		||||
                .withExec(this::openSearchPopup)
 | 
			
		||||
                .noEventPropagation()
 | 
			
		||||
                .publishIf(privilege)
 | 
			
		||||
                .publishIf(isExamSupporter)
 | 
			
		||||
 | 
			
		||||
                .newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
 | 
			
		||||
                .withEntityKey(entityKey)
 | 
			
		||||
| 
						 | 
				
			
			@ -265,7 +270,7 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
                        action -> this.quitSEBClients(action, clientTable, false),
 | 
			
		||||
                        EMPTY_ACTIVE_SELECTION_TEXT_KEY)
 | 
			
		||||
                .noEventPropagation()
 | 
			
		||||
                .publishIf(privilege, false)
 | 
			
		||||
                .publishIf(isExamSupporter, false)
 | 
			
		||||
 | 
			
		||||
                .newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION)
 | 
			
		||||
                .withEntityKey(entityKey)
 | 
			
		||||
| 
						 | 
				
			
			@ -275,31 +280,111 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
                        action -> this.disableSEBClients(action, clientTable, false),
 | 
			
		||||
                        EMPTY_SELECTION_TEXT_KEY)
 | 
			
		||||
                .noEventPropagation()
 | 
			
		||||
                .publishIf(privilege, false);
 | 
			
		||||
                .publishIf(isExamSupporter, false);
 | 
			
		||||
 | 
			
		||||
        if (privilege.getAsBoolean()) {
 | 
			
		||||
        if (isExamSupporter.getAsBoolean()) {
 | 
			
		||||
            addFilterActions(actionBuilder, clientTable, isExamSupporter);
 | 
			
		||||
            addProctoringActions(
 | 
			
		||||
                    currentUser.getProctoringGUIService(),
 | 
			
		||||
                    pageContext,
 | 
			
		||||
                    content,
 | 
			
		||||
                    actionBuilder);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
            if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
 | 
			
		||||
                actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
 | 
			
		||||
                        .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
 | 
			
		||||
    private void addProctoringActions(
 | 
			
		||||
            final ProctoringGUIService proctoringGUIService,
 | 
			
		||||
            final PageContext pageContext,
 | 
			
		||||
            final Composite parent,
 | 
			
		||||
            final PageActionBuilder actionBuilder) {
 | 
			
		||||
 | 
			
		||||
        final EntityKey entityKey = pageContext.getEntityKey();
 | 
			
		||||
        final ProctoringSettings proctoringSettings = this.restService
 | 
			
		||||
                .getBuilder(GetProctoringSettings.class)
 | 
			
		||||
                .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
			
		||||
                .call()
 | 
			
		||||
                .getOr(null);
 | 
			
		||||
 | 
			
		||||
        if (proctoringSettings != null && proctoringSettings.enableProctoring) {
 | 
			
		||||
 | 
			
		||||
            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)
 | 
			
		||||
                    .withEntityKey(entityKey)
 | 
			
		||||
                    .withExec(action -> this.toggleTownhallRoom(proctoringGUIService, action))
 | 
			
		||||
                    .noEventPropagation()
 | 
			
		||||
                    .publish();
 | 
			
		||||
 | 
			
		||||
            if (isTownhallRoomActive(entityKey.modelId)) {
 | 
			
		||||
                this.pageService.firePageEvent(
 | 
			
		||||
                        new ActionActivationEvent(
 | 
			
		||||
                                true,
 | 
			
		||||
                                new Tuple<>(
 | 
			
		||||
                                        ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
 | 
			
		||||
                                        ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
 | 
			
		||||
                        pageContext);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>();
 | 
			
		||||
            updateRoomActions(
 | 
			
		||||
                    pageContext,
 | 
			
		||||
                    availableRooms,
 | 
			
		||||
                    actionBuilder,
 | 
			
		||||
                    proctoringSettings,
 | 
			
		||||
                    proctoringGUIService);
 | 
			
		||||
            this.serverPushService.runServerPush(
 | 
			
		||||
                    new ServerPushContext(
 | 
			
		||||
                            parent,
 | 
			
		||||
                            Utils.truePredicate(),
 | 
			
		||||
                            createServerPushUpdateErrorHandler(this.pageService, pageContext)),
 | 
			
		||||
                    this.proctoringRoomUpdateInterval,
 | 
			
		||||
                    context -> updateRoomActions(
 | 
			
		||||
                            pageContext,
 | 
			
		||||
                            availableRooms,
 | 
			
		||||
                            actionBuilder,
 | 
			
		||||
                            proctoringSettings,
 | 
			
		||||
                            proctoringGUIService));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void addFilterActions(
 | 
			
		||||
            final PageActionBuilder actionBuilder,
 | 
			
		||||
            final ClientConnectionTable clientTable,
 | 
			
		||||
            final BooleanSupplier isExamSupporter) {
 | 
			
		||||
 | 
			
		||||
        addClosedFilterAction(actionBuilder, clientTable);
 | 
			
		||||
        addRequestedFilterAction(actionBuilder, clientTable);
 | 
			
		||||
        addDisabledFilterAction(actionBuilder, clientTable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void addDisabledFilterAction(
 | 
			
		||||
            final PageActionBuilder actionBuilder,
 | 
			
		||||
            final ClientConnectionTable clientTable) {
 | 
			
		||||
 | 
			
		||||
        if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
 | 
			
		||||
            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
 | 
			
		||||
                    .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
 | 
			
		||||
                    .noEventPropagation()
 | 
			
		||||
                    .withSwitchAction(
 | 
			
		||||
                                actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
 | 
			
		||||
                                        .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
 | 
			
		||||
                            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
 | 
			
		||||
                                    .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
 | 
			
		||||
                                    .noEventPropagation()
 | 
			
		||||
                                    .create())
 | 
			
		||||
                    .publish();
 | 
			
		||||
        } else {
 | 
			
		||||
                actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
 | 
			
		||||
                        .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
 | 
			
		||||
            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
 | 
			
		||||
                    .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
 | 
			
		||||
                    .noEventPropagation()
 | 
			
		||||
                    .withSwitchAction(
 | 
			
		||||
                                actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
 | 
			
		||||
                                        .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
 | 
			
		||||
                            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
 | 
			
		||||
                                    .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
 | 
			
		||||
                                    .noEventPropagation()
 | 
			
		||||
                                    .create())
 | 
			
		||||
                    .publish();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void addRequestedFilterAction(
 | 
			
		||||
            final PageActionBuilder actionBuilder,
 | 
			
		||||
            final ClientConnectionTable clientTable) {
 | 
			
		||||
 | 
			
		||||
        if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) {
 | 
			
		||||
            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
 | 
			
		||||
| 
						 | 
				
			
			@ -324,85 +409,42 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
                                    .create())
 | 
			
		||||
                    .publish();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
            if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
 | 
			
		||||
                actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
 | 
			
		||||
                        .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
 | 
			
		||||
    private void addClosedFilterAction(
 | 
			
		||||
            final PageActionBuilder actionBuilder,
 | 
			
		||||
            final ClientConnectionTable clientTable) {
 | 
			
		||||
 | 
			
		||||
        if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
 | 
			
		||||
            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
 | 
			
		||||
                    .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
 | 
			
		||||
                    .noEventPropagation()
 | 
			
		||||
                    .withSwitchAction(
 | 
			
		||||
                                actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
 | 
			
		||||
                                        .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
 | 
			
		||||
                            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
 | 
			
		||||
                                    .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
 | 
			
		||||
                                    .noEventPropagation()
 | 
			
		||||
                                    .create())
 | 
			
		||||
                    .publish();
 | 
			
		||||
        } else {
 | 
			
		||||
                actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
 | 
			
		||||
                        .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
 | 
			
		||||
            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
 | 
			
		||||
                    .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
 | 
			
		||||
                    .noEventPropagation()
 | 
			
		||||
                    .withSwitchAction(
 | 
			
		||||
                                actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
 | 
			
		||||
                                        .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
 | 
			
		||||
                            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
 | 
			
		||||
                                    .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
 | 
			
		||||
                                    .noEventPropagation()
 | 
			
		||||
                                    .create())
 | 
			
		||||
                    .publish();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        final ProctoringSettings proctoringSettings = restService
 | 
			
		||||
                .getBuilder(GetProctoringSettings.class)
 | 
			
		||||
                .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
			
		||||
                .call()
 | 
			
		||||
                .getOr(null);
 | 
			
		||||
 | 
			
		||||
        if (proctoringSettings != null && proctoringSettings.enableProctoring) {
 | 
			
		||||
 | 
			
		||||
            actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)
 | 
			
		||||
                    .withEntityKey(entityKey)
 | 
			
		||||
                    .withExec(this::toggleTownhallRoom)
 | 
			
		||||
                    .noEventPropagation()
 | 
			
		||||
                    .publish();
 | 
			
		||||
 | 
			
		||||
            if (isTownhallRoomActive(entityKey.modelId)) {
 | 
			
		||||
                this.pageService.firePageEvent(
 | 
			
		||||
                        new ActionActivationEvent(
 | 
			
		||||
                                true,
 | 
			
		||||
                                new Tuple<>(
 | 
			
		||||
                                        ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
 | 
			
		||||
                                        ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
 | 
			
		||||
                        pageContext);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>();
 | 
			
		||||
            updateRoomActions(
 | 
			
		||||
                    entityKey,
 | 
			
		||||
                    pageContext,
 | 
			
		||||
                    availableRooms,
 | 
			
		||||
                    actionBuilder,
 | 
			
		||||
                    proctoringSettings);
 | 
			
		||||
            this.serverPushService.runServerPush(
 | 
			
		||||
                    new ServerPushContext(
 | 
			
		||||
                            content,
 | 
			
		||||
                            Utils.truePredicate(),
 | 
			
		||||
                            createServerPushUpdateErrorHandler(this.pageService, pageContext)),
 | 
			
		||||
                    this.proctoringRoomUpdateInterval,
 | 
			
		||||
                    context -> updateRoomActions(
 | 
			
		||||
                            entityKey,
 | 
			
		||||
                            pageContext,
 | 
			
		||||
                            availableRooms,
 | 
			
		||||
                            actionBuilder,
 | 
			
		||||
                            proctoringSettings));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isTownhallRoomActive(final String examModelId) {
 | 
			
		||||
        final RemoteProctoringRoom townhall = this.pageService.getRestService()
 | 
			
		||||
                .getBuilder(GetTownhallRoom.class)
 | 
			
		||||
        return !BooleanUtils.toBoolean(this.pageService
 | 
			
		||||
                .getRestService()
 | 
			
		||||
                .getBuilder(IsTownhallRoomAvailable.class)
 | 
			
		||||
                .withURIVariable(API.PARAM_MODEL_ID, examModelId)
 | 
			
		||||
                .call()
 | 
			
		||||
                .getOr(null);
 | 
			
		||||
 | 
			
		||||
        return townhall != null && townhall.id != null;
 | 
			
		||||
                .getOr(Constants.FALSE_STRING));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private PageAction openSearchPopup(final PageAction action) {
 | 
			
		||||
| 
						 | 
				
			
			@ -410,9 +452,12 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
        return action;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private PageAction toggleTownhallRoom(final PageAction action) {
 | 
			
		||||
    private PageAction toggleTownhallRoom(
 | 
			
		||||
            final ProctoringGUIService proctoringGUIService,
 | 
			
		||||
            final PageAction action) {
 | 
			
		||||
 | 
			
		||||
        if (isTownhallRoomActive(action.getEntityKey().modelId)) {
 | 
			
		||||
            closeTownhallRoom(action);
 | 
			
		||||
            closeTownhallRoom(proctoringGUIService, action);
 | 
			
		||||
            this.pageService.firePageEvent(
 | 
			
		||||
                    new ActionActivationEvent(
 | 
			
		||||
                            true,
 | 
			
		||||
| 
						 | 
				
			
			@ -422,7 +467,7 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
                    action.pageContext());
 | 
			
		||||
            return action;
 | 
			
		||||
        } else {
 | 
			
		||||
            openTownhallRoom(action);
 | 
			
		||||
            openTownhallRoom(proctoringGUIService, action);
 | 
			
		||||
            this.pageService.firePageEvent(
 | 
			
		||||
                    new ActionActivationEvent(
 | 
			
		||||
                            true,
 | 
			
		||||
| 
						 | 
				
			
			@ -434,14 +479,12 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private PageAction openTownhallRoom(final PageAction action) {
 | 
			
		||||
    private PageAction openTownhallRoom(
 | 
			
		||||
            final ProctoringGUIService proctoringGUIService,
 | 
			
		||||
            final PageAction action) {
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            final EntityKey examId = action.getEntityKey();
 | 
			
		||||
 | 
			
		||||
            final ProctoringGUIService proctoringGUIService = this.pageService
 | 
			
		||||
                    .getCurrentUser()
 | 
			
		||||
                    .getProctoringGUIService();
 | 
			
		||||
 | 
			
		||||
            String activeAllRoomName = proctoringGUIService.getTownhallRoom(examId.modelId);
 | 
			
		||||
 | 
			
		||||
            if (activeAllRoomName == null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -476,7 +519,10 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
        return action;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private PageAction closeTownhallRoom(final PageAction action) {
 | 
			
		||||
    private PageAction closeTownhallRoom(
 | 
			
		||||
            final ProctoringGUIService proctoringGUIService,
 | 
			
		||||
            final PageAction action) {
 | 
			
		||||
 | 
			
		||||
        final String examId = action.getEntityKey().modelId;
 | 
			
		||||
        final RemoteProctoringRoom townhall = this.pageService.getRestService()
 | 
			
		||||
                .getBuilder(GetTownhallRoom.class)
 | 
			
		||||
| 
						 | 
				
			
			@ -492,19 +538,22 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            final ProctoringGUIService proctoringGUIService = this.pageService
 | 
			
		||||
                    .getCurrentUser()
 | 
			
		||||
                    .getProctoringGUIService();
 | 
			
		||||
 | 
			
		||||
            proctoringGUIService.closeRoom(townhall.name);
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            log.error("Failed to close procotring townhall room for exam: {}", examId);
 | 
			
		||||
            log.error("Failed to close proctoring townhall room for exam: {}", examId);
 | 
			
		||||
        }
 | 
			
		||||
        return action;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateTownhallButton(final EntityKey entityKey, final PageContext pageContext) {
 | 
			
		||||
    private void updateTownhallButton(
 | 
			
		||||
            final ProctoringGUIService proctoringGUIService,
 | 
			
		||||
            final PageContext pageContext) {
 | 
			
		||||
        final EntityKey entityKey = pageContext.getEntityKey();
 | 
			
		||||
 | 
			
		||||
        if (isTownhallRoomActive(entityKey.modelId)) {
 | 
			
		||||
            final boolean townhallRoomFromThisUser = proctoringGUIService
 | 
			
		||||
                    .getTownhallRoom(entityKey.modelId) != null;
 | 
			
		||||
            if (townhallRoomFromThisUser) {
 | 
			
		||||
                this.pageService.firePageEvent(
 | 
			
		||||
                        new ActionActivationEvent(
 | 
			
		||||
                                true,
 | 
			
		||||
| 
						 | 
				
			
			@ -512,25 +561,36 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
                                        ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
 | 
			
		||||
                                        ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
 | 
			
		||||
                        pageContext);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.pageService.firePageEvent(
 | 
			
		||||
                        new ActionActivationEvent(
 | 
			
		||||
                                false,
 | 
			
		||||
                                ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
 | 
			
		||||
                                ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
 | 
			
		||||
                        pageContext);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.pageService.firePageEvent(
 | 
			
		||||
                    new ActionActivationEvent(
 | 
			
		||||
                            true,
 | 
			
		||||
                            new Tuple<>(
 | 
			
		||||
                                    ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
 | 
			
		||||
                                    ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)),
 | 
			
		||||
                                    ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM),
 | 
			
		||||
                            ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
 | 
			
		||||
                            ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
 | 
			
		||||
                    pageContext);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateRoomActions(
 | 
			
		||||
            final EntityKey entityKey,
 | 
			
		||||
            final PageContext pageContext,
 | 
			
		||||
            final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms,
 | 
			
		||||
            final PageActionBuilder actionBuilder,
 | 
			
		||||
            final ProctoringSettings proctoringSettings) {
 | 
			
		||||
            final ProctoringSettings proctoringSettings,
 | 
			
		||||
            final ProctoringGUIService proctoringGUIService) {
 | 
			
		||||
 | 
			
		||||
        updateTownhallButton(entityKey, pageContext);
 | 
			
		||||
        final EntityKey entityKey = pageContext.getEntityKey();
 | 
			
		||||
        updateTownhallButton(proctoringGUIService, pageContext);
 | 
			
		||||
        final I18nSupport i18nSupport = this.pageService.getI18nSupport();
 | 
			
		||||
        this.pageService.getRestService().getBuilder(GetProcotringRooms.class)
 | 
			
		||||
                .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
			
		||||
| 
						 | 
				
			
			@ -571,7 +631,7 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
                        this.pageService.publishAction(
 | 
			
		||||
                                action,
 | 
			
		||||
                                _treeItem -> rooms.put(room.name, new Pair<>(room, _treeItem)));
 | 
			
		||||
                        addRoomConnectionsPopupListener(entityKey, pageContext, rooms);
 | 
			
		||||
                        addRoomConnectionsPopupListener(pageContext, rooms);
 | 
			
		||||
                        processProctorRoomActionActivation(rooms.get(room.name).b, room, pageContext);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			@ -596,11 +656,11 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    private void addRoomConnectionsPopupListener(
 | 
			
		||||
            final EntityKey entityKey,
 | 
			
		||||
            final PageContext pageContext,
 | 
			
		||||
            final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms) {
 | 
			
		||||
 | 
			
		||||
        if (!rooms.isEmpty()) {
 | 
			
		||||
            final EntityKey entityKey = pageContext.getEntityKey();
 | 
			
		||||
            final TreeItem treeItem = rooms.values().iterator().next().b;
 | 
			
		||||
            final Tree tree = treeItem.getParent();
 | 
			
		||||
            if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,17 +80,18 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
 | 
			
		|||
    public void compose(final PageContext pageContext) {
 | 
			
		||||
 | 
			
		||||
        final ProctoringWindowData proctoringWindowData = ProctoringGUIService.getCurrentProctoringWindowData();
 | 
			
		||||
 | 
			
		||||
        final Composite parent = pageContext.getParent();
 | 
			
		||||
 | 
			
		||||
        final Composite content = new Composite(parent, SWT.NONE | SWT.NO_SCROLL);
 | 
			
		||||
        final GridLayout gridLayout = new GridLayout();
 | 
			
		||||
        final ProctoringGUIService proctoringGUIService = this.pageService
 | 
			
		||||
                .getCurrentUser()
 | 
			
		||||
                .getProctoringGUIService();
 | 
			
		||||
 | 
			
		||||
        content.setLayout(gridLayout);
 | 
			
		||||
        final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
 | 
			
		||||
        content.setLayoutData(headerCell);
 | 
			
		||||
 | 
			
		||||
        parent.addListener(SWT.Dispose, event -> closeRoom(proctoringWindowData));
 | 
			
		||||
        parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
 | 
			
		||||
 | 
			
		||||
        final String url = this.guiServiceInfo
 | 
			
		||||
                .getExternalServerURIBuilder()
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +121,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
 | 
			
		|||
 | 
			
		||||
        final Button closeAction = widgetFactory.buttonLocalized(footer, CLOSE_WINDOW_TEXT_KEY);
 | 
			
		||||
        closeAction.setLayoutData(new RowData(150, 30));
 | 
			
		||||
        closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringWindowData));
 | 
			
		||||
        closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringGUIService, proctoringWindowData));
 | 
			
		||||
 | 
			
		||||
        final BroadcastActionState broadcastActionState = new BroadcastActionState();
 | 
			
		||||
        final String connectionTokens = getConnectionTokens(proctoringWindowData);
 | 
			
		||||
| 
						 | 
				
			
			@ -256,11 +257,15 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
 | 
			
		|||
        boolean chat = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void closeRoom(final ProctoringWindowData proctoringWindowData) {
 | 
			
		||||
        this.pageService
 | 
			
		||||
                .getCurrentUser()
 | 
			
		||||
                .getProctoringGUIService()
 | 
			
		||||
                .closeRoom(proctoringWindowData.connectionData.roomName);
 | 
			
		||||
    private void closeRoom(
 | 
			
		||||
            final ProctoringGUIService proctoringGUIService,
 | 
			
		||||
            final ProctoringWindowData proctoringWindowData) {
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            proctoringGUIService.closeRoom(proctoringWindowData.connectionData.roomName);
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            log.error("Failed to close proctoring window properly: ", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
 | 
			
		||||
 *
 | 
			
		||||
 * This Source Code Form is subject to the terms of the Mozilla Public
 | 
			
		||||
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
			
		||||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session;
 | 
			
		||||
 | 
			
		||||
import org.springframework.context.annotation.Lazy;
 | 
			
		||||
import org.springframework.http.HttpMethod;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.core.type.TypeReference;
 | 
			
		||||
 | 
			
		||||
import ch.ethz.seb.sebserver.gbl.api.API;
 | 
			
		||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
			
		||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
			
		||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
 | 
			
		||||
 | 
			
		||||
@Lazy
 | 
			
		||||
@Component
 | 
			
		||||
@GuiProfile
 | 
			
		||||
public class IsTownhallRoomAvailable extends RestCall<String> {
 | 
			
		||||
 | 
			
		||||
    public IsTownhallRoomAvailable() {
 | 
			
		||||
        super(new TypeKey<>(
 | 
			
		||||
                CallType.GET_SINGLE,
 | 
			
		||||
                EntityType.REMOTE_PROCTORING_ROOM,
 | 
			
		||||
                new TypeReference<String>() {
 | 
			
		||||
                }),
 | 
			
		||||
                HttpMethod.GET,
 | 
			
		||||
                MediaType.APPLICATION_FORM_URLENCODED,
 | 
			
		||||
                API.EXAM_PROCTORING_ENDPOINT
 | 
			
		||||
                        + API.MODEL_ID_VAR_PATH_SEGMENT
 | 
			
		||||
                        + API.EXAM_PROCTORING_TOWNHALL_ROOM_AVAILABLE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -195,7 +195,7 @@ public class ProctoringGUIService {
 | 
			
		|||
        closeWindow(name);
 | 
			
		||||
        final RoomConnectionData roomConnectionData = this.rooms.remove(name);
 | 
			
		||||
        if (roomConnectionData != null) {
 | 
			
		||||
            // send reset of broadcast attributes to all in the room
 | 
			
		||||
            // Send reset of broadcast attributes to all in the room
 | 
			
		||||
            this.restService.getBuilder(SendProctoringBroadcastAttributes.class)
 | 
			
		||||
                    .withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId)
 | 
			
		||||
                    .withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomConnectionData.roomName)
 | 
			
		||||
| 
						 | 
				
			
			@ -209,7 +209,7 @@ public class ProctoringGUIService {
 | 
			
		|||
                            "Failed to send reset broadcast attribute instruction call for room: {}, cause: {}",
 | 
			
		||||
                            roomConnectionData.roomName,
 | 
			
		||||
                            error.getMessage()));
 | 
			
		||||
            // send instruction to leave this room and join the own exam collecting room
 | 
			
		||||
            // Send instruction to leave this room and join the own exam collecting room
 | 
			
		||||
            if (!roomConnectionData.connections.isEmpty()) {
 | 
			
		||||
                this.restService.getBuilder(SendRejoinExamCollectionRoom.class)
 | 
			
		||||
                        .withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId)
 | 
			
		||||
| 
						 | 
				
			
			@ -221,6 +221,7 @@ public class ProctoringGUIService {
 | 
			
		|||
                                name,
 | 
			
		||||
                                error.getMessage()));
 | 
			
		||||
            } else {
 | 
			
		||||
                // Close town-hall room
 | 
			
		||||
                this.restService.getBuilder(DisposeTownhallRoom.class)
 | 
			
		||||
                        .withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId)
 | 
			
		||||
                        .call()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,8 @@ public interface RemoteProctoringRoomDAO {
 | 
			
		|||
 | 
			
		||||
    Result<String> getRoomName(Long roomId);
 | 
			
		||||
 | 
			
		||||
    boolean isTownhallRoomActive(Long examId);
 | 
			
		||||
 | 
			
		||||
    Result<RemoteProctoringRoom> getTownhallRoom(Long examId);
 | 
			
		||||
 | 
			
		||||
    Result<RemoteProctoringRoom> createTownhallRoom(Long examId, String subject);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,8 @@ import java.util.stream.Collectors;
 | 
			
		|||
 | 
			
		||||
import org.apache.commons.lang3.BooleanUtils;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.context.annotation.Lazy;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +41,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
 | 
			
		|||
@WebServiceProfile
 | 
			
		||||
public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
 | 
			
		||||
 | 
			
		||||
    private static final Logger log = LoggerFactory.getLogger(RemoteProctoringRoomDAOImpl.class);
 | 
			
		||||
 | 
			
		||||
    private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper;
 | 
			
		||||
 | 
			
		||||
    protected RemoteProctoringRoomDAOImpl(
 | 
			
		||||
| 
						 | 
				
			
			@ -94,14 +98,9 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
 | 
			
		|||
    @Transactional
 | 
			
		||||
    public Result<RemoteProctoringRoom> createTownhallRoom(final Long examId, final String subject) {
 | 
			
		||||
        return Result.tryCatch(() -> {
 | 
			
		||||
            // check first if town-hall room is not already active
 | 
			
		||||
            final long active = this.remoteProctoringRoomRecordMapper.countByExample()
 | 
			
		||||
                    .where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
 | 
			
		||||
                    .and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isNotEqualTo(0))
 | 
			
		||||
                    .build()
 | 
			
		||||
                    .execute();
 | 
			
		||||
 | 
			
		||||
            if (active > 0) {
 | 
			
		||||
            // Check first if town-hall room is not already active
 | 
			
		||||
            if (isTownhallRoomActive(examId)) {
 | 
			
		||||
                throw new IllegalStateException("Townhall, for exam: " + examId + " already exists");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -122,6 +121,24 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
 | 
			
		|||
                .onError(TransactionHandler::rollback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Transactional(readOnly = true)
 | 
			
		||||
    public boolean isTownhallRoomActive(final Long examId) {
 | 
			
		||||
        try {
 | 
			
		||||
            final long active = this.remoteProctoringRoomRecordMapper.countByExample()
 | 
			
		||||
                    .where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
 | 
			
		||||
                    .and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isNotEqualTo(0))
 | 
			
		||||
                    .build()
 | 
			
		||||
                    .execute();
 | 
			
		||||
            return (active > 0);
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            log.error(
 | 
			
		||||
                    "Failed to verify town-hall room activity for exam: {}. Mark it as active to avoid double openings",
 | 
			
		||||
                    examId, e);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Transactional
 | 
			
		||||
    public Result<RemoteProctoringRoom> saveRoom(final Long examId, final RemoteProctoringRoom room) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,6 +44,12 @@ public interface ExamProctoringRoomService {
 | 
			
		|||
     * name of an exam. */
 | 
			
		||||
    void updateProctoringCollectingRooms();
 | 
			
		||||
 | 
			
		||||
    /** Indicates whether the town-hall room for a specified exam is active or not.
 | 
			
		||||
     *
 | 
			
		||||
     * @param examId the exam identifier
 | 
			
		||||
     * @return true if the town-hall room for specified exam is active, false if not. */
 | 
			
		||||
    boolean isTownhallRoomActive(Long examId);
 | 
			
		||||
 | 
			
		||||
    /** This creates a town-hall room for a specific exam. The exam must be active and running
 | 
			
		||||
     * and there must be no other town-hall room already be active. An unique room name will be
 | 
			
		||||
     * created and returned.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,6 +95,11 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTownhallRoomActive(final Long examId) {
 | 
			
		||||
        return this.remoteProctoringRoomDAO.isTownhallRoomActive(examId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Result<RemoteProctoringRoom> createTownhallRoom(final Long examId, final String subject) {
 | 
			
		||||
        if (!this.examSessionService.isExamRunning(examId)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -304,6 +304,22 @@ public class ExamProctoringController {
 | 
			
		|||
                Utils.toByteArray(connectionToken));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @RequestMapping(
 | 
			
		||||
            path = API.MODEL_ID_VAR_PATH_SEGMENT
 | 
			
		||||
                    + API.EXAM_PROCTORING_TOWNHALL_ROOM_AVAILABLE,
 | 
			
		||||
            method = RequestMethod.GET,
 | 
			
		||||
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 | 
			
		||||
    public String isTownhallRoomAvialbale(
 | 
			
		||||
            @RequestParam(
 | 
			
		||||
                    name = API.PARAM_INSTITUTION_ID,
 | 
			
		||||
                    required = true,
 | 
			
		||||
                    defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
 | 
			
		||||
            @PathVariable(name = API.PARAM_MODEL_ID) final Long examId) {
 | 
			
		||||
 | 
			
		||||
        checkExamReadAccess(institutionId);
 | 
			
		||||
        return String.valueOf(!this.examProcotringRoomService.isTownhallRoomActive(examId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @RequestMapping(
 | 
			
		||||
            path = API.MODEL_ID_VAR_PATH_SEGMENT
 | 
			
		||||
                    + API.EXAM_PROCTORING_TOWNHALL_ROOM_DATA,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue