From 191f8432de87b6f6334161c259670bfd7dd74294 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 13 Apr 2022 14:21:27 +0200 Subject: [PATCH] SEBSERV-287 back-end implementation --- .../sebserver/gui/content/exam/ExamForm.java | 14 +- ... ExamTemplateProctoringSettingsPopup.java} | 30 +- .../content/exam/ProctoringSettingsPopup.java | 343 ++++++++++++++++++ .../MonitoringClientConnection.java | 4 +- .../monitoring/MonitoringRunningExam.java | 4 +- .../page/impl/JitsiMeetProctoringView.java | 4 +- .../service/page/impl/ZoomProctoringView.java | 4 +- ...gs.java => GetExamProctoringSettings.java} | 4 +- .../GetExamTemplateProctoringSettings.java | 42 +++ ...s.java => SaveExamProctoringSettings.java} | 8 +- .../SaveExamTemplateProctoringSettings.java | 42 +++ .../dao/AdditionalAttributesDAO.java | 13 + .../servicelayer/exam/ExamAdminService.java | 12 +- .../exam/ProctoringAdminService.java | 56 +++ .../exam/impl/ExamAdminServiceImpl.java | 206 +---------- .../exam/impl/ExamTemplateServiceImpl.java | 6 +- .../exam/impl/ProctoringAdminServiceImpl.java | 270 ++++++++++++++ .../ExamProctoringRoomServiceImpl.java | 27 +- .../api/ExamAdministrationController.java | 5 - .../weblayer/api/ExamTemplateController.java | 66 +++- .../integration/UseCasesIntegrationTest.java | 18 +- .../admin/ExamProctoringRoomServiceTest.java | 2 +- 22 files changed, 907 insertions(+), 273 deletions(-) rename src/main/java/ch/ethz/seb/sebserver/gui/content/exam/{ExamProctoringSettings.java => ExamTemplateProctoringSettingsPopup.java} (91%) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java rename src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/{GetProctoringSettings.java => GetExamProctoringSettings.java} (89%) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamTemplateProctoringSettings.java rename src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/{SaveProctoringSettings.java => SaveExamProctoringSettings.java} (80%) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExamTemplateProctoringSettings.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ProctoringAdminService.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java index 78928680..742e150e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java @@ -65,8 +65,8 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamCon import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRestriction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetDefaultExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData; @@ -143,7 +143,7 @@ public class ExamForm implements TemplateComposer { private final PageService pageService; private final ResourceService resourceService; private final ExamSEBRestrictionSettings examSEBRestrictionSettings; - private final ExamProctoringSettings examProctoringSettings; + private final ProctoringSettingsPopup proctoringSettingsPopup; private final WidgetFactory widgetFactory; private final RestService restService; private final ExamDeletePopup examDeletePopup; @@ -154,7 +154,7 @@ public class ExamForm implements TemplateComposer { protected ExamForm( final PageService pageService, final ExamSEBRestrictionSettings examSEBRestrictionSettings, - final ExamProctoringSettings examProctoringSettings, + final ProctoringSettingsPopup proctoringSettingsPopup, final ExamToConfigBindingForm examToConfigBindingForm, final DownloadService downloadService, final ExamDeletePopup examDeletePopup, @@ -165,7 +165,7 @@ public class ExamForm implements TemplateComposer { this.pageService = pageService; this.resourceService = pageService.getResourceService(); this.examSEBRestrictionSettings = examSEBRestrictionSettings; - this.examProctoringSettings = examProctoringSettings; + this.proctoringSettingsPopup = proctoringSettingsPopup; this.widgetFactory = pageService.getWidgetFactory(); this.restService = this.resourceService.getRestService(); this.examDeletePopup = examDeletePopup; @@ -390,7 +390,7 @@ public class ExamForm implements TemplateComposer { } final boolean proctoringEnabled = importFromQuizData ? false : this.restService - .getBuilder(GetProctoringSettings.class) + .getBuilder(GetExamProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .call() .map(ProctoringServiceSettings::getEnableProctoring) @@ -455,13 +455,13 @@ public class ExamForm implements TemplateComposer { .newAction(ActionDefinition.EXAM_PROCTORING_ON) .withEntityKey(entityKey) - .withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant && editable)) + .withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable)) .noEventPropagation() .publishIf(() -> proctoringEnabled && readonly) .newAction(ActionDefinition.EXAM_PROCTORING_OFF) .withEntityKey(entityKey) - .withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant && editable)) + .withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable)) .noEventPropagation() .publishIf(() -> !proctoringEnabled && readonly); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateProctoringSettingsPopup.java similarity index 91% rename from src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamProctoringSettings.java rename to src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateProctoringSettingsPopup.java index 2eef3c2b..84bf9fa2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateProctoringSettingsPopup.java @@ -44,15 +44,15 @@ import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplateProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplateProctoringSettings; @Lazy @Component @GuiProfile -public class ExamProctoringSettings { +public class ExamTemplateProctoringSettingsPopup { - private static final Logger log = LoggerFactory.getLogger(ExamProctoringSettings.class); + private static final Logger log = LoggerFactory.getLogger(ExamTemplateProctoringSettingsPopup.class); private final static LocTextKey SEB_PROCTORING_FORM_TITLE = new LocTextKey("sebserver.exam.proctoring.form.title"); @@ -178,7 +178,7 @@ public class ExamProctoringSettings { final boolean saveOk = !pageService .getRestService() - .getBuilder(SaveProctoringSettings.class) + .getBuilder(SaveExamTemplateProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .withBody(examProctoring) .call() @@ -227,7 +227,7 @@ public class ExamProctoringSettings { .createPopupScrollComposite(parent); final ProctoringServiceSettings proctoringSettings = restService - .getBuilder(GetProctoringSettings.class) + .getBuilder(GetExamTemplateProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .call() .getOrThrow(); @@ -329,24 +329,6 @@ public class ExamProctoringSettings { return () -> formHandle; } - -// TODO -// private void procServiceSelection(final Form form) { -// final ProctoringServerType proctoringServerType = ProctoringServerType -// .valueOf(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE)); -// switch (proctoringServerType) { -// case ZOOM: { -// form.setFieldVisible(true, ProctoringServiceSettings.ATTR_SDK_KEY); -// form.setFieldVisible(true, ProctoringServiceSettings.ATTR_SDK_SECRET); -// break; -// } -// default: { -// form.setFieldVisible(false, ProctoringServiceSettings.ATTR_SDK_KEY); -// form.setFieldVisible(false, ProctoringServiceSettings.ATTR_SDK_SECRET); -// } -// } -// } - } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java new file mode 100644 index 00000000..570540fd --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java @@ -0,0 +1,343 @@ +/* + * 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.content.exam; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.swt.widgets.Composite; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.form.Form; +import ch.ethz.seb.sebserver.gui.form.FormBuilder; +import ch.ethz.seb.sebserver.gui.form.FormHandle; +import ch.ethz.seb.sebserver.gui.service.ResourceService; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageService; +import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; +import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog; +import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplateProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplateProctoringSettings; + +@Lazy +@Component +@GuiProfile +public class ProctoringSettingsPopup { + + private static final Logger log = LoggerFactory.getLogger(ProctoringSettingsPopup.class); + + private final static LocTextKey SEB_PROCTORING_FORM_TITLE = + new LocTextKey("sebserver.exam.proctoring.form.title"); + private final static LocTextKey SEB_PROCTORING_FORM_INFO = + new LocTextKey("sebserver.exam.proctoring.form.info"); + private final static LocTextKey SEB_PROCTORING_FORM_INFO_TITLE = + new LocTextKey("sebserver.exam.proctoring.form.info.title"); + private final static LocTextKey SEB_PROCTORING_FORM_ENABLE = + new LocTextKey("sebserver.exam.proctoring.form.enabled"); + private final static LocTextKey SEB_PROCTORING_FORM_TYPE = + new LocTextKey("sebserver.exam.proctoring.form.type"); + private final static LocTextKey SEB_PROCTORING_FORM_URL = + new LocTextKey("sebserver.exam.proctoring.form.url"); + private final static LocTextKey SEB_PROCTORING_FORM_ROOM_SIZE = + new LocTextKey("sebserver.exam.proctoring.form.collectingRoomSize"); + private final static LocTextKey SEB_PROCTORING_FORM_APPKEY = + new LocTextKey("sebserver.exam.proctoring.form.appkey"); + private final static LocTextKey SEB_PROCTORING_FORM_SECRET = + new LocTextKey("sebserver.exam.proctoring.form.secret"); + private final static LocTextKey SEB_PROCTORING_FORM_SDKKEY = + new LocTextKey("sebserver.exam.proctoring.form.sdkkey"); + private final static LocTextKey SEB_PROCTORING_FORM_SDKSECRET = + new LocTextKey("sebserver.exam.proctoring.form.sdksecret"); + private final static LocTextKey SEB_PROCTORING_FORM_USE_ZOOM_APP_CLIENT = + new LocTextKey("sebserver.exam.proctoring.form.useZoomAppClient"); + + private final static LocTextKey SEB_PROCTORING_FORM_FEATURES = + new LocTextKey("sebserver.exam.proctoring.form.features"); + + Function settingsFunction(final PageService pageService, final boolean modifyGrant) { + + return action -> { + + final PageContext pageContext = action.pageContext() + .withAttribute( + PageContext.AttributeKeys.FORCE_READ_ONLY, + (modifyGrant) ? Constants.FALSE_STRING : Constants.TRUE_STRING); + final ModalInputDialog> dialog = + new ModalInputDialog>( + action.pageContext().getParent().getShell(), + pageService.getWidgetFactory()) + .setDialogWidth(740) + .setDialogHeight(400); + + final SEBProctoringPropertiesForm bindFormContext = new SEBProctoringPropertiesForm( + pageService, + pageContext); + + final Predicate> doBind = formHandle -> doSaveSettings( + pageService, + pageContext, + formHandle); + + if (modifyGrant) { + dialog.open( + SEB_PROCTORING_FORM_TITLE, + doBind, + Utils.EMPTY_EXECUTION, + bindFormContext); + } else { + dialog.open( + SEB_PROCTORING_FORM_TITLE, + pageContext, + pc -> bindFormContext.compose(pc.getParent())); + } + + return action; + }; + } + + private boolean doSaveSettings( + final PageService pageService, + final PageContext pageContext, + final FormHandle formHandle) { + + final boolean isReadonly = BooleanUtils.toBoolean( + pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY)); + if (isReadonly) { + return true; + } + + final EntityKey entityKey = pageContext.getEntityKey(); + ProctoringServiceSettings examProctoring = null; + try { + final Form form = formHandle.getForm(); + form.clearErrors(); + + final boolean enabled = BooleanUtils.toBoolean( + form.getFieldValue(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING)); + final ProctoringServerType serverType = ProctoringServerType + .valueOf(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE)); + + final String features = form.getFieldValue(ProctoringServiceSettings.ATTR_ENABLED_FEATURES); + final EnumSet featureFlags = (StringUtils.isNotBlank(features)) + ? EnumSet.copyOf(Arrays.asList(StringUtils.split(features, Constants.LIST_SEPARATOR)) + .stream() + .map(str -> ProctoringFeature.valueOf(str)) + .collect(Collectors.toSet())) + : EnumSet.noneOf(ProctoringFeature.class); + + examProctoring = new ProctoringServiceSettings( + Long.parseLong(entityKey.modelId), + enabled, + serverType, + form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_URL), + Integer.parseInt(form.getFieldValue(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)), + featureFlags, + false, + form.getFieldValue(ProctoringServiceSettings.ATTR_APP_KEY), + form.getFieldValue(ProctoringServiceSettings.ATTR_APP_SECRET), + form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_KEY), + form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_SECRET), + BooleanUtils.toBoolean(form.getFieldValue( + ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM))); + + } catch (final Exception e) { + log.error("Unexpected error while trying to get settings from form: ", e); + } + + if (examProctoring == null) { + return false; + } + + final boolean saveOk = !pageService + .getRestService() + .getBuilder( + entityKey.entityType == EntityType.EXAM + ? SaveExamProctoringSettings.class + : SaveExamTemplateProctoringSettings.class) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .withBody(examProctoring) + .call() + .onError(formHandle::handleError) + .hasError(); + + if (saveOk) { + final PageAction action = pageService.pageActionBuilder(pageContext) + .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST) + .create(); + + pageService.firePageEvent( + new ActionEvent(action), + action.pageContext()); + return true; + } + + return false; + } + + private final class SEBProctoringPropertiesForm + implements ModalInputDialogComposer> { + + private final PageService pageService; + private final PageContext pageContext; + + protected SEBProctoringPropertiesForm( + final PageService pageService, + final PageContext pageContext) { + + this.pageService = pageService; + this.pageContext = pageContext; + + } + + @Override + public Supplier> compose(final Composite parent) { + final RestService restService = this.pageService.getRestService(); + final ResourceService resourceService = this.pageService.getResourceService(); + final EntityKey entityKey = this.pageContext.getEntityKey(); + final boolean isReadonly = BooleanUtils.toBoolean( + this.pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY)); + + final Composite content = this.pageService + .getWidgetFactory() + .createPopupScrollComposite(parent); + + final ProctoringServiceSettings proctoringSettings = restService + .getBuilder( + entityKey.entityType == EntityType.EXAM + ? GetExamProctoringSettings.class + : GetExamTemplateProctoringSettings.class) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .call() + .getOrThrow(); + + final PageContext formContext = this.pageContext + .copyOf(content) + .clearEntityKeys(); + + final FormHandle formHandle = this.pageService.formBuilder( + formContext) + .withDefaultSpanInput(5) + .withEmptyCellSeparation(true) + .withDefaultSpanEmptyCell(1) + .readonly(isReadonly) + + .addField(FormBuilder.text( + "Info", + SEB_PROCTORING_FORM_INFO_TITLE, + this.pageService.getI18nSupport().getText(SEB_PROCTORING_FORM_INFO)) + .asArea(50) + .asHTML() + .readonly(true)) + + .addField(FormBuilder.checkbox( + ProctoringServiceSettings.ATTR_ENABLE_PROCTORING, + SEB_PROCTORING_FORM_ENABLE, + String.valueOf(proctoringSettings.enableProctoring))) + + .addField(FormBuilder.singleSelection( + ProctoringServiceSettings.ATTR_SERVER_TYPE, + SEB_PROCTORING_FORM_TYPE, + proctoringSettings.serverType.name(), + resourceService::examProctoringTypeResources)) + + .addField(FormBuilder.text( + ProctoringServiceSettings.ATTR_SERVER_URL, + SEB_PROCTORING_FORM_URL, + proctoringSettings.serverURL) + .mandatory()) + + .addField(FormBuilder.text( + ProctoringServiceSettings.ATTR_APP_KEY, + SEB_PROCTORING_FORM_APPKEY, + proctoringSettings.appKey) + .mandatory()) + .withEmptyCellSeparation(false) + + .addField(FormBuilder.password( + ProctoringServiceSettings.ATTR_APP_SECRET, + SEB_PROCTORING_FORM_SECRET, + (proctoringSettings.appSecret != null) + ? String.valueOf(proctoringSettings.appSecret) + : null) + .mandatory()) + + .addField(FormBuilder.text( + ProctoringServiceSettings.ATTR_SDK_KEY, + SEB_PROCTORING_FORM_SDKKEY, + proctoringSettings.sdkKey)) + .withEmptyCellSeparation(false) + + .addField(FormBuilder.password( + ProctoringServiceSettings.ATTR_SDK_SECRET, + SEB_PROCTORING_FORM_SDKSECRET, + (proctoringSettings.sdkSecret != null) + ? String.valueOf(proctoringSettings.sdkSecret) + : null)) + + .withDefaultSpanInput(1) + .addField(FormBuilder.text( + ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE, + SEB_PROCTORING_FORM_ROOM_SIZE, + String.valueOf(proctoringSettings.getCollectingRoomSize())) + .asNumber(numString -> Long.parseLong(numString))) + .withEmptyCellSeparation(true) + .withDefaultSpanEmptyCell(4) + .withDefaultSpanInput(5) + + .addField(FormBuilder.checkbox( + ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM, + SEB_PROCTORING_FORM_USE_ZOOM_APP_CLIENT, + String.valueOf(proctoringSettings.useZoomAppClientForCollectingRoom))) + .withDefaultSpanInput(5) + .withEmptyCellSeparation(true) + .withDefaultSpanEmptyCell(1) + + .addField(FormBuilder.multiCheckboxSelection( + ProctoringServiceSettings.ATTR_ENABLED_FEATURES, + SEB_PROCTORING_FORM_FEATURES, + StringUtils.join(proctoringSettings.enabledFeatures, Constants.LIST_SEPARATOR), + resourceService::examProctoringFeaturesResources)) + + .build(); + + if (proctoringSettings.serviceInUse) { + formHandle.getForm().getFieldInput(ProctoringServiceSettings.ATTR_SERVER_TYPE).setEnabled(false); + formHandle.getForm().getFieldInput(ProctoringServiceSettings.ATTR_SERVER_URL).setEnabled(false); + } + + return () -> formHandle; + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java index 0a9cc71b..7c2fda35 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java @@ -53,7 +53,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ConfirmPendingClientNotification; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData; @@ -376,7 +376,7 @@ public class MonitoringClientConnection implements TemplateComposer { if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) { final ProctoringServiceSettings proctoringSettings = restService - .getBuilder(GetProctoringSettings.class) + .getBuilder(GetExamProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId) .call() .onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java index 48150835..cb9e2174 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java @@ -56,7 +56,7 @@ import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; 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.FullPageMonitoringGUIUpdate; @@ -243,7 +243,7 @@ public class MonitoringRunningExam implements TemplateComposer { isExamSupporter)); final ProctoringServiceSettings proctoringSettings = this.restService - .getBuilder(GetProctoringSettings.class) + .getBuilder(GetExamProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .call() .getOr(null); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java index 8ffae1b5..2c58f42d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java @@ -32,7 +32,7 @@ import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.GuiServiceInfo; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageService; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @@ -69,7 +69,7 @@ public class JitsiMeetProctoringView extends AbstractProctoringView { .getProctoringGUIService(); final ProctoringServiceSettings proctoringSettings = this.pageService .getRestService() - .getBuilder(GetProctoringSettings.class) + .getBuilder(GetExamProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, proctoringWindowData.examId) .call() .onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java index 90013704..33cf3cee 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java @@ -32,7 +32,7 @@ import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.GuiServiceInfo; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageService; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @@ -69,7 +69,7 @@ public class ZoomProctoringView extends AbstractProctoringView { .getProctoringGUIService(); final ProctoringServiceSettings proctoringSettings = this.pageService .getRestService() - .getBuilder(GetProctoringSettings.class) + .getBuilder(GetExamProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, proctoringWindowData.examId) .call() .onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamProctoringSettings.java similarity index 89% rename from src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetProctoringSettings.java rename to src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamProctoringSettings.java index 25c836a7..2f1a3e07 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamProctoringSettings.java @@ -24,9 +24,9 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; @Lazy @Component @GuiProfile -public class GetProctoringSettings extends RestCall { +public class GetExamProctoringSettings extends RestCall { - public GetProctoringSettings() { + public GetExamProctoringSettings() { super(new TypeKey<>( CallType.GET_SINGLE, EntityType.EXAM_PROCTOR_DATA, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamTemplateProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamTemplateProctoringSettings.java new file mode 100644 index 00000000..d59937fe --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamTemplateProctoringSettings.java @@ -0,0 +1,42 @@ +/* + * 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.exam; + +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.model.exam.ProctoringServiceSettings; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class GetExamTemplateProctoringSettings extends RestCall { + + public GetExamTemplateProctoringSettings() { + super(new TypeKey<>( + CallType.GET_SINGLE, + EntityType.EXAM_PROCTOR_DATA, + new TypeReference() { + }), + HttpMethod.GET, + MediaType.APPLICATION_JSON, + API.EXAM_TEMPLATE_ENDPOINT + + API.MODEL_ID_VAR_PATH_SEGMENT + + API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExamProctoringSettings.java similarity index 80% rename from src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java rename to src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExamProctoringSettings.java index dbacc253..8a3c5bfb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExamProctoringSettings.java @@ -17,20 +17,20 @@ 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.model.exam.Exam; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; @Lazy @Component @GuiProfile -public class SaveProctoringSettings extends RestCall { +public class SaveExamProctoringSettings extends RestCall { - public SaveProctoringSettings() { + public SaveExamProctoringSettings() { super(new TypeKey<>( CallType.SAVE, EntityType.EXAM_PROCTOR_DATA, - new TypeReference() { + new TypeReference() { }), HttpMethod.POST, MediaType.APPLICATION_JSON, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExamTemplateProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExamTemplateProctoringSettings.java new file mode 100644 index 00000000..3f41700c --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExamTemplateProctoringSettings.java @@ -0,0 +1,42 @@ +/* + * 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.exam; + +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.model.exam.ProctoringServiceSettings; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class SaveExamTemplateProctoringSettings extends RestCall { + + public SaveExamTemplateProctoringSettings() { + super(new TypeKey<>( + CallType.SAVE, + EntityType.EXAM_PROCTOR_DATA, + new TypeReference() { + }), + HttpMethod.POST, + MediaType.APPLICATION_JSON, + API.EXAM_TEMPLATE_ENDPOINT + + API.MODEL_ID_VAR_PATH_SEGMENT + + API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/AdditionalAttributesDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/AdditionalAttributesDAO.java index 1d73b34b..ed41f2e5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/AdditionalAttributesDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/AdditionalAttributesDAO.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.stream.Collectors; import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord; @@ -22,6 +23,18 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttribut * in a separated data-base table. */ public interface AdditionalAttributesDAO { + /** Use this to get all additional attribute records for a specific entity. + * + * @param type the entity type + * @param entityId the entity identifier (primary key) + * @return Result refer to the collection of additional attribute records or to an error if happened */ + default Result> getAdditionalAttributes(final EntityKey entityKey) { + return Result.tryCatch(() -> getAdditionalAttributes( + entityKey.entityType, + Long.valueOf(entityKey.modelId)) + .getOrThrow()); + } + /** Use this to get all additional attribute records for a specific entity. * * @param type the entity type diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java index 24ce95e2..61f9c246 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java @@ -12,7 +12,6 @@ import org.apache.commons.lang3.BooleanUtils; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; -import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; @@ -83,19 +82,10 @@ public interface ExamAdminService { * @return Result refer to proctoring is enabled flag or to an error when happened. */ Result isProctoringEnabled(final Long examId); - /** Get the exam proctoring service implementation of specified type. - * - * @param type exam proctoring service server type - * @return ExamProctoringService instance */ - Result getExamProctoringService(final ProctoringServerType type); - /** Get the exam proctoring service implementation for specified exam. * * @param examId the exam identifier * @return ExamProctoringService instance */ - default Result getExamProctoringService(final Long examId) { - return getProctoringServiceSettings(examId) - .flatMap(settings -> getExamProctoringService(settings.serverType)); - } + Result getExamProctoringService(final Long examId); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ProctoringAdminService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ProctoringAdminService.java new file mode 100644 index 00000000..6a0d3355 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ProctoringAdminService.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 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.servicelayer.exam; + +import java.util.EnumSet; + +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; + +public interface ProctoringAdminService { + + EnumSet SUPPORTED_PARENT_ENTITES = EnumSet.of( + EntityType.EXAM, + EntityType.EXAM_TEMPLATE); + + /** Get proctoring service settings for a certain entity (SUPPORTED_PARENT_ENTITES). + * + * @param parentEntityKey the entity key of the parent entity to get the proctoring service settings from + * @return Result refer to proctoring service settings or to an error when happened. */ + Result getProctoringSettings(EntityKey parentEntityKey); + + /** Save the given proctoring service settings for a certain entity (SUPPORTED_PARENT_ENTITES). + * + * @param parentEntityKey the entity key of the parent entity to save the proctoring service settings to + * @param proctoringServiceSettings The proctoring service settings to save + * @return Result refer to saved proctoring service settings or to an error when happened. */ + Result saveProctoringServiceSettings( + EntityKey parentEntityKey, + ProctoringServiceSettings proctoringServiceSettings); + + /** Get the exam proctoring service implementation of specified type. + * + * @param type exam proctoring service server type + * @return ExamProctoringService instance */ + Result getExamProctoringService(final ProctoringServerType type); + + /** Use this to test the proctoring service settings against the remote proctoring server. + * + * @param proctoringSettings the settings to test + * @return Result refer to true if the settings are correct and the proctoring server can be accessed. */ + default Result testExamProctoring(final ProctoringServiceSettings proctoringSettings) { + return getExamProctoringService(proctoringSettings.serverType) + .flatMap(service -> service.testExamProctoring(proctoringSettings)); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java index 2d38f963..2da1e5e4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java @@ -9,12 +9,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl; import java.util.Arrays; -import java.util.EnumSet; import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -26,28 +21,23 @@ import org.springframework.transaction.annotation.Transactional; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; -import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature; -import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; -import ch.ethz.seb.sebserver.gbl.util.Cryptor; import ch.ethz.seb.sebserver.gbl.util.Result; -import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ExamProctoringServiceFactory; @Lazy @Service @@ -57,26 +47,20 @@ public class ExamAdminServiceImpl implements ExamAdminService { private static final Logger log = LoggerFactory.getLogger(ExamAdminServiceImpl.class); private final ExamDAO examDAO; + private final ProctoringAdminService proctoringServiceSettingsService; private final AdditionalAttributesDAO additionalAttributesDAO; private final LmsAPIService lmsAPIService; - private final Cryptor cryptor; - private final ExamProctoringServiceFactory examProctoringServiceFactory; - private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; protected ExamAdminServiceImpl( final ExamDAO examDAO, + final ProctoringAdminService proctoringServiceSettingsService, final AdditionalAttributesDAO additionalAttributesDAO, - final LmsAPIService lmsAPIService, - final Cryptor cryptor, - final ExamProctoringServiceFactory examProctoringServiceFactory, - final RemoteProctoringRoomDAO remoteProctoringRoomDAO) { + final LmsAPIService lmsAPIService) { this.examDAO = examDAO; + this.proctoringServiceSettingsService = proctoringServiceSettingsService; this.additionalAttributesDAO = additionalAttributesDAO; this.lmsAPIService = lmsAPIService; - this.cryptor = cryptor; - this.examProctoringServiceFactory = examProctoringServiceFactory; - this.remoteProctoringRoomDAO = remoteProctoringRoomDAO; } @Override @@ -135,26 +119,7 @@ public class ExamAdminServiceImpl implements ExamAdminService { @Override public Result getProctoringServiceSettings(final Long examId) { - return this.additionalAttributesDAO.getAdditionalAttributes(EntityType.EXAM, examId) - .map(attrs -> attrs.stream() - .collect(Collectors.toMap( - attr -> attr.getName(), - Function.identity()))) - .map(mapping -> { - return new ProctoringServiceSettings( - examId, - getEnabled(mapping), - getServerType(mapping), - getString(mapping, ProctoringServiceSettings.ATTR_SERVER_URL), - getCollectingRoomSize(mapping), - getEnabledFeatures(mapping), - this.remoteProctoringRoomDAO.isServiceInUse(examId).getOr(true), - getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY), - getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET), - getString(mapping, ProctoringServiceSettings.ATTR_SDK_KEY), - getString(mapping, ProctoringServiceSettings.ATTR_SDK_SECRET), - getBoolean(mapping, ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM)); - }); + return this.proctoringServiceSettingsService.getProctoringSettings(new EntityKey(examId, EntityType.EXAM)); } @Override @@ -163,79 +128,14 @@ public class ExamAdminServiceImpl implements ExamAdminService { final Long examId, final ProctoringServiceSettings proctoringServiceSettings) { - return Result.tryCatch(() -> { - - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_ENABLE_PROCTORING, - String.valueOf(proctoringServiceSettings.enableProctoring)); - - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_SERVER_TYPE, - proctoringServiceSettings.serverType.name()); - - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_SERVER_URL, - StringUtils.trim(proctoringServiceSettings.serverURL)); - - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE, - String.valueOf(proctoringServiceSettings.collectingRoomSize)); - - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_APP_KEY, - StringUtils.trim(proctoringServiceSettings.appKey)); - - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_APP_SECRET, - this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.appSecret)) - .getOrThrow() - .toString()); - - if (StringUtils.isNotBlank(proctoringServiceSettings.sdkKey)) { - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_SDK_KEY, - StringUtils.trim(proctoringServiceSettings.sdkKey)); - - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_SDK_SECRET, - this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.sdkSecret)) - .getOrThrow() - .toString()); - } - - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_ENABLED_FEATURES, - StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR)); - - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - examId, - ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM, - String.valueOf(proctoringServiceSettings.useZoomAppClientForCollectingRoom)); - - // Mark the involved exam as updated to notify changes - this.examDAO.setModified(examId); - - return proctoringServiceSettings; - }); + return this.proctoringServiceSettingsService + .saveProctoringServiceSettings( + new EntityKey(examId, EntityType.EXAM), + proctoringServiceSettings) + .map(settings -> { + this.examDAO.setModified(examId); + return settings; + }); } @Override @@ -256,78 +156,10 @@ public class ExamAdminServiceImpl implements ExamAdminService { } @Override - public Result getExamProctoringService(final ProctoringServerType type) { - return this.examProctoringServiceFactory - .getExamProctoringService(type); - } - - private Boolean getEnabled(final Map mapping) { - if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING)) { - return BooleanUtils.toBoolean(mapping.get(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING).getValue()); - } else { - return false; - } - } - - private ProctoringServerType getServerType(final Map mapping) { - if (mapping.containsKey(ProctoringServiceSettings.ATTR_SERVER_TYPE)) { - return ProctoringServerType.valueOf(mapping.get(ProctoringServiceSettings.ATTR_SERVER_TYPE).getValue()); - } else { - return ProctoringServerType.JITSI_MEET; - } - } - - private String getString(final Map mapping, final String name) { - if (mapping.containsKey(name)) { - return mapping.get(name).getValue(); - } else { - return null; - } - } - - private Boolean getBoolean(final Map mapping, final String name) { - if (mapping.containsKey(name)) { - return BooleanUtils.toBooleanObject(mapping.get(name).getValue()); - } else { - return false; - } - } - - private Integer getCollectingRoomSize(final Map mapping) { - if (mapping.containsKey(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)) { - return Integer.valueOf(mapping.get(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE).getValue()); - } else { - return 20; - } - } - - private EnumSet getEnabledFeatures(final Map mapping) { - if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLED_FEATURES)) { - try { - final String value = mapping.get(ProctoringServiceSettings.ATTR_ENABLED_FEATURES).getValue(); - return StringUtils.isNotBlank(value) - ? EnumSet.copyOf(Arrays.asList(StringUtils.split(value, Constants.LIST_SEPARATOR)) - .stream() - .map(str -> { - try { - return ProctoringFeature.valueOf(str); - } catch (final Exception e) { - log.error( - "Failed to enabled single features for proctoring settings. Skipping. {}", - e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toSet())) - : EnumSet.noneOf(ProctoringFeature.class); - } catch (final Exception e) { - log.error("Failed to get enabled features for proctoring settings. Enable all. {}", e.getMessage()); - return EnumSet.allOf(ProctoringFeature.class); - } - } else { - return EnumSet.allOf(ProctoringFeature.class); - } + public Result getExamProctoringService(final Long examId) { + return getProctoringServiceSettings(examId) + .flatMap(settings -> this.proctoringServiceSettingsService + .getExamProctoringService(settings.serverType)); } private Result saveAdditionalAttributesForMoodleExams(final Exam exam) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java index 973d3814..dbcc1562 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java @@ -118,7 +118,8 @@ public class ExamTemplateServiceImpl implements ExamTemplateService { @Override public Result initAdditionalAttributes(final Exam exam) { - return this.examAdminService.saveLMSAttributes(exam) + return this.examAdminService + .saveLMSAttributes(exam) .map(_exam -> { if (exam.examTemplateId != null) { @@ -148,7 +149,8 @@ public class ExamTemplateServiceImpl implements ExamTemplateService { } } return _exam; - }).onError(error -> log.error("Failed to create additional attributes defined by template for exam: ", + }).onError(error -> log.error( + "Failed to create additional attributes defined by template for exam: ", error)); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java new file mode 100644 index 00000000..897ead0b --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2022 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.servicelayer.exam.impl; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +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.Service; +import org.springframework.transaction.annotation.Transactional; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Cryptor; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ExamProctoringServiceFactory; + +@Lazy +@Service +@WebServiceProfile +public class ProctoringAdminServiceImpl implements ProctoringAdminService { + + private static final Logger log = LoggerFactory.getLogger(ProctoringAdminServiceImpl.class); + + private final AdditionalAttributesDAO additionalAttributesDAO; + private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; + private final ExamProctoringServiceFactory examProctoringServiceFactory; + private final Cryptor cryptor; + + public ProctoringAdminServiceImpl( + final AdditionalAttributesDAO additionalAttributesDAO, + final RemoteProctoringRoomDAO remoteProctoringRoomDAO, + final ExamProctoringServiceFactory examProctoringServiceFactory, + final Cryptor cryptor) { + + this.additionalAttributesDAO = additionalAttributesDAO; + this.remoteProctoringRoomDAO = remoteProctoringRoomDAO; + this.examProctoringServiceFactory = examProctoringServiceFactory; + this.cryptor = cryptor; + } + + @Override + @Transactional(readOnly = true) + public Result getProctoringSettings(final EntityKey parentEntityKey) { + + return Result.tryCatch(() -> { + final Long entityId = Long.parseLong(parentEntityKey.modelId); + checkType(parentEntityKey); + + return this.additionalAttributesDAO + .getAdditionalAttributes(parentEntityKey.entityType, entityId) + .map(attrs -> attrs.stream() + .collect(Collectors.toMap( + attr -> attr.getName(), + Function.identity()))) + .map(mapping -> { + return new ProctoringServiceSettings( + entityId, + getEnabled(mapping), + getServerType(mapping), + getString(mapping, ProctoringServiceSettings.ATTR_SERVER_URL), + getCollectingRoomSize(mapping), + getEnabledFeatures(mapping), + parentEntityKey.entityType == EntityType.EXAM + ? this.remoteProctoringRoomDAO.isServiceInUse(entityId).getOr(true) + : false, + getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY), + getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET), + getString(mapping, ProctoringServiceSettings.ATTR_SDK_KEY), + getString(mapping, ProctoringServiceSettings.ATTR_SDK_SECRET), + getBoolean(mapping, + ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM)); + }) + .getOrThrow(); + + }); + } + + @Override + @Transactional + public Result saveProctoringServiceSettings( + final EntityKey parentEntityKey, + final ProctoringServiceSettings proctoringServiceSettings) { + + return Result.tryCatch(() -> { + final Long entityId = Long.parseLong(parentEntityKey.modelId); + checkType(parentEntityKey); + + if (StringUtils.isNotBlank(proctoringServiceSettings.serverURL)) { + testExamProctoring(proctoringServiceSettings).getOrThrow(); + } + + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_ENABLE_PROCTORING, + String.valueOf(proctoringServiceSettings.enableProctoring)); + + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_SERVER_TYPE, + proctoringServiceSettings.serverType.name()); + + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_SERVER_URL, + StringUtils.trim(proctoringServiceSettings.serverURL)); + + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE, + String.valueOf(proctoringServiceSettings.collectingRoomSize)); + + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_APP_KEY, + StringUtils.trim(proctoringServiceSettings.appKey)); + + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_APP_SECRET, + this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.appSecret)) + .getOrThrow() + .toString()); + + if (StringUtils.isNotBlank(proctoringServiceSettings.sdkKey)) { + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_SDK_KEY, + StringUtils.trim(proctoringServiceSettings.sdkKey)); + + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_SDK_SECRET, + this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.sdkSecret)) + .getOrThrow() + .toString()); + } + + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_ENABLED_FEATURES, + StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR)); + + this.additionalAttributesDAO.saveAdditionalAttribute( + parentEntityKey.entityType, + entityId, + ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM, + String.valueOf(proctoringServiceSettings.useZoomAppClientForCollectingRoom)); + + return proctoringServiceSettings; + }); + } + + @Override + public Result getExamProctoringService(final ProctoringServerType type) { + return this.examProctoringServiceFactory + .getExamProctoringService(type); + } + + private void checkType(final EntityKey parentEntityKey) { + if (!SUPPORTED_PARENT_ENTITES.contains(parentEntityKey.entityType)) { + throw new UnsupportedOperationException( + "No proctoring service settings supported for entity: " + parentEntityKey); + } + } + + private Boolean getEnabled(final Map mapping) { + if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING)) { + return BooleanUtils.toBoolean(mapping.get(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING).getValue()); + } else { + return false; + } + } + + private ProctoringServerType getServerType(final Map mapping) { + if (mapping.containsKey(ProctoringServiceSettings.ATTR_SERVER_TYPE)) { + return ProctoringServerType.valueOf(mapping.get(ProctoringServiceSettings.ATTR_SERVER_TYPE).getValue()); + } else { + return ProctoringServerType.JITSI_MEET; + } + } + + private String getString(final Map mapping, final String name) { + if (mapping.containsKey(name)) { + return mapping.get(name).getValue(); + } else { + return null; + } + } + + private Boolean getBoolean(final Map mapping, final String name) { + if (mapping.containsKey(name)) { + return BooleanUtils.toBooleanObject(mapping.get(name).getValue()); + } else { + return false; + } + } + + private Integer getCollectingRoomSize(final Map mapping) { + if (mapping.containsKey(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)) { + return Integer.valueOf(mapping.get(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE).getValue()); + } else { + return 20; + } + } + + private EnumSet getEnabledFeatures(final Map mapping) { + if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLED_FEATURES)) { + try { + final String value = mapping.get(ProctoringServiceSettings.ATTR_ENABLED_FEATURES).getValue(); + return StringUtils.isNotBlank(value) + ? EnumSet.copyOf(Arrays.asList(StringUtils.split(value, Constants.LIST_SEPARATOR)) + .stream() + .map(str -> { + try { + return ProctoringFeature.valueOf(str); + } catch (final Exception e) { + log.error( + "Failed to enabled single features for proctoring settings. Skipping. {}", + e.getMessage()); + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet())) + : EnumSet.noneOf(ProctoringFeature.class); + } catch (final Exception e) { + log.error("Failed to get enabled features for proctoring settings. Enable all. {}", e.getMessage()); + return EnumSet.allOf(ProctoringFeature.class); + } + } else { + return EnumSet.allOf(ProctoringFeature.class); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java index 0259349e..5241d681 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java @@ -40,6 +40,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; @@ -58,6 +59,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; private final ClientConnectionDAO clientConnectionDAO; private final ExamAdminService examAdminService; + private final ProctoringAdminService proctoringAdminService; private final ExamSessionService examSessionService; private final SEBClientInstructionService sebInstructionService; private final boolean sendBroadcastReset; @@ -66,6 +68,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService final RemoteProctoringRoomDAO remoteProctoringRoomDAO, final ClientConnectionDAO clientConnectionDAO, final ExamAdminService examAdminService, + final ProctoringAdminService proctoringAdminService, final ExamSessionService examSessionService, final SEBClientInstructionService sebInstructionService, @Value("${sebserver.webservice.proctoring.resetBroadcastOnLeav:true}") final boolean sendBroadcastReset) { @@ -73,6 +76,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService this.remoteProctoringRoomDAO = remoteProctoringRoomDAO; this.clientConnectionDAO = clientConnectionDAO; this.examAdminService = examAdminService; + this.proctoringAdminService = proctoringAdminService; this.examSessionService = examSessionService; this.sebInstructionService = sebInstructionService; this.sendBroadcastReset = sendBroadcastReset; @@ -146,7 +150,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService event.ids.forEach(examId -> { try { - this.examAdminService.examForPK(examId) + this.examAdminService + .examForPK(examId) .flatMap(this::disposeRoomsForExam) .getOrThrow(); @@ -176,7 +181,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getProctoringServiceSettings(exam.id) .getOrThrow(); - this.examAdminService + this.proctoringAdminService .getExamProctoringService(proctoringSettings.serverType) .flatMap(service -> service.disposeServiceRoomsForExam(exam.id, proctoringSettings)) .onError(error -> log.error("Failed to dispose proctoring service rooms for exam: {} / {}", @@ -204,7 +209,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getProctoringServiceSettings(examId) .getOrThrow(); - final ExamProctoringService examProctoringService = this.examAdminService + final ExamProctoringService examProctoringService = this.proctoringAdminService .getExamProctoringService(settings.serverType) .getOrThrow(); @@ -242,7 +247,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getProctoringServiceSettings(examId) .getOrThrow(); - final ExamProctoringService examProctoringService = this.examAdminService + final ExamProctoringService examProctoringService = this.proctoringAdminService .getExamProctoringService(settings.serverType) .getOrThrow(); @@ -251,7 +256,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .flatMap(room -> this.remoteProctoringRoomDAO.createBreakOutRoom(examId, room, connectionTokens)) .getOrThrow(); - return this.examAdminService + return this.proctoringAdminService .getExamProctoringService(settings.serverType) .map(service -> sendJoinRoomBreakOutInstructions( settings, @@ -272,7 +277,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getProctoringServiceSettings(examId) .getOrThrow(); - final ExamProctoringService examProctoringService = this.examAdminService + final ExamProctoringService examProctoringService = this.proctoringAdminService .getExamProctoringService(settings.serverType) .getOrThrow(); @@ -357,7 +362,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getProctoringServiceSettings(examId) .getOrThrow(); - final ExamProctoringService examProctoringService = this.examAdminService + final ExamProctoringService examProctoringService = this.proctoringAdminService .getExamProctoringService(proctoringSettings.serverType) .getOrThrow(); @@ -401,7 +406,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getProctoringServiceSettings(examId) .getOrThrow(); - final ExamProctoringService examProctoringService = this.examAdminService + final ExamProctoringService examProctoringService = this.proctoringAdminService .getExamProctoringService(proctoringSettings.serverType) .getOrThrow(); @@ -574,7 +579,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getProctoringServiceSettings(examId) .getOrThrow(); - final ExamProctoringService examProctoringService = this.examAdminService + final ExamProctoringService examProctoringService = this.proctoringAdminService .getExamProctoringService(settings.serverType) .getOrThrow(); @@ -635,7 +640,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getProctoringServiceSettings(examId) .getOrThrow(); - final ExamProctoringService examProctoringService = this.examAdminService + final ExamProctoringService examProctoringService = this.proctoringAdminService .getExamProctoringService(settings.serverType) .getOrThrow(); @@ -689,7 +694,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService final String roomName, final String subject) { - final ExamProctoringService examProctoringService = this.examAdminService + final ExamProctoringService examProctoringService = this.proctoringAdminService .getExamProctoringService(proctoringSettings.serverType) .getOrThrow(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index 457f6b78..b81e243b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -381,11 +381,6 @@ public class ExamAdministrationController extends EntityController { .byPK(examId) .flatMap(this.authorization::checkModify) .map(exam -> { - if (StringUtils.isNotBlank(proctoringServiceSettings.serverURL)) { - this.examAdminService.getExamProctoringService(proctoringServiceSettings.serverType) - .flatMap(service -> service.testExamProctoring(proctoringServiceSettings)) - .getOrThrow(); - } this.examAdminService.saveProctoringServiceSettings(examId, proctoringServiceSettings); return exam; }) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java index 4c223a93..799d8925 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java @@ -39,6 +39,7 @@ import ch.ethz.seb.sebserver.gbl.model.PageSortOrder; import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamTemplateRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; @@ -48,6 +49,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamTemplateDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; @WebServiceProfile @@ -56,6 +58,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe public class ExamTemplateController extends EntityController { private final ExamTemplateDAO examTemplateDAO; + private final ProctoringAdminService proctoringServiceSettingsService; protected ExamTemplateController( final AuthorizationService authorization, @@ -63,7 +66,8 @@ public class ExamTemplateController extends EntityController { + this.proctoringServiceSettingsService.saveProctoringServiceSettings( + new EntityKey(examId, EntityType.EXAM_TEMPLATE), + proctoringServiceSettings); + return exam; + }) + .flatMap(this.userActivityLogDAO::logModify) + .getOrThrow(); + } + + // **** Proctoring + // **************************************************************************** + @Override protected ExamTemplate createNew(final POSTMapper postParams) { final Long institutionId = postParams.getLong(API.PARAM_INSTITUTION_ID); diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java index 02f6fdfd..26d0f9e1 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java @@ -132,6 +132,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfi import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamDependencies; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplatePage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplates; @@ -140,7 +141,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicator import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorTemplatePage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSEBRestrictionSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamConfigMapping; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamTemplate; @@ -148,10 +148,10 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewIndicator import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewIndicatorTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamConfigMapping; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicator; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicatorTemplate; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSEBRestriction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.ActivateInstitution; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution; @@ -3371,15 +3371,15 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { final RestServiceImpl restService = createRestServiceForUser( "admin", "admin", - new GetProctoringSettings(), - new SaveProctoringSettings()); + new GetExamProctoringSettings(), + new SaveExamProctoringSettings()); final Exam exam = createTestExam("admin", "admin"); assertNotNull(exam); assertEquals("Demo Quiz 6 (MOCKUP)", exam.name); final ProctoringServiceSettings settings = restService - .getBuilder(GetProctoringSettings.class) + .getBuilder(GetExamProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) .call() .getOrThrow(); @@ -3401,16 +3401,16 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { "sdkSecret", false); - final Result saveCall = restService - .getBuilder(SaveProctoringSettings.class) + final Result saveCall = restService + .getBuilder(SaveExamProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) .withBody(newSettings) .call(); if (!saveCall.hasError()) { assertFalse(saveCall.hasError()); - final Exam exam2 = saveCall.get(); - assertEquals(exam2.id, exam.id); + final ProctoringServiceSettings settings2 = saveCall.get(); + assertEquals(settings2.examId, exam.id); } } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java index 9c1874c6..15863ec3 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java @@ -63,7 +63,7 @@ public class ExamProctoringRoomServiceTest extends AdministrationAPIIntegrationT this.examAdminService.saveProctoringServiceSettings( 2L, new ProctoringServiceSettings( - 2L, true, ProctoringServerType.JITSI_MEET, "http://jitsi.ch", 1, null, false, + 2L, true, ProctoringServerType.JITSI_MEET, "", 1, null, false, "app-key", "app.secret", "sdk-key", "sdk.secret", false)); assertTrue(this.examAdminService.isProctoringEnabled(2L).get());