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 3f824401..4f2c7bcd 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 @@ -81,6 +81,8 @@ public final class API { public static final String TOGGLE_ACTIVITY_PATH_SEGMENT = "/toggle-activity"; public static final String INACTIVE_PATH_SEGMENT = "/inactive"; + public static final String FORCE_PATH_SEGMENT = "/force"; + public static final String DEPENDENCY_PATH_SEGMENT = "/dependency"; public static final String PASSWORD_PATH_SEGMENT = "/password"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamDeletePopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamDeletePopup.java index c039a91d..ba3938bb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamDeletePopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamDeletePopup.java @@ -16,6 +16,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ForceDeleteExam; import org.apache.commons.text.StringEscapeUtils; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; @@ -62,28 +63,19 @@ public class ExamDeletePopup { private static final Logger log = LoggerFactory.getLogger(ExamDeletePopup.class); - private final static LocTextKey FORM_TITLE = - new LocTextKey("sebserver.exam.delete.form.title"); - private final static LocTextKey FORM_INFO = - new LocTextKey("sebserver.exam.delete.form.info"); - private final static LocTextKey FORM_REPORT_INFO = - new LocTextKey("sebserver.exam.delete.report.info"); - private final static LocTextKey FORM_REPORT_LIST_TYPE = - new LocTextKey("sebserver.exam.delete.report.list.type"); - private final static LocTextKey FORM_REPORT_LIST_NAME = - new LocTextKey("sebserver.exam.delete.report.list.name"); - private final static LocTextKey FORM_REPORT_LIST_DESC = - new LocTextKey("sebserver.exam.delete.report.list.description"); - private final static LocTextKey FORM_REPORT_NONE = - new LocTextKey("sebserver.exam.delete.report.list.empty"); - - private final static LocTextKey ACTION_DELETE = - new LocTextKey("sebserver.exam.delete.action.delete"); - - private final static LocTextKey DELETE_CONFIRM_TITLE = - new LocTextKey("sebserver.exam.delete.confirm.title"); - private final static LocTextKey DELETE_ERROR_CONSISTENCY = - new LocTextKey("sebserver.exam.action.delete.consistency.error"); + private final static LocTextKey FORM_TITLE = new LocTextKey("sebserver.exam.delete.form.title"); + private final static LocTextKey FORM_INFO = new LocTextKey("sebserver.exam.delete.form.info"); + private final static LocTextKey FORM_REPORT_INFO = new LocTextKey("sebserver.exam.delete.report.info"); + private final static LocTextKey FORM_REPORT_LIST_TYPE = new LocTextKey("sebserver.exam.delete.report.list.type"); + private final static LocTextKey FORM_REPORT_LIST_NAME = new LocTextKey("sebserver.exam.delete.report.list.name"); + private final static LocTextKey FORM_REPORT_LIST_DESC = new LocTextKey("sebserver.exam.delete.report.list.description"); + private final static LocTextKey FORM_REPORT_NONE = new LocTextKey("sebserver.exam.delete.report.list.empty"); + private final static LocTextKey ACTION_DELETE = new LocTextKey("sebserver.exam.delete.action.delete"); + private final static LocTextKey DELETE_CONFIRM_TITLE = new LocTextKey("sebserver.exam.delete.confirm.title"); + private final static LocTextKey DELETE_ERROR_CONSISTENCY = new LocTextKey("sebserver.exam.action.delete.consistency.error"); + private final static LocTextKey FORCE_DELETE_CONFIRM_TITLE = new LocTextKey("sebserver.exam.action.delete.force.confirm.title"); + private final static LocTextKey FORCE_DELETE_CONFIRM_ACTION = new LocTextKey("sebserver.exam.action.delete.force.confirm.action"); + private final static LocTextKey FORCE_DELETE_CONFIRM = new LocTextKey("sebserver.exam.action.delete.force.confirm"); private final PageService pageService; @@ -131,12 +123,12 @@ public class ExamDeletePopup { .call() .getOrThrow(); - final RestCall.RestCallBuilder restCallBuilder = this.pageService.getRestService() + final Result deleteCall = this.pageService.getRestService() .getBuilder(DeleteExam.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) - .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name()); + .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name()) + .call(); - final Result deleteCall = restCallBuilder.call(); if (deleteCall.hasError()) { final Exception error = deleteCall.getError(); if (error instanceof RestCallError) { @@ -146,33 +138,33 @@ public class ExamDeletePopup { .findFirst() .orElse(null); if (message != null && ErrorMessage.INTEGRITY_VALIDATION.isOf(message)) { - pageContext.publishPageMessage(new PageMessageException(DELETE_ERROR_CONSISTENCY)); - return false; + pageContext.applyConfirmDialog( + FORCE_DELETE_CONFIRM_TITLE, + FORCE_DELETE_CONFIRM, + FORCE_DELETE_CONFIRM_ACTION, + confirm -> { + if (confirm) { + final Result forceDeleteCall = this.pageService.getRestService() + .getBuilder(ForceDeleteExam.class) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name()) + .call(); + + if (forceDeleteCall.hasError()) { + pageContext.notifyUnexpectedError(forceDeleteCall.getError()); + } else { + showSuccessDialog(pageContext, examToDelete, forceDeleteCall.getOrThrow(), entityKey); + } + } + } + ); + return true; } } } - final EntityProcessingReport report = deleteCall.getOrThrow(); + showSuccessDialog(pageContext, examToDelete, deleteCall.getOrThrow(), entityKey); - final PageAction action = this.pageService.pageActionBuilder(pageContext) - .newAction(ActionDefinition.EXAM_VIEW_LIST) - .create(); - - this.pageService.firePageEvent( - new ActionEvent(action), - action.pageContext()); - - final String examName = StringEscapeUtils.escapeXml11(examToDelete.toName().name); - final List dependencies = report.results.stream() - .filter(key -> !key.equals(entityKey)) - .collect(Collectors.toList()); - pageContext.publishPageMessage( - DELETE_CONFIRM_TITLE, - new LocTextKey( - "sebserver.exam.delete.confirm.message", - examName, - dependencies.size(), - (report.errors.isEmpty()) ? "no" : String.valueOf((report.errors.size())))); return true; } catch (final Exception e) { log.error("Unexpected error while trying to delete Exam:", e); @@ -181,6 +173,34 @@ public class ExamDeletePopup { } } + private void showSuccessDialog( + final PageContext pageContext, + final Exam examToDelete, + final EntityProcessingReport report, + final EntityKey entityKey) { + + final PageAction action = this.pageService.pageActionBuilder(pageContext) + .newAction(ActionDefinition.EXAM_VIEW_LIST) + .create(); + + this.pageService.firePageEvent( + new ActionEvent(action), + action.pageContext()); + + final String examName = StringEscapeUtils.escapeXml11(examToDelete.toName().name); + final List dependencies = report.results.stream() + .filter(key -> !key.equals(entityKey)) + .collect(Collectors.toList()); + + pageContext.publishPageMessage( + DELETE_CONFIRM_TITLE, + new LocTextKey( + "sebserver.exam.delete.confirm.message", + examName, + dependencies.size(), + (report.errors.isEmpty()) ? "no" : String.valueOf((report.errors.size())))); + } + private Supplier composeDeleteDialog( final Composite parent, final PageContext pageContext) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java index d6a461cd..cd62014c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java @@ -200,6 +200,19 @@ public interface PageContext { * @param callback callback code block that will be called on users selection */ void applyConfirmDialog(LocTextKey confirmMessage, final Consumer callback); + /** Apply a confirm dialog with a specified confirm message and a callback code + * block that will be executed on users OK selection. + * + * @param confirmTitle the title of the confirm message dialog + * @param confirmMessage the localized confirm message key + * @param confirmButtonText The text for the confirm button to display + * @param callback callback code block that will be called on users selection */ + void applyConfirmDialog( + LocTextKey confirmTitle, + LocTextKey confirmMessage, + LocTextKey confirmButtonText, + Consumer callback); + /** This can be used to forward to a defined page. * * @param pageDefinition the defined page */ diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java index 50007b98..7db0b86c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java @@ -64,18 +64,20 @@ public class ComposerServiceImpl implements ComposerService { private final Class mainPageType = DefaultMainPage.class; final AuthorizationContextHolder authorizationContextHolder; + private final PageService pageService; private final I18nSupport i18nSupport; private final Map composer; private final Map pages; public ComposerServiceImpl( final AuthorizationContextHolder authorizationContextHolder, - final I18nSupport i18nSupport, + final PageService pageService, final Collection composer, final Collection pageDefinitions) { this.authorizationContextHolder = authorizationContextHolder; - this.i18nSupport = i18nSupport; + this.pageService = pageService; + this.i18nSupport = pageService.getI18nSupport(); this.composer = composer .stream() .collect(Collectors.toMap( @@ -233,7 +235,7 @@ public class ComposerServiceImpl implements ComposerService { } private PageContext createPageContext(final Composite root) { - return new PageContextImpl(this.i18nSupport, this, root, root, null); + return new PageContextImpl(this.pageService, this, root, root, null); } } 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 9b8aef74..e9a23db6 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 @@ -184,10 +184,8 @@ public final class PageAction { } } catch (final PageMessageException pme) { PageAction.this.pageContext.publishPageMessage(pme); - return; } catch (final Exception e) { this.pageContext.notifyUnexpectedError(e); - return; } } else { callback.accept(exec()); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java index ff12ffb3..2e9f4db2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java @@ -14,16 +14,18 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; +import ch.ethz.seb.sebserver.gui.service.page.*; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.widgets.DialogCallback; import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.MessageBox; -import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,10 +37,6 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; -import ch.ethz.seb.sebserver.gui.service.page.ComposerService; -import ch.ethz.seb.sebserver.gui.service.page.PageContext; -import ch.ethz.seb.sebserver.gui.service.page.PageDefinition; -import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; import ch.ethz.seb.sebserver.gui.widget.Message; public class PageContextImpl implements PageContext { @@ -48,6 +46,7 @@ public class PageContextImpl implements PageContext { private static final LocTextKey UNEXPECTED_ERROR = new LocTextKey("sebserver.error.unexpected"); private static final String ENTITY_LIST_TYPE = null; + private final PageService pageService; private final I18nSupport i18nSupport; private final ComposerService composerService; private final Composite root; @@ -55,13 +54,14 @@ public class PageContextImpl implements PageContext { private final Map attributes; PageContextImpl( - final I18nSupport i18nSupport, + final PageService pageService, final ComposerService composerService, final Composite root, final Composite parent, final Map attributes) { - this.i18nSupport = i18nSupport; + this.pageService = pageService; + this.i18nSupport = pageService.getI18nSupport(); this.composerService = composerService; this.root = root; this.parent = parent; @@ -105,7 +105,7 @@ public class PageContextImpl implements PageContext { @Override public PageContext copyOf(final Composite parent) { return new PageContextImpl( - this.i18nSupport, + this.pageService, this.composerService, this.root, parent, @@ -118,7 +118,7 @@ public class PageContextImpl implements PageContext { attrs.putAll(this.attributes); attrs.putAll(((PageContextImpl) otherContext).attributes); return new PageContextImpl( - this.i18nSupport, + this.pageService, this.composerService, this.root, this.parent, @@ -130,7 +130,7 @@ public class PageContextImpl implements PageContext { final Map attrs = new HashMap<>(this.attributes); attrs.put(key, value); return new PageContextImpl( - this.i18nSupport, + this.pageService, this.composerService, this.root, this.parent, @@ -238,7 +238,7 @@ public class PageContextImpl implements PageContext { final Map attrs = new HashMap<>(this.attributes); attrs.remove(name); return new PageContextImpl( - this.i18nSupport, + this.pageService, this.composerService, this.root, this.parent, @@ -248,7 +248,7 @@ public class PageContextImpl implements PageContext { @Override public PageContext clearAttributes() { return new PageContextImpl( - this.i18nSupport, + this.pageService, this.composerService, this.root, this.parent, @@ -267,6 +267,36 @@ public class PageContextImpl implements PageContext { messageBox.open(new ConfirmDialogCallback(callback)); } + @Override + public void applyConfirmDialog( + final LocTextKey confirmTitle, + final LocTextKey confirmMessage, + final LocTextKey confirmButtonText, + final Consumer callback) { + + final ModalInputDialog dialog = new ModalInputDialog<>( + this.root.getShell(), + this.pageService.getWidgetFactory()); + dialog.setDialogWidth(500); + + + final ModalInputDialogComposer contentComposer = comp -> { + this.pageService.getWidgetFactory().labelLocalized(comp, confirmMessage, true); + return () -> (Void) null; + }; + final BiConsumer> actionComposer = (actionsComp, value) -> { + final Button confirm = this.pageService.getWidgetFactory() + .buttonLocalized(actionsComp, confirmButtonText); + + confirm.addListener(SWT.Selection, event -> { + dialog.close(); + callback.accept(true); + }); + }; + + dialog.openWithActions(confirmTitle, actionComposer, () -> callback.accept(false), contentComposer); + } + @Override public void forwardToPage(final PageDefinition pageDefinition) { this.composerService.compose( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ForceDeleteExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ForceDeleteExam.java new file mode 100644 index 00000000..296a9ae1 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ForceDeleteExam.java @@ -0,0 +1,30 @@ +package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; +import com.fasterxml.jackson.core.type.TypeReference; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +@Lazy +@Component +@GuiProfile +public class ForceDeleteExam extends RestCall { + + public ForceDeleteExam() { + super(new TypeKey<>( + CallType.DELETE, + EntityType.EXAM, + new TypeReference() { + }), + HttpMethod.DELETE, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_ADMINISTRATION_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT + API.FORCE_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java index 9f4fa0d7..47b528f9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java @@ -439,7 +439,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate { if (protectedRun.hasError()) { final Throwable cause = protectedRun.getError().getCause(); - if (cause.getMessage().contains("LMS Warnings")) { + if (cause != null && cause.getMessage().contains("LMS Warnings")) { return Result.ofRuntimeError(cause.getMessage()); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java index 4481ccc9..9366b188 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java @@ -49,7 +49,6 @@ public class SEBClientEventBatchService { private static final Logger log = LoggerFactory.getLogger(SEBClientEventBatchService.class); private final SEBClientNotificationService sebClientNotificationService; - private final SqlSessionFactory sqlSessionFactory; private final TransactionTemplate transactionTemplate; private final ExamSessionCacheService examSessionCacheService; private final JSONMapper jsonMapper; @@ -65,13 +64,12 @@ public class SEBClientEventBatchService { final JSONMapper jsonMapper) { this.sebClientNotificationService = sebClientNotificationService; - this.sqlSessionFactory = sqlSessionFactory; this.transactionTemplate = new TransactionTemplate(transactionManager); this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); this.examSessionCacheService = examSessionCacheService; this.jsonMapper = jsonMapper; - this.sqlSessionTemplate = new SqlSessionTemplate(this.sqlSessionFactory, ExecutorType.BATCH); + this.sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH); this.clientEventMapper = this.sqlSessionTemplate.getMapper(ClientEventRecordMapper.class); } @@ -172,7 +170,7 @@ public class SEBClientEventBatchService { this.sqlSessionTemplate.flushStatements(); - if (log.isDebugEnabled()) { + if (log.isTraceEnabled()) { log.debug("SEBClientEventBatchService worker {} processes batch of size {} in {} ms", workerName, size, @@ -227,7 +225,7 @@ public class SEBClientEventBatchService { eventData.event.eventType, eventData.event.getClientTime(), eventData.event.getServerTime(), - (eventData.event.numValue != null) ? eventData.event.numValue.doubleValue() : null, + (eventData.event.numValue != null) ? eventData.event.numValue : null, typeAndPlainText.b, typeAndPlainText.a); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java index 2b04ba32..207e97df 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; +import ch.ethz.seb.sebserver.gbl.model.*; import org.apache.commons.lang3.StringUtils; import org.mybatis.dynamic.sql.SqlTable; import org.slf4j.Logger; @@ -38,13 +39,6 @@ import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; -import ch.ethz.seb.sebserver.gbl.model.Entity; -import ch.ethz.seb.sebserver.gbl.model.EntityDependency; -import ch.ethz.seb.sebserver.gbl.model.EntityKey; -import ch.ethz.seb.sebserver.gbl.model.EntityName; -import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; -import ch.ethz.seb.sebserver.gbl.model.GrantEntity; -import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.util.Pair; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; @@ -97,8 +91,8 @@ public abstract class EntityController { /** This is called by Spring to initialize the WebDataBinder and is used here to * initialize the default value binding for the institutionId request-parameter - * that has the current users insitutionId as default. - * + * that has the current users institutionId as default. + *

* See also UserService.addUsersInstitutionDefaultPropertySupport */ @InitBinder public void initBinder(final WebDataBinder binder) { @@ -293,10 +287,10 @@ public abstract class EntityController { + "This is the name of the enumeration "), @Parameter( name = API.PARAM_BULK_ACTION_ADD_INCLUDES, - description = "Indicates if the following 'includes' paramerer shall be processed or not.\n The default is false "), + description = "Indicates if the following 'includes' parameter shall be processed or not.\n The default is false "), @Parameter( name = API.PARAM_BULK_ACTION_INCLUDES, - description = "A comma separated list of names of the EntityType enummeration that defines all entity types that shall be included in the result.") + description = "A comma separated list of names of the EntityType enumeration that defines all entity types that shall be included in the result.") }) @RequestMapping( path = API.MODEL_ID_VAR_PATH_SEGMENT + API.DEPENDENCY_PATH_SEGMENT, @@ -466,7 +460,7 @@ public abstract class EntityController { summary = "Deletes a single entity (and all its dependencies) by its modelId.", description = "To check or report what dependent object also would be deleted for a certain entity object, " + - "please use the dependency endpoint to get a report of all dependend entity objects.", + "please use the dependency endpoint to get a report of all dependent entity objects.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( content = { @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) }), parameters = { @@ -476,10 +470,10 @@ public abstract class EntityController { in = ParameterIn.PATH), @Parameter( name = API.PARAM_BULK_ACTION_ADD_INCLUDES, - description = "Indicates if the following 'includes' paramerer shall be processed or not.\n The default is false "), + description = "Indicates if the following 'includes' parameter shall be processed or not.\n The default is false "), @Parameter( name = API.PARAM_BULK_ACTION_INCLUDES, - description = "A comma separated list of names of the EntityType enummeration that defines all entity types that shall be included in the result.") + description = "A comma separated list of names of the EntityType enumeration that defines all entity types that shall be included in the result.") }) @RequestMapping( path = API.MODEL_ID_VAR_PATH_SEGMENT, @@ -500,6 +494,43 @@ public abstract class EntityController { .getOrThrow(); } + @Operation( + summary = "Force deletes a single entity (and all its dependencies) by its modelId.", + description = "To check or report what dependent object also would be deleted for a certain entity object, " + + + "please use the dependency endpoint to get a report of all dependent entity objects.", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + content = { @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) }), + parameters = { + @Parameter( + name = API.PARAM_MODEL_ID, + description = "The model identifier of the entity object to get.", + in = ParameterIn.PATH), + @Parameter( + name = API.PARAM_BULK_ACTION_ADD_INCLUDES, + description = "Indicates if the following 'includes' parameter shall be processed or not.\n The default is false "), + @Parameter( + name = API.PARAM_BULK_ACTION_INCLUDES, + description = "A comma separated list of names of the EntityType enumeration that defines all entity types that shall be included in the result.") + }) + @RequestMapping( + path = API.MODEL_ID_VAR_PATH_SEGMENT + API.FORCE_PATH_SEGMENT, + method = RequestMethod.DELETE, + produces = MediaType.APPLICATION_JSON_VALUE) + public EntityProcessingReport forceHardDelete( + @PathVariable final String modelId, + @RequestParam(name = API.PARAM_BULK_ACTION_ADD_INCLUDES, defaultValue = "false") final boolean addIncludes, + @RequestParam(name = API.PARAM_BULK_ACTION_INCLUDES, required = false) final List includes) { + + return this.entityDAO.byModelId(modelId) + .flatMap(this::checkWriteAccess) + .flatMap(this::logDelete) + .flatMap(entity -> bulkDelete(entity, convertToEntityType(addIncludes, includes))) + .flatMap(this::notifyDeleted) + .flatMap(pair -> this.logBulkAction(pair.b)) + .getOrThrow(); + } + // ************************** // * DELETE ALL (hard-delete) // ************************** @@ -508,7 +539,7 @@ public abstract class EntityController { summary = "Deletes all given entity (and all its dependencies) by a given list of model identifiers.", description = "To check or report what dependent object also would be deleted for a certain entity object, " + - "please use the dependency endpoint to get a report of all dependend entity objects.", + "please use the dependency endpoint to get a report of all dependent entity objects.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( content = { @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) }), parameters = { @@ -521,7 +552,7 @@ public abstract class EntityController { description = "Indicates if the following 'includes' paramerer shall be processed or not.\n The default is false "), @Parameter( name = API.PARAM_BULK_ACTION_INCLUDES, - description = "A comma separated list of names of the EntityType enummeration that defines all entity types that shall be included in the result.") + description = "A comma separated list of names of the EntityType enumeration that defines all entity types that shall be included in the result.") }) @RequestMapping( method = RequestMethod.DELETE, @@ -547,11 +578,11 @@ public abstract class EntityController { final Collection sources = ids.stream() .map(id -> { return this.entityDAO.byModelId(id) - .flatMap(exam -> this.validForDelete(exam)) + .flatMap(this::validForDelete) .getOr(null); }) .filter(Objects::nonNull) - .map(exam -> exam.getModelId()) + .map(ModelIdAware::getModelId) .map(id -> new EntityKey(id, entityType)) .collect(Collectors.toList()); @@ -756,9 +787,9 @@ public abstract class EntityController { } /** Get the EntityType of the GrantEntity that is used for grant checks of the concrete Entity. - * + *

* NOTE: override this if the EntityType of the GrantEntity is not the same as the Entity itself. - * For example, the Exam is the GrantEntity of a Indicator + * For example, the Exam is the GrantEntity of an Indicator * * @return the EntityType of the GrantEntity that is used for grant checks of the concrete Entity */ protected EntityType getGrantEntityType() { diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 0bc88f41..ccb0540f 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -554,6 +554,9 @@ sebserver.exam.action.activate=Activate Exam sebserver.exam.action.deactivate=Deactivate Exam sebserver.exam.action.delete=Delete Exam sebserver.exam.action.delete.consistency.error=Deletion Failed.
Please make sure there are no active SEB clients connected to the exam before deletion. +sebserver.exam.action.delete.force.confirm.title=Deletion Failed due to condition violation +sebserver.exam.action.delete.force.confirm.action=Force Delete +sebserver.exam.action.delete.force.confirm=There are still some active SEB client connections for this exam.

Please use "Force Delete" to delete anyway or "Cancel" to aboard deletion. sebserver.exam.action.archive=Archive sebserver.exam.action.archive.confirm=An archived exam cannot be rerun and will remain in read-only view.

Are you sure to archive the exam? sebserver.exam.action.sebrestriction.enable=Apply SEB Lock