SEBSERV-462 implement force delete

This commit is contained in:
anhefti 2023-12-19 10:15:06 +01:00
parent 8484e2f6b5
commit ea94adf29b
11 changed files with 219 additions and 92 deletions

View file

@ -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";

View file

@ -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<EntityProcessingReport>.RestCallBuilder restCallBuilder = this.pageService.getRestService()
final Result<EntityProcessingReport> 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<EntityProcessingReport> 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<EntityProcessingReport> 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<EntityKey> 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<EntityKey> 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<PageContext> composeDeleteDialog(
final Composite parent,
final PageContext pageContext) {

View file

@ -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<Boolean> 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<Boolean> callback);
/** This can be used to forward to a defined page.
*
* @param pageDefinition the defined page */

View file

@ -64,18 +64,20 @@ public class ComposerServiceImpl implements ComposerService {
private final Class<? extends PageDefinition> mainPageType = DefaultMainPage.class;
final AuthorizationContextHolder authorizationContextHolder;
private final PageService pageService;
private final I18nSupport i18nSupport;
private final Map<String, TemplateComposer> composer;
private final Map<String, PageDefinition> pages;
public ComposerServiceImpl(
final AuthorizationContextHolder authorizationContextHolder,
final I18nSupport i18nSupport,
final PageService pageService,
final Collection<TemplateComposer> composer,
final Collection<PageDefinition> 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);
}
}

View file

@ -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());

View file

@ -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<String, String> attributes;
PageContextImpl(
final I18nSupport i18nSupport,
final PageService pageService,
final ComposerService composerService,
final Composite root,
final Composite parent,
final Map<String, String> 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<String, String> 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<String, String> 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<Boolean> callback) {
final ModalInputDialog<Void> dialog = new ModalInputDialog<>(
this.root.getShell(),
this.pageService.getWidgetFactory());
dialog.setDialogWidth(500);
final ModalInputDialogComposer<Void> contentComposer = comp -> {
this.pageService.getWidgetFactory().labelLocalized(comp, confirmMessage, true);
return () -> (Void) null;
};
final BiConsumer<Composite, Supplier<Void>> 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(

View file

@ -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<EntityProcessingReport> {
public ForceDeleteExam() {
super(new TypeKey<>(
CallType.DELETE,
EntityType.EXAM,
new TypeReference<EntityProcessingReport>() {
}),
HttpMethod.DELETE,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_ADMINISTRATION_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT + API.FORCE_PATH_SEGMENT);
}
}

View file

@ -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());
}
}

View file

@ -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);

View file

@ -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<T extends Entity, M extends Entity> {
/** 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.
* <p>
* See also UserService.addUsersInstitutionDefaultPropertySupport */
@InitBinder
public void initBinder(final WebDataBinder binder) {
@ -293,10 +287,10 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
+ "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<T extends Entity, M extends Entity> {
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<T extends Entity, M extends Entity> {
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<T extends Entity, M extends Entity> {
.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<String> 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<T extends Entity, M extends Entity> {
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<T extends Entity, M extends Entity> {
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<T extends Entity, M extends Entity> {
final Collection<EntityKey> 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<T extends Entity, M extends Entity> {
}
/** Get the EntityType of the GrantEntity that is used for grant checks of the concrete Entity.
*
* <p>
* 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() {

View file

@ -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.<br/>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.<br/><br/>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.<br/><br/>Are you sure to archive the exam?
sebserver.exam.action.sebrestriction.enable=Apply SEB Lock