diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 0f807018..7c006678 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -20,7 +20,7 @@ public final class API { public enum BatchActionType { EXAM_CONFIG_STATE_CHANGE(EntityType.CONFIGURATION_NODE), - EXAM_CONFIG_APPLY_TEMPLATE_VALUES(EntityType.CONFIGURATION_NODE); + EXAM_CONFIG_REST_TEMPLATE_SETTINGS(EntityType.CONFIGURATION_NODE); public final EntityType entityType; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java index e8393fa0..37da4881 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java @@ -246,7 +246,7 @@ public class APIMessage implements Serializable { } public APIMessageException(final APIMessage apiMessage) { - super(); + super(apiMessage.systemMessage + " " + apiMessage.details); this.apiMessages = Arrays.asList(apiMessage); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserLogActivityType.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserLogActivityType.java index 777140a9..1ad5c6d6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserLogActivityType.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserLogActivityType.java @@ -18,6 +18,7 @@ public enum UserLogActivityType { PASSWORD_CHANGE, DEACTIVATE, ACTIVATE, + FINISHED, DELETE, LOGIN, LOGOUT diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 886fb9b8..10ea439b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -533,6 +533,12 @@ public enum ActionDefinition { PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST, ActionCategory.SEB_EXAM_CONFIG_LIST), + SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE( + new LocTextKey("sebserver.examconfig.list.action.reset"), + ImageIcon.EXPORT, + PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST, + ActionCategory.SEB_EXAM_CONFIG_LIST), + SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST( new LocTextKey("sebserver.examconfig.action.list.modify.properties"), ImageIcon.EDIT, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/InstitutionList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/InstitutionList.java index 55f18947..24f3053e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/InstitutionList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/InstitutionList.java @@ -138,14 +138,14 @@ public class InstitutionList implements TemplateComposer { .newAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST) .withSelect( - table::getSelection, + table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .publish(false) .newAction(ActionDefinition.INSTITUTION_MODIFY_FROM_LIST) .withSelect( - table::getSelection, + table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> instGrant.m(), false) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/UserAccountList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/UserAccountList.java index d6790fca..c6032e0b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/UserAccountList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/UserAccountList.java @@ -222,11 +222,12 @@ public class UserAccountList implements TemplateComposer { .publishIf(userGrant::iw) .newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST) - .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, + EMPTY_SELECTION_TEXT_KEY) .publish(false) .newAction(ActionDefinition.USER_ACCOUNT_MODIFY_FROM_LIST) - .withSelect(table::getSelection, this::editAction, EMPTY_SELECTION_TEXT_KEY) + .withSelect(table::getMultiSelection, this::editAction, EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> userGrant.im(), false) .newAction(ActionDefinition.USER_ACCOUNT_TOGGLE_ACTIVITY) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/UserActivityLogs.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/UserActivityLogs.java index 3da98473..cfd870d9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/UserActivityLogs.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/admin/UserActivityLogs.java @@ -244,7 +244,7 @@ public class UserActivityLogs implements TemplateComposer { actionBuilder .newAction(ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS) .withSelect( - table::getSelection, + table::getMultiSelection, action -> this.showDetails(action, table.getSingleSelectedROWData()), EMPTY_SELECTION_TEXT) .noEventPropagation() diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/CertificateList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/CertificateList.java index f887aea6..cc508c0a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/CertificateList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/CertificateList.java @@ -159,7 +159,7 @@ public class CertificateList implements TemplateComposer { .newAction(ActionDefinition.SEB_CERTIFICATE_REMOVE) .withConfirm(() -> FORM_ACTION_MESSAGE_REMOVE_CONFIRM_TEXT_KEY) .withSelect( - table::getSelection, + table::getMultiSelection, this::removeCertificate, EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> grantCheck.iw(), false); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateForm.java index d332ddb9..ffadcf76 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateForm.java @@ -249,7 +249,7 @@ public class ConfigTemplateForm implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_EDIT) .withParentEntityKey(entityKey) .withSelect( - attrTable::getSelection, + attrTable::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY) .publishIf(() -> modifyGrant, false) @@ -257,7 +257,7 @@ public class ConfigTemplateForm implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_SET_DEFAULT) .withParentEntityKey(entityKey) .withSelect( - attrTable::getSelection, + attrTable::getMultiSelection, action -> this.resetToDefaults(action, attrTable), EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY) .noEventPropagation() @@ -266,7 +266,7 @@ public class ConfigTemplateForm implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_REMOVE_VIEW) .withParentEntityKey(entityKey) .withSelect( - attrTable::getSelection, + attrTable::getMultiSelection, action -> this.removeFormView(action, attrTable), EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY) .noEventPropagation() @@ -275,7 +275,7 @@ public class ConfigTemplateForm implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_ATTACH_DEFAULT_VIEW) .withParentEntityKey(entityKey) .withSelect( - attrTable::getSelection, + attrTable::getMultiSelection, action -> this.attachView(action, attrTable), EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY) .noEventPropagation() diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateList.java index 37eae780..1286fa34 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateList.java @@ -147,7 +147,7 @@ public class ConfigTemplateList implements TemplateComposer { .publishIf(examConfigGrant::iw) .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST) - .withSelect(templateTable::getSelection, PageAction::applySingleSelectionAsEntityKey, + .withSelect(templateTable::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_TEMPLATE_SELECTION_TEXT_KEY) .publish(false) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigList.java index 18b80186..4c170341 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigList.java @@ -173,7 +173,8 @@ public class SEBClientConfigList implements TemplateComposer { .publishIf(clientConfigGrant::iw) .newAction(ActionDefinition.SEB_CLIENT_CONFIG_VIEW_FROM_LIST) - .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, + EMPTY_SELECTION_TEXT_KEY) .publish(false) .newAction(ActionDefinition.SEB_CLIENT_CONFIG_MODIFY_FROM_LIST) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchResetToTemplatePopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchResetToTemplatePopup.java new file mode 100644 index 00000000..b6158cfe --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchResetToTemplatePopup.java @@ -0,0 +1,95 @@ +/* + * 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.gui.content.configs; + +import java.util.function.Supplier; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType; +import ch.ethz.seb.sebserver.gbl.model.BatchAction; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.form.FormBuilder; +import ch.ethz.seb.sebserver.gui.form.FormHandle; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.AbstractBatchActionWizard; +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.push.ServerPushService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class SEBExamConfigBatchResetToTemplatePopup extends AbstractBatchActionWizard { + + private final static LocTextKey FORM_TITLE = + new LocTextKey("sebserver.examconfig.list.batch.reset.title"); + private final static LocTextKey ACTION_DO_RESET = + new LocTextKey("sebserver.examconfig.list.batch.action.reset"); + private final static LocTextKey FORM_INFO = + new LocTextKey("sebserver.examconfig.list.batch.action.reset.info"); + + protected SEBExamConfigBatchResetToTemplatePopup( + final PageService pageService, + final ServerPushService serverPushService) { + + super(pageService, serverPushService); + } + + @Override + protected LocTextKey getTitle() { + return FORM_TITLE; + } + + @Override + protected LocTextKey getBatchActionInfo() { + return FORM_INFO; + } + + @Override + protected LocTextKey getBatchActionTitle() { + return ACTION_DO_RESET; + } + + @Override + protected BatchActionType getBatchActionType() { + return BatchActionType.EXAM_CONFIG_REST_TEMPLATE_SETTINGS; + } + + @Override + protected Supplier createResultPageSupplier( + final PageContext pageContext, + final FormHandle formHandle) { + + // No specific fields for this action + return () -> pageContext; + } + + @Override + protected void extendBatchActionRequest( + final PageContext pageContext, + final RestCall.RestCallBuilder batchActionRequestBuilder) { + + // Nothing to do here + } + + @Override + protected FormBuilder buildSpecificFormFields( + final PageContext formContext, + final FormBuilder formHead, + final boolean readonly) { + + // No specific fields for this action + return formHead; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigStateChangePopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchStateChangePopup.java similarity index 94% rename from src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigStateChangePopup.java rename to src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchStateChangePopup.java index 30c74b07..76e5d5fa 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigStateChangePopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchStateChangePopup.java @@ -32,7 +32,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; @Lazy @Component @GuiProfile -public class SEBExamConfigStateChangePopup extends AbstractBatchActionWizard { +public class SEBExamConfigBatchStateChangePopup extends AbstractBatchActionWizard { private static final String ATTR_SELECTED_TARGET_STATE = "selectedTargetState"; @@ -45,7 +45,7 @@ public class SEBExamConfigStateChangePopup extends AbstractBatchActionWizard { private final static LocTextKey FORM_STATUS_TEXT_KEY = new LocTextKey("sebserver.examconfig.list.batch.action.status"); - protected SEBExamConfigStateChangePopup( + protected SEBExamConfigBatchStateChangePopup( final PageService pageService, final ServerPushService serverPushService) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigList.java index 4b7d0075..e02d4ad1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigList.java @@ -70,7 +70,8 @@ public class SEBExamConfigList implements TemplateComposer { private final PageService pageService; private final SEBExamConfigImportPopup sebExamConfigImportPopup; private final SEBExamConfigCreationPopup sebExamConfigCreationPopup; - private final SEBExamConfigStateChangePopup sebExamConfigStateChangePopup; + private final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup; + private final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup; private final CurrentUser currentUser; private final ResourceService resourceService; private final int pageSize; @@ -79,13 +80,15 @@ public class SEBExamConfigList implements TemplateComposer { final PageService pageService, final SEBExamConfigImportPopup sebExamConfigImportPopup, final SEBExamConfigCreationPopup sebExamConfigCreationPopup, - final SEBExamConfigStateChangePopup sebExamConfigStateChangePopup, + final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup, + final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup, @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; this.sebExamConfigImportPopup = sebExamConfigImportPopup; this.sebExamConfigCreationPopup = sebExamConfigCreationPopup; - this.sebExamConfigStateChangePopup = sebExamConfigStateChangePopup; + this.sebExamConfigBatchStateChangePopup = sebExamConfigBatchStateChangePopup; + this.sebExamConfigBatchResetToTemplatePopup = sebExamConfigBatchResetToTemplatePopup; this.currentUser = pageService.getCurrentUser(); this.resourceService = pageService.getResourceService(); this.pageSize = pageSize; @@ -159,7 +162,8 @@ public class SEBExamConfigList implements TemplateComposer { ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST, ActionDefinition.SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST, ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG_FROM_LIST, - ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE)) + ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE, + ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE)) .compose(pageContext.copyOf(content)); @@ -171,7 +175,7 @@ public class SEBExamConfigList implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST) .withSelect( - configTable::getSelection, + configTable::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .publish(false) @@ -205,8 +209,16 @@ public class SEBExamConfigList implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE) .withSelect( - configTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION), - this.sebExamConfigStateChangePopup.popupCreationFunction(pageContext), + configTable::getMultiSelection, + this.sebExamConfigBatchStateChangePopup.popupCreationFunction(pageContext), + EMPTY_SELECTION_TEXT_KEY) + .noEventPropagation() + .publishIf(() -> examConfigGrant.im(), false) + + .newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE) + .withSelect( + configTable::getMultiSelection, + this.sebExamConfigBatchResetToTemplatePopup.popupCreationFunction(pageContext), EMPTY_SELECTION_TEXT_KEY) .noEventPropagation() .publishIf(() -> examConfigGrant.im(), false) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamFormIndicators.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamFormIndicators.java index 650be970..34915995 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamFormIndicators.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamFormIndicators.java @@ -133,7 +133,7 @@ public class ExamFormIndicators implements TemplateComposer { .newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST) .withParentEntityKey(entityKey) .withSelect( - indicatorTable::getSelection, + indicatorTable::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, INDICATOR_EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> editable && indicatorTable.hasAnyContent(), false) @@ -141,7 +141,7 @@ public class ExamFormIndicators implements TemplateComposer { .newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST) .withEntityKey(entityKey) .withSelect( - indicatorTable::getSelection, + indicatorTable::getMultiSelection, this::deleteSelectedIndicator, INDICATOR_EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> editable && indicatorTable.hasAnyContent(), false) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java index 906446db..e3fb12d1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java @@ -210,7 +210,8 @@ public class ExamList implements TemplateComposer { final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM); actionBuilder .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST) - .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, + EMPTY_SELECTION_TEXT_KEY) .publish(false) .newAction(ActionDefinition.EXAM_MODIFY_FROM_LIST) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java index a047df9d..8bf0fa11 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java @@ -268,7 +268,7 @@ public class ExamTemplateForm implements TemplateComposer { .newAction(ActionDefinition.INDICATOR_TEMPLATE_MODIFY_FROM_LIST) .withParentEntityKey(entityKey) .withSelect( - indicatorTable::getSelection, + indicatorTable::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, INDICATOR_EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false) @@ -276,7 +276,7 @@ public class ExamTemplateForm implements TemplateComposer { .newAction(ActionDefinition.INDICATOR_TEMPLATE_DELETE_FROM_LIST) .withEntityKey(entityKey) .withSelect( - indicatorTable::getSelection, + indicatorTable::getMultiSelection, this::deleteSelectedIndicator, INDICATOR_EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateList.java index 760526b2..a4151cf2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateList.java @@ -175,11 +175,13 @@ public class ExamTemplateList implements TemplateComposer { .publishIf(userGrant::iw) .newAction(ActionDefinition.EXAM_TEMPLATE_VIEW_FROM_LIST) - .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, + EMPTY_SELECTION_TEXT_KEY) .publish(false) .newAction(ActionDefinition.EXAM_TEMPLATE_MODIFY_FROM_LIST) - .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey, + EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> userGrant.im(), false); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/LmsSetupList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/LmsSetupList.java index 02d3ddd7..0f3c0742 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/LmsSetupList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/LmsSetupList.java @@ -169,7 +169,10 @@ public class LmsSetupList implements TemplateComposer { .publishIf(userGrant::iw) .newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST) - .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .withSelect( + table::getMultiSelection, + PageAction::applySingleSelectionAsEntityKey, + EMPTY_SELECTION_TEXT_KEY) .publish(false) .newAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/QuizLookupList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/QuizLookupList.java index 5033ea0d..abcb58d7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/QuizLookupList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/QuizLookupList.java @@ -236,7 +236,7 @@ public class QuizLookupList implements TemplateComposer { actionBuilder .newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS) .withSelect( - table::getSelection, + table::getMultiSelection, action -> this.showDetails( action, table.getSingleSelectedROWData(), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java index 9839d159..5abc050a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java @@ -170,7 +170,10 @@ public class FinishedExam implements TemplateComposer { .newAction(ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION) .withParentEntityKey(examKey) - .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .withSelect( + table::getMultiSelection, + PageAction::applySingleSelectionAsEntityKey, + EMPTY_SELECTION_TEXT_KEY) .publishIf(isExamSupporter, false); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java index ff21ef96..2575d680 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java @@ -141,7 +141,10 @@ public class FinishedExamList implements TemplateComposer { actionBuilder .newAction(ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST) - .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .withSelect( + table::getMultiSelection, + PageAction::applySingleSelectionAsEntityKey, + EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false); } 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 a3b5eb4b..0a9cc71b 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 @@ -264,7 +264,7 @@ public class MonitoringClientConnection implements TemplateComposer { .withParentEntityKey(parentEntityKey) .withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY) .withSelect( - () -> notificationTable.getSelection(), + () -> notificationTable.getMultiSelection(), action -> this.confirmNotification(action, connectionData, notificationTable), NOTIFICATION_LIST_NO_SELECTION_KEY) 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 0a953fd5..48150835 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 @@ -13,6 +13,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.function.BooleanSupplier; +import java.util.function.Consumer; import java.util.function.Function; import org.eclipse.swt.SWT; @@ -172,7 +173,7 @@ public class MonitoringRunningExam implements TemplateComposer { .withParentEntityKey(entityKey) .create(), this.pageService) - .withSelectionListener(this.pageService.getSelectionPublisher( + .withSelectionListener(this.getSelectionPublisherClientConnectionTable( pageContext, ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION, ActionDefinition.MONITOR_EXAM_QUIT_SELECTED, @@ -505,4 +506,13 @@ public class MonitoringRunningExam implements TemplateComposer { return action; } + private Consumer getSelectionPublisherClientConnectionTable( + final PageContext pageContext, + final ActionDefinition... actionDefinitions) { + + return table -> this.pageService.firePageEvent( + new ActionActivationEvent(table.getSingleSelection() != null, actionDefinitions), + pageContext); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExamList.java index be50dd72..5ebdcef8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExamList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExamList.java @@ -141,7 +141,10 @@ public class MonitoringRunningExamList implements TemplateComposer { actionBuilder .newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST) - .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .withSelect( + table::getMultiSelection, + PageAction::applySingleSelectionAsEntityKey, + EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/SEBClientEvents.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/SEBClientEvents.java index b522ac71..083dec9d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/SEBClientEvents.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/SEBClientEvents.java @@ -227,7 +227,7 @@ public class SEBClientEvents implements TemplateComposer { actionBuilder .newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS) .withSelect( - table::getSelection, + table::getMultiSelection, action -> this.sebClientEventDetailsPopup.showDetails(action, table.getSingleSelectedROWData()), EMPTY_SELECTION_TEXT) .noEventPropagation() diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java index 52d688a5..bac021ac 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java @@ -172,7 +172,7 @@ public interface PageService { * @return a message supplier to notify deactivation dependencies to the user */ default Supplier confirmDeactivation(final EntityTable table) { return () -> { - final List multiSelection = table.getMultiSelection(); + final Set multiSelection = table.getMultiSelection(); if (multiSelection.size() > 1) { throw new PageMessageException(MESSAGE_NO_MULTISELECTION); } @@ -204,12 +204,12 @@ public interface PageService { * @param pageContext the current PageContext * @param actionDefinitions list of action definitions that activity should be toggled on table selection * @return the selection publisher that handles this defines action activation on table selection */ - default Consumer> getSelectionPublisher( + default Consumer> getSelectionPublisher( final PageContext pageContext, final ActionDefinition... actionDefinitions) { - return rows -> firePageEvent( - new ActionActivationEvent(!rows.isEmpty(), actionDefinitions), + return table -> firePageEvent( + new ActionActivationEvent(table.hasSelection(), actionDefinitions), pageContext); } @@ -225,15 +225,15 @@ public interface PageService { * @param pageContext the current PageContext * @param actionDefinitions list of action definitions that activity should be toggled on table selection * @return the selection publisher that handles this defines action activation on table selection */ - default Consumer> getSelectionPublisher( + default Consumer> getSelectionPublisher( final ActionDefinition toggle, final ActionDefinition activate, final ActionDefinition deactivate, final PageContext pageContext, final ActionDefinition... actionDefinitions) { - return rows -> { - + return table -> { + final Set rows = table.getPageSelectionData(); if (!rows.isEmpty()) { firePageEvent( new ActionActivationEvent( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java index f86826bb..1c78953e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java @@ -171,7 +171,6 @@ public final class PageAction { // if selection is needed, check selection fist, before confirm dialog if (this.selectionSupplier != null) { getMultiSelection(); - } final LocTextKey confirmMessage = this.confirm.apply(this); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java index 3c9fe53f..8faf5b3b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java @@ -249,7 +249,7 @@ public class PageServiceImpl implements PageService { final Function testBeforeActivation) { return action -> { - final List multiSelection = table.getMultiSelection(); + final Set multiSelection = table.getMultiSelection(); if (multiSelection == null || multiSelection.isEmpty()) { throw new PageMessageException(noSelectionText); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 6a338c88..d8af46af 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -88,7 +88,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private final Table table; private final ColorData colorData; private final Function localizedClientConnectionStatusNameFunction; - private Consumer> selectionListener; + private Consumer selectionListener; private int tableWidth; private boolean needsSort = false; @@ -238,7 +238,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } } - public ClientConnectionTable withSelectionListener(final Consumer> selectionListener) { + public ClientConnectionTable withSelectionListener(final Consumer selectionListener) { this.selectionListener = selectionListener; return this; } @@ -402,7 +402,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate return; } - this.selectionListener.accept(this.getSelection()); + this.selectionListener.accept(this); } private void notifyTableInfoClick(final Event event) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java index da86273c..222dddce 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java @@ -95,7 +95,7 @@ public class EntityTable { private final MultiValueMap staticQueryParams; private final BiConsumer rowDecorator; - private final Consumer> selectionListener; + private final Consumer> selectionListener; private final Consumer contentChangeListener; private final Set multiselection; @@ -125,7 +125,7 @@ public class EntityTable { final boolean hideNavigation, final MultiValueMap staticQueryParams, final BiConsumer rowDecorator, - final Consumer> selectionListener, + final Consumer> selectionListener, final Consumer contentChangeListener, final String defaultSortColumn, final PageSortOrder defaultSortOrder) { @@ -263,6 +263,10 @@ public class EntityTable { return this.table.getItemCount() > 0; } + public boolean hasSelection() { + return (this.multiselection != null && !this.multiselection.isEmpty()) || this.table.getSelectionCount() > 0; + } + public void setPageSize(final int pageSize) { this.pageSize = pageSize; updateTableRows( @@ -363,15 +367,18 @@ public class EntityTable { return getEntityKey(selection[0]); } - public List getMultiSelection() { + public Set getMultiSelection() { if (this.multiselection == null) { - return Collections.emptyList(); + return getPageSelectionData() + .stream() + .map(row -> new EntityKey(row.getModelId(), getEntityType())) + .collect(Collectors.toSet()); } return this.multiselection .stream() .map(modelId -> new EntityKey(modelId, getEntityType())) - .collect(Collectors.toList()); + .collect(Collectors.toSet()); } public ROW getFirstRowData() { @@ -396,7 +403,7 @@ public class EntityTable { return getRowData(selection[0]); } - private Set getPageSelectionData() { + public Set getPageSelectionData() { final TableItem[] selection = this.table.getSelection(); if (selection == null || selection.length == 0) { return Collections.emptySet(); @@ -407,10 +414,6 @@ public class EntityTable { .collect(Collectors.toSet()); } - public Set getSelection() { - return getSelection(null); - } - public Set getSelection(final Predicate grantCheck) { final TableItem[] selection = this.table.getSelection(); if (selection == null) { @@ -685,7 +688,7 @@ public class EntityTable { return; } - this.selectionListener.accept(this.getPageSelectionData()); + this.selectionListener.accept(this); } private void updateCurrentPageAttr() { @@ -807,6 +810,9 @@ public class EntityTable { this.multiselection.remove(modelId); } else { this.multiselection.add(modelId); + Arrays.asList(this.table.getSelection()) + .stream() + .forEach(i -> this.multiselection.add(getModelId(i))); } multiselectFromPage(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java index b15897ff..3542835b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java @@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.gui.table; import java.util.ArrayList; import java.util.List; -import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -48,7 +47,7 @@ public class TableBuilder { private boolean hideNavigation = false; private Function, PageSupplier.Builder> restCallAdapter; private BiConsumer rowDecorator; - private Consumer> selectionListener; + private Consumer> selectionListener; private Consumer contentChangeListener; private boolean markupEnabled = false; private String defaultSortColumn = null; @@ -152,7 +151,7 @@ public class TableBuilder { return this; } - public TableBuilder withSelectionListener(final Consumer> selectionListener) { + public TableBuilder withSelectionListener(final Consumer> selectionListener) { this.selectionListener = selectionListener; return this; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java index eadf45e1..fab53546 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java @@ -201,6 +201,7 @@ public class PaginationServiceImpl implements PaginationService { final PageSortOrder sortOrder = PageSortOrder.getSortOrder(sort); final String sortColumnName = verifySortColumnName(sort, sortMappingName); if (StringUtils.isNotBlank(sortColumnName)) { + startPage.setOrderByOnly(false); switch (sortOrder) { case DESCENDING: { PageHelper.orderBy(sortColumnName + " DESC"); @@ -211,6 +212,7 @@ public class PaginationServiceImpl implements PaginationService { break; } } + PageHelper.orderBy("id"); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/UserService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/UserService.java index a5557dae..14ad2f4d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/UserService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/UserService.java @@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.authorization; import java.security.Principal; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.WebDataBinder; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; @@ -51,4 +52,9 @@ public interface UserService { * @param binder Springs WebDataBinder is injected on controller side */ void addUsersInstitutionDefaultPropertySupport(final WebDataBinder binder); + /** Used to set authentication on different thread. + * + * @param authentication */ + void setAuthenticationIfAbsent(Authentication authentication); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/SEBServerUser.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/SEBServerUser.java index eb1c0595..03e3f0ea 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/SEBServerUser.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/SEBServerUser.java @@ -13,6 +13,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.stream.Collectors; +import org.springframework.security.core.Authentication; import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -26,7 +27,7 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserRole; * * This implements Spring's UserDetails and CredentialsContainer to act as a principal * within internal authentication and authorization processes. */ -public final class SEBServerUser implements UserDetails, CredentialsContainer { +public final class SEBServerUser implements UserDetails, CredentialsContainer, Authentication { private static final long serialVersionUID = 5726250141482925769L; @@ -160,4 +161,34 @@ public final class SEBServerUser implements UserDetails, CredentialsContainer { return new SEBServerUser(user.id, UserInfo.of(user.userInfo), user.password); } + @Override + public String getName() { + return this.userInfo.username; + } + + @Override + public Object getCredentials() { + return this; + } + + @Override + public Object getDetails() { + return this; + } + + @Override + public Object getPrincipal() { + return this; + } + + @Override + public boolean isAuthenticated() { + return isEnabled(); + } + + @Override + public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException { + + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/UserServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/UserServiceImpl.java index 4204ea3a..19cfd637 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/UserServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/UserServiceImpl.java @@ -94,6 +94,13 @@ public class UserServiceImpl implements UserService { binder.registerCustomEditor(Long.class, usersInstitutionDefaultEditor); } + @Override + public void setAuthenticationIfAbsent(final Authentication authentication) { + if (SecurityContextHolder.getContext().getAuthentication() == null) { + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + // 1. OAuth2Authentication strategy @Lazy @Component @@ -115,6 +122,21 @@ public class UserServiceImpl implements UserService { } } + // 2. Separated thread strategy + @Lazy + @Component + public static class OtherThreadUserExtractStrategy implements ExtractUserFromAuthenticationStrategy { + + @Override + public SEBServerUser extract(final Principal principal) { + if (principal instanceof SEBServerUser) { + return (SEBServerUser) principal; + } + + return null; + } + } + private static final SEBServerUser ANONYMOUS_USER = new SEBServerUser( -1L, new UserInfo("SEB_SERVER_ANONYMOUS_USER", -2L, null, "anonymous", "anonymous", "anonymous", null, false, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/BatchActionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/BatchActionServiceImpl.java index d7f3e4a9..42be44b6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/BatchActionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/BatchActionServiceImpl.java @@ -22,6 +22,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import ch.ethz.seb.sebserver.gbl.api.API; @@ -38,6 +40,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionServi import ch.ethz.seb.sebserver.webservice.servicelayer.dao.BatchActionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; 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.dao.UserDAO; @Service @WebServiceProfile @@ -46,17 +50,23 @@ public class BatchActionServiceImpl implements BatchActionService { private static final Logger log = LoggerFactory.getLogger(BatchActionServiceImpl.class); private final BatchActionDAO batchActionDAO; + private final UserDAO userDAO; private final TaskScheduler taskScheduler; + private final UserActivityLogDAO userActivityLogDAO; private final EnumMap batchExecutions; private ScheduledFuture runningBatchProcess = null; public BatchActionServiceImpl( final BatchActionDAO batchActionDAO, + final UserDAO userDAO, + final UserActivityLogDAO userActivityLogDAO, final Collection batchExecutions, final TaskScheduler taskScheduler) { this.batchActionDAO = batchActionDAO; + this.userDAO = userDAO; + this.userActivityLogDAO = userActivityLogDAO; this.taskScheduler = taskScheduler; this.batchExecutions = new EnumMap<>(BatchActionType.class); @@ -158,7 +168,8 @@ public class BatchActionServiceImpl implements BatchActionService { new BatchActionProcess( new BatchActionHandlerImpl(action), this.batchExecutions.get(action.actionType), - action), + action, + getAuthentication(action)), Instant.now()); }) .onError(error -> { @@ -174,22 +185,43 @@ public class BatchActionServiceImpl implements BatchActionService { } } + private Authentication getAuthentication(final BatchAction action) { + try { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + return authentication; + } + + return this.userDAO.byModelId(action.ownerId) + .flatMap(userInfo -> this.userDAO.sebServerUserByUsername(userInfo.username)) + .onError(error -> log.error("Failed to get batch action owner user -> ", error)) + .getOr(null); + + } catch (final Exception e) { + log.error("Failed to get authentication: ", e); + return null; + } + } + private final static class BatchActionProcess implements Runnable { private final BatchActionHandler batchActionHandler; private final BatchActionExec batchActionExec; private final BatchAction batchAction; + private final Authentication authentication; private Set processingIds; public BatchActionProcess( final BatchActionHandler batchActionHandler, final BatchActionExec batchActionExec, - final BatchAction batchAction) { + final BatchAction batchAction, + final Authentication authentication) { this.batchActionHandler = batchActionHandler; this.batchActionExec = batchActionExec; this.batchAction = batchAction; + this.authentication = authentication; } @Override @@ -198,6 +230,13 @@ public class BatchActionServiceImpl implements BatchActionService { log.info("Starting or continuing batch action - {}", this.batchAction); + if (SecurityContextHolder.getContext().getAuthentication() == null) { + if (this.authentication == null) { + throw new IllegalStateException("No authentication found within batch context"); + } + SecurityContextHolder.getContext().setAuthentication(this.authentication); + } + this.processingIds = new HashSet<>(this.batchAction.sourceIds); this.processingIds.removeAll(this.batchAction.successful); @@ -256,10 +295,10 @@ public class BatchActionServiceImpl implements BatchActionService { @Override public void handleError(final String modelId, final Exception error) { log.error( - "Failed to process single entity on batch action. ModelId: {}, action: ", + "Failed to process single entity on batch action. ModelId: {}, action: {}, errorMessage: {}", modelId, this.batchAction, - error); + error.getMessage()); BatchActionServiceImpl.this.batchActionDAO.setFailure( this.batchAction.id, @@ -276,6 +315,10 @@ public class BatchActionServiceImpl implements BatchActionService { .onError(error -> log.error( "Failed to mark batch action as finished: {}", this.batchAction, error)); + + BatchActionServiceImpl.this.batchActionDAO.byPK(this.batchAction.id) + .flatMap(BatchActionServiceImpl.this.userActivityLogDAO::logFinished) + .onError(error -> log.error("Failed to put audit log for batch action finish: ", error)); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigResetToTemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigResetToTemplate.java new file mode 100644 index 00000000..cbbe1477 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigResetToTemplate.java @@ -0,0 +1,86 @@ +/* + * 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.bulkaction.impl; + +import java.util.Map; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType; +import ch.ethz.seb.sebserver.gbl.api.APIMessage; +import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; +import ch.ethz.seb.sebserver.gbl.model.BatchAction; +import ch.ethz.seb.sebserver.gbl.model.Entity; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionExec; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService; + +@Lazy +@Component +@WebServiceProfile +public class ExamConfigResetToTemplate implements BatchActionExec { + + private final ConfigurationNodeDAO configurationNodeDAO; + private final ExamConfigService sebExamConfigService; + private final ExamConfigUpdateService examConfigUpdateService; + private final AuthorizationService authorizationService; + + public ExamConfigResetToTemplate( + final ConfigurationNodeDAO configurationNodeDAO, + final ExamConfigService sebExamConfigService, + final ExamConfigUpdateService examConfigUpdateService, + final AuthorizationService authorizationService) { + + this.configurationNodeDAO = configurationNodeDAO; + this.sebExamConfigService = sebExamConfigService; + this.examConfigUpdateService = examConfigUpdateService; + this.authorizationService = authorizationService; + } + + @Override + public BatchActionType actionType() { + return BatchActionType.EXAM_CONFIG_REST_TEMPLATE_SETTINGS; + } + + @Override + public APIMessage checkConsistency(final Map actionAttributes) { + // no additional check here + return null; + } + + @Override + public Result doSingleAction(final String modelId, final BatchAction batchAction) { + + return this.configurationNodeDAO + .byModelId(modelId) + .flatMap(node -> this.authorizationService.check(PrivilegeType.MODIFY, node)) + .map(this::checkConsistency) + .flatMap(this.sebExamConfigService::resetToTemplateSettings) + .map(node -> { + this.examConfigUpdateService + .processExamConfigurationChange(node.id) + .getOrThrow(); + return node; + }) + .map(Entity::getEntityKey); + + } + + private ConfigurationNode checkConsistency(final ConfigurationNode configurationNode) { + return configurationNode; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/SingleExamConfigStateChange.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigStateChange.java similarity index 83% rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/SingleExamConfigStateChange.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigStateChange.java index b4f07cc9..136280b4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/SingleExamConfigStateChange.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigStateChange.java @@ -21,36 +21,31 @@ import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus; -import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionExec; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService; import io.micrometer.core.instrument.util.StringUtils; @Lazy @Component @WebServiceProfile -public class SingleExamConfigStateChange implements BatchActionExec { +public class ExamConfigStateChange implements BatchActionExec { private final ExamConfigService sebExamConfigService; private final ConfigurationNodeDAO configurationNodeDAO; private final AuthorizationService authorizationService; - private final UserDAO userDAO; - public SingleExamConfigStateChange( + public ExamConfigStateChange( final ExamConfigService sebExamConfigService, final ConfigurationNodeDAO configurationNodeDAO, - final AuthorizationService authorizationService, - final UserDAO userDAO) { + final AuthorizationService authorizationService) { this.sebExamConfigService = sebExamConfigService; this.configurationNodeDAO = configurationNodeDAO; this.authorizationService = authorizationService; - this.userDAO = userDAO; } @Override @@ -71,13 +66,9 @@ public class SingleExamConfigStateChange implements BatchActionExec { @Override public Result doSingleAction(final String modelId, final BatchAction batchAction) { - final UserInfo user = this.userDAO - .byModelId(batchAction.ownerId) - .getOrThrow(); - return this.configurationNodeDAO .byModelId(modelId) - .map(node -> this.authorizationService.check(PrivilegeType.MODIFY, user, node)) + .flatMap(node -> this.authorizationService.check(PrivilegeType.MODIFY, node)) .map(node -> new ConfigurationNode( node.id, null, null, null, null, null, null, getTargetState(batchAction.attributes))) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java index 7d432e16..f11ddbc9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java @@ -77,6 +77,12 @@ public interface UserActivityLogDAO extends * @return Result of the Entity or referring to an Error if happened */ Result logModify(E entity); + /** Create a user activity log entry for the current user of activity type FINISHED + * + * @param entity the Entity + * @return Result of the Entity or referring to an Error if happened */ + Result logFinished(E entity); + /** Create a user activity log entry for the current user of activity type DELETE * * @param entity the Entity diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java index 23c6f586..66cb963d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java @@ -160,6 +160,11 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO { return log(UserLogActivityType.MODIFY, entity); } + @Override + public Result logFinished(final E entity) { + return log(UserLogActivityType.FINISHED, entity); + } + @Override @Transactional public Result logDelete(final E entity) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java index bccbd4d1..58901a00 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java @@ -14,6 +14,7 @@ import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.Future; import java.util.stream.Collectors; @@ -414,6 +415,18 @@ public class ExamConfigServiceImpl implements ExamConfigService { "The Type of ConfigurationNode cannot change after creation"); } + // if configuration is in use, "Ready to Use" is not possible + if (configurationNode.status == ConfigurationStatus.READY_TO_USE) { + if (!this.examConfigurationMapDAO + .getExamIdsForConfigNodeId(configurationNode.id) + .getOr(Collections.emptyList()) + .isEmpty()) { + throw new APIMessageException( + APIMessage.ErrorMessage.INTEGRITY_VALIDATION + .of("Exam configuration has references to at least one exam.")); + } + } + // if changing to archived check possibility if (configurationNode.status == ConfigurationStatus.ARCHIVED) { if (existingNode.status != ConfigurationStatus.ARCHIVED) { diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index ceb9617f..d33e13ee 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -49,6 +49,7 @@ sebserver.overall.types.activityType.ACTIVATE=Activate sebserver.overall.types.activityType.DELETE=Delete sebserver.overall.types.activityType.LOGIN=Login sebserver.overall.types.activityType.LOGOUT=Logout +sebserver.overall.types.activityType.FINISHED=Finished sebserver.overall.types.entityType.CONFIGURATION_ATTRIBUTE=Configuration Attribute sebserver.overall.types.entityType.CONFIGURATION_VALUE=Configuration Value @@ -70,6 +71,7 @@ sebserver.overall.types.entityType.EXAM_SEB_RESTRICTION=SEB Exam Restriction sebserver.overall.types.entityType.CERTIFICATE=Certificate sebserver.overall.types.entityType.EXAM_TEMPLATE=Exam Template sebserver.overall.types.entityType.EXAM_PROCTOR_DATA=Exam Proctoring Settings +sebserver.overall.types.entityType.BATCH_ACTION=Batch Action sebserver.overall.activity.title.serveradmin=SEB Server Administration sebserver.overall.activity.title.sebconfig=Configurations @@ -814,13 +816,16 @@ sebserver.examconfig.list.action.no.modify.privilege=No Access: An Exam Configur sebserver.examconfig.action.list.new=Add Exam Configuration sebserver.examconfig.action.list.view=View Exam Configuration sebserver.examconfig.list.action.statechange=State Change -sebserver.examconfig.list.batch.statechange.title=Bulk State Change +sebserver.examconfig.list.batch.statechange.title=State Change All sebserver.examconfig.list.batch.action.statechange=Change States sebserver.examconfig.list.batch.progress=Bulk State Change in Progress: {0}% sebserver.examconfig.list.batch.finished=Bulk State Change finished. Successful: {0}.Failed: {1} sebserver.examconfig.list.batch.action.statechange.info=This action changes all selected exam configurations to a defined target state.
Please note that this will be done only for those that can be changed to the defined target state in regard to its current state. sebserver.examconfig.list.batch.action.status=Target Status - +sebserver.examconfig.list.action.reset=Reset To Template Settings +sebserver.examconfig.list.batch.reset.title=Reset To Template Settings +sebserver.examconfig.list.batch.action.reset=Reset All +sebserver.examconfig.list.batch.action.reset.info=This action process a reset of the SEB settings to its template settings for all selected configurations.
Please note this will be done only for those that has an origin template and are not in state "Used".
Please note also that this action tries to directly publish the changes and if not possible (active SEB client connections) it will report an error even if the settings has been reseted. sebserver.examconfig.action.list.modify.properties=Edit Exam Configuration sebserver.examconfig.action.delete=Delete Exam Configuration diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/AdministrationAPIIntegrationTester.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/AdministrationAPIIntegrationTester.java index 5722dd74..b053c2d8 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/AdministrationAPIIntegrationTester.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/AdministrationAPIIntegrationTester.java @@ -15,6 +15,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -248,11 +249,12 @@ public abstract class AdministrationAPIIntegrationTester { } protected String getOrderedUUIDs(final Collection list) { - return list + final List l = list .stream() .map(userInfo -> userInfo.getModelId()) - .collect(Collectors.toList()) - .toString(); + .collect(Collectors.toList()); + l.sort((s1, s2) -> s1.compareTo(s2)); + return l.toString(); } } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java index 5d3b8495..22aef9ec 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java @@ -268,7 +268,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { assertTrue(userInfos.numberOfPages == 1); assertNotNull(userInfos.content); assertTrue(userInfos.content.size() == 3); - assertEquals("[user5, user2, user1]", getOrderedUUIDs(userInfos.content)); + assertEquals("[user1, user2, user5]", getOrderedUUIDs(userInfos.content)); } @Test @@ -347,7 +347,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { assertTrue(userInfos.numberOfPages == 2); assertNotNull(userInfos.content); assertTrue(userInfos.content.size() == 3); - assertEquals("[user7, user6, user4]", getOrderedUUIDs(userInfos.content)); + assertEquals("[user3, user4, user6]", getOrderedUUIDs(userInfos.content)); } @Test