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 TOGGLE_ACTIVITY_PATH_SEGMENT = "/toggle-activity";
public static final String INACTIVE_PATH_SEGMENT = "/inactive"; 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 DEPENDENCY_PATH_SEGMENT = "/dependency";
public static final String PASSWORD_PATH_SEGMENT = "/password"; 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.function.Supplier;
import java.util.stream.Collectors; 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.apache.commons.text.StringEscapeUtils;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
@ -62,28 +63,19 @@ public class ExamDeletePopup {
private static final Logger log = LoggerFactory.getLogger(ExamDeletePopup.class); private static final Logger log = LoggerFactory.getLogger(ExamDeletePopup.class);
private final static LocTextKey FORM_TITLE = private final static LocTextKey FORM_TITLE = new LocTextKey("sebserver.exam.delete.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_INFO = private final static LocTextKey FORM_REPORT_INFO = new LocTextKey("sebserver.exam.delete.report.info");
new LocTextKey("sebserver.exam.delete.form.info"); private final static LocTextKey FORM_REPORT_LIST_TYPE = new LocTextKey("sebserver.exam.delete.report.list.type");
private final static LocTextKey FORM_REPORT_INFO = private final static LocTextKey FORM_REPORT_LIST_NAME = new LocTextKey("sebserver.exam.delete.report.list.name");
new LocTextKey("sebserver.exam.delete.report.info"); private final static LocTextKey FORM_REPORT_LIST_DESC = new LocTextKey("sebserver.exam.delete.report.list.description");
private final static LocTextKey FORM_REPORT_LIST_TYPE = private final static LocTextKey FORM_REPORT_NONE = new LocTextKey("sebserver.exam.delete.report.list.empty");
new LocTextKey("sebserver.exam.delete.report.list.type"); private final static LocTextKey ACTION_DELETE = new LocTextKey("sebserver.exam.delete.action.delete");
private final static LocTextKey FORM_REPORT_LIST_NAME = private final static LocTextKey DELETE_CONFIRM_TITLE = new LocTextKey("sebserver.exam.delete.confirm.title");
new LocTextKey("sebserver.exam.delete.report.list.name"); private final static LocTextKey DELETE_ERROR_CONSISTENCY = new LocTextKey("sebserver.exam.action.delete.consistency.error");
private final static LocTextKey FORM_REPORT_LIST_DESC = private final static LocTextKey FORCE_DELETE_CONFIRM_TITLE = new LocTextKey("sebserver.exam.action.delete.force.confirm.title");
new LocTextKey("sebserver.exam.delete.report.list.description"); private final static LocTextKey FORCE_DELETE_CONFIRM_ACTION = new LocTextKey("sebserver.exam.action.delete.force.confirm.action");
private final static LocTextKey FORM_REPORT_NONE = private final static LocTextKey FORCE_DELETE_CONFIRM = new LocTextKey("sebserver.exam.action.delete.force.confirm");
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 PageService pageService; private final PageService pageService;
@ -131,12 +123,12 @@ public class ExamDeletePopup {
.call() .call()
.getOrThrow(); .getOrThrow();
final RestCall<EntityProcessingReport>.RestCallBuilder restCallBuilder = this.pageService.getRestService() final Result<EntityProcessingReport> deleteCall = this.pageService.getRestService()
.getBuilder(DeleteExam.class) .getBuilder(DeleteExam.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .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()) { if (deleteCall.hasError()) {
final Exception error = deleteCall.getError(); final Exception error = deleteCall.getError();
if (error instanceof RestCallError) { if (error instanceof RestCallError) {
@ -146,33 +138,33 @@ public class ExamDeletePopup {
.findFirst() .findFirst()
.orElse(null); .orElse(null);
if (message != null && ErrorMessage.INTEGRITY_VALIDATION.isOf(message)) { if (message != null && ErrorMessage.INTEGRITY_VALIDATION.isOf(message)) {
pageContext.publishPageMessage(new PageMessageException(DELETE_ERROR_CONSISTENCY)); pageContext.applyConfirmDialog(
return false; 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; return true;
} catch (final Exception e) { } catch (final Exception e) {
log.error("Unexpected error while trying to delete Exam:", 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( private Supplier<PageContext> composeDeleteDialog(
final Composite parent, final Composite parent,
final PageContext pageContext) { final PageContext pageContext) {

View file

@ -200,6 +200,19 @@ public interface PageContext {
* @param callback callback code block that will be called on users selection */ * @param callback callback code block that will be called on users selection */
void applyConfirmDialog(LocTextKey confirmMessage, final Consumer<Boolean> callback); 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. /** This can be used to forward to a defined page.
* *
* @param pageDefinition the 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; private final Class<? extends PageDefinition> mainPageType = DefaultMainPage.class;
final AuthorizationContextHolder authorizationContextHolder; final AuthorizationContextHolder authorizationContextHolder;
private final PageService pageService;
private final I18nSupport i18nSupport; private final I18nSupport i18nSupport;
private final Map<String, TemplateComposer> composer; private final Map<String, TemplateComposer> composer;
private final Map<String, PageDefinition> pages; private final Map<String, PageDefinition> pages;
public ComposerServiceImpl( public ComposerServiceImpl(
final AuthorizationContextHolder authorizationContextHolder, final AuthorizationContextHolder authorizationContextHolder,
final I18nSupport i18nSupport, final PageService pageService,
final Collection<TemplateComposer> composer, final Collection<TemplateComposer> composer,
final Collection<PageDefinition> pageDefinitions) { final Collection<PageDefinition> pageDefinitions) {
this.authorizationContextHolder = authorizationContextHolder; this.authorizationContextHolder = authorizationContextHolder;
this.i18nSupport = i18nSupport; this.pageService = pageService;
this.i18nSupport = pageService.getI18nSupport();
this.composer = composer this.composer = composer
.stream() .stream()
.collect(Collectors.toMap( .collect(Collectors.toMap(
@ -233,7 +235,7 @@ public class ComposerServiceImpl implements ComposerService {
} }
private PageContext createPageContext(final Composite root) { 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) { } catch (final PageMessageException pme) {
PageAction.this.pageContext.publishPageMessage(pme); PageAction.this.pageContext.publishPageMessage(pme);
return;
} catch (final Exception e) { } catch (final Exception e) {
this.pageContext.notifyUnexpectedError(e); this.pageContext.notifyUnexpectedError(e);
return;
} }
} else { } else {
callback.accept(exec()); callback.accept(exec());

View file

@ -14,16 +14,18 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.gui.service.page.*;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.widgets.DialogCallback; import org.eclipse.rap.rwt.widgets.DialogCallback;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.*;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; 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.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; import ch.ethz.seb.sebserver.gui.widget.Message;
public class PageContextImpl implements PageContext { 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 LocTextKey UNEXPECTED_ERROR = new LocTextKey("sebserver.error.unexpected");
private static final String ENTITY_LIST_TYPE = null; private static final String ENTITY_LIST_TYPE = null;
private final PageService pageService;
private final I18nSupport i18nSupport; private final I18nSupport i18nSupport;
private final ComposerService composerService; private final ComposerService composerService;
private final Composite root; private final Composite root;
@ -55,13 +54,14 @@ public class PageContextImpl implements PageContext {
private final Map<String, String> attributes; private final Map<String, String> attributes;
PageContextImpl( PageContextImpl(
final I18nSupport i18nSupport, final PageService pageService,
final ComposerService composerService, final ComposerService composerService,
final Composite root, final Composite root,
final Composite parent, final Composite parent,
final Map<String, String> attributes) { final Map<String, String> attributes) {
this.i18nSupport = i18nSupport; this.pageService = pageService;
this.i18nSupport = pageService.getI18nSupport();
this.composerService = composerService; this.composerService = composerService;
this.root = root; this.root = root;
this.parent = parent; this.parent = parent;
@ -105,7 +105,7 @@ public class PageContextImpl implements PageContext {
@Override @Override
public PageContext copyOf(final Composite parent) { public PageContext copyOf(final Composite parent) {
return new PageContextImpl( return new PageContextImpl(
this.i18nSupport, this.pageService,
this.composerService, this.composerService,
this.root, this.root,
parent, parent,
@ -118,7 +118,7 @@ public class PageContextImpl implements PageContext {
attrs.putAll(this.attributes); attrs.putAll(this.attributes);
attrs.putAll(((PageContextImpl) otherContext).attributes); attrs.putAll(((PageContextImpl) otherContext).attributes);
return new PageContextImpl( return new PageContextImpl(
this.i18nSupport, this.pageService,
this.composerService, this.composerService,
this.root, this.root,
this.parent, this.parent,
@ -130,7 +130,7 @@ public class PageContextImpl implements PageContext {
final Map<String, String> attrs = new HashMap<>(this.attributes); final Map<String, String> attrs = new HashMap<>(this.attributes);
attrs.put(key, value); attrs.put(key, value);
return new PageContextImpl( return new PageContextImpl(
this.i18nSupport, this.pageService,
this.composerService, this.composerService,
this.root, this.root,
this.parent, this.parent,
@ -238,7 +238,7 @@ public class PageContextImpl implements PageContext {
final Map<String, String> attrs = new HashMap<>(this.attributes); final Map<String, String> attrs = new HashMap<>(this.attributes);
attrs.remove(name); attrs.remove(name);
return new PageContextImpl( return new PageContextImpl(
this.i18nSupport, this.pageService,
this.composerService, this.composerService,
this.root, this.root,
this.parent, this.parent,
@ -248,7 +248,7 @@ public class PageContextImpl implements PageContext {
@Override @Override
public PageContext clearAttributes() { public PageContext clearAttributes() {
return new PageContextImpl( return new PageContextImpl(
this.i18nSupport, this.pageService,
this.composerService, this.composerService,
this.root, this.root,
this.parent, this.parent,
@ -267,6 +267,36 @@ public class PageContextImpl implements PageContext {
messageBox.open(new ConfirmDialogCallback(callback)); 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 @Override
public void forwardToPage(final PageDefinition pageDefinition) { public void forwardToPage(final PageDefinition pageDefinition) {
this.composerService.compose( 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()) { if (protectedRun.hasError()) {
final Throwable cause = protectedRun.getError().getCause(); 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()); return Result.ofRuntimeError(cause.getMessage());
} }
} }

View file

@ -49,7 +49,6 @@ public class SEBClientEventBatchService {
private static final Logger log = LoggerFactory.getLogger(SEBClientEventBatchService.class); private static final Logger log = LoggerFactory.getLogger(SEBClientEventBatchService.class);
private final SEBClientNotificationService sebClientNotificationService; private final SEBClientNotificationService sebClientNotificationService;
private final SqlSessionFactory sqlSessionFactory;
private final TransactionTemplate transactionTemplate; private final TransactionTemplate transactionTemplate;
private final ExamSessionCacheService examSessionCacheService; private final ExamSessionCacheService examSessionCacheService;
private final JSONMapper jsonMapper; private final JSONMapper jsonMapper;
@ -65,13 +64,12 @@ public class SEBClientEventBatchService {
final JSONMapper jsonMapper) { final JSONMapper jsonMapper) {
this.sebClientNotificationService = sebClientNotificationService; this.sebClientNotificationService = sebClientNotificationService;
this.sqlSessionFactory = sqlSessionFactory;
this.transactionTemplate = new TransactionTemplate(transactionManager); this.transactionTemplate = new TransactionTemplate(transactionManager);
this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
this.examSessionCacheService = examSessionCacheService; this.examSessionCacheService = examSessionCacheService;
this.jsonMapper = jsonMapper; 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); this.clientEventMapper = this.sqlSessionTemplate.getMapper(ClientEventRecordMapper.class);
} }
@ -172,7 +170,7 @@ public class SEBClientEventBatchService {
this.sqlSessionTemplate.flushStatements(); this.sqlSessionTemplate.flushStatements();
if (log.isDebugEnabled()) { if (log.isTraceEnabled()) {
log.debug("SEBClientEventBatchService worker {} processes batch of size {} in {} ms", log.debug("SEBClientEventBatchService worker {} processes batch of size {} in {} ms",
workerName, workerName,
size, size,
@ -227,7 +225,7 @@ public class SEBClientEventBatchService {
eventData.event.eventType, eventData.event.eventType,
eventData.event.getClientTime(), eventData.event.getClientTime(),
eventData.event.getServerTime(), eventData.event.getServerTime(),
(eventData.event.numValue != null) ? eventData.event.numValue.doubleValue() : null, (eventData.event.numValue != null) ? eventData.event.numValue : null,
typeAndPlainText.b, typeAndPlainText.b,
typeAndPlainText.a); typeAndPlainText.a);

View file

@ -18,6 +18,7 @@ import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid; import javax.validation.Valid;
import ch.ethz.seb.sebserver.gbl.model.*;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger; 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.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; 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.Pair;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; 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 /** This is called by Spring to initialize the WebDataBinder and is used here to
* initialize the default value binding for the institutionId request-parameter * 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 */ * See also UserService.addUsersInstitutionDefaultPropertySupport */
@InitBinder @InitBinder
public void initBinder(final WebDataBinder binder) { 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 "), + "This is the name of the enumeration "),
@Parameter( @Parameter(
name = API.PARAM_BULK_ACTION_ADD_INCLUDES, 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( @Parameter(
name = API.PARAM_BULK_ACTION_INCLUDES, 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( @RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.DEPENDENCY_PATH_SEGMENT, 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.", 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, " 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( requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
content = { @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) }), content = { @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) }),
parameters = { parameters = {
@ -476,10 +470,10 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
in = ParameterIn.PATH), in = ParameterIn.PATH),
@Parameter( @Parameter(
name = API.PARAM_BULK_ACTION_ADD_INCLUDES, 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( @Parameter(
name = API.PARAM_BULK_ACTION_INCLUDES, 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( @RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT, path = API.MODEL_ID_VAR_PATH_SEGMENT,
@ -500,6 +494,43 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
.getOrThrow(); .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) // * 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.", 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, " 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( requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
content = { @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) }), content = { @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) }),
parameters = { 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 "), description = "Indicates if the following 'includes' paramerer shall be processed or not.\n The default is false "),
@Parameter( @Parameter(
name = API.PARAM_BULK_ACTION_INCLUDES, 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( @RequestMapping(
method = RequestMethod.DELETE, method = RequestMethod.DELETE,
@ -547,11 +578,11 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
final Collection<EntityKey> sources = ids.stream() final Collection<EntityKey> sources = ids.stream()
.map(id -> { .map(id -> {
return this.entityDAO.byModelId(id) return this.entityDAO.byModelId(id)
.flatMap(exam -> this.validForDelete(exam)) .flatMap(this::validForDelete)
.getOr(null); .getOr(null);
}) })
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(exam -> exam.getModelId()) .map(ModelIdAware::getModelId)
.map(id -> new EntityKey(id, entityType)) .map(id -> new EntityKey(id, entityType))
.collect(Collectors.toList()); .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. /** 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. * 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 */ * @return the EntityType of the GrantEntity that is used for grant checks of the concrete Entity */
protected EntityType getGrantEntityType() { protected EntityType getGrantEntityType() {

View file

@ -554,6 +554,9 @@ sebserver.exam.action.activate=Activate Exam
sebserver.exam.action.deactivate=Deactivate Exam sebserver.exam.action.deactivate=Deactivate Exam
sebserver.exam.action.delete=Delete 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.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=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.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 sebserver.exam.action.sebrestriction.enable=Apply SEB Lock