new import implementation with creating new configuration

This commit is contained in:
anhefti 2019-11-05 13:55:05 +01:00
parent da178edf50
commit 9bc8dfaf8b
13 changed files with 338 additions and 72 deletions

View file

@ -9,32 +9,41 @@
package ch.ethz.seb.sebserver.gui.content; package ch.ethz.seb.sebserver.gui.content;
import java.io.InputStream; import java.io.InputStream;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Control;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.form.Form; import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.form.FormBuilder; import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle; import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer; import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog; import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfigOnExistingConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportNewExamConfig;
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection; import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
public final class SebExamConfigImport { public final class SebExamConfigImport {
static Function<PageAction, PageAction> importConfigFunction(final PageService pageService) { static Function<PageAction, PageAction> importFunction(
final PageService pageService,
final boolean newConfig) {
return action -> { return action -> {
final ModalInputDialog<FormHandle<ConfigurationNode>> dialog = final ModalInputDialog<FormHandle<ConfigurationNode>> dialog =
@ -45,13 +54,15 @@ public final class SebExamConfigImport {
final ImportFormContext importFormContext = new ImportFormContext( final ImportFormContext importFormContext = new ImportFormContext(
pageService, pageService,
action.pageContext()); action.pageContext(),
newConfig);
dialog.open( dialog.open(
SebExamConfigPropForm.FORM_IMPORT_TEXT_KEY, SebExamConfigPropForm.FORM_IMPORT_TEXT_KEY,
(Consumer<FormHandle<ConfigurationNode>>) formHandle -> doImport( (Predicate<FormHandle<ConfigurationNode>>) formHandle -> doImport(
pageService, pageService,
formHandle), formHandle,
newConfig),
importFormContext::cancelUpload, importFormContext::cancelUpload,
importFormContext); importFormContext);
@ -59,39 +70,90 @@ public final class SebExamConfigImport {
}; };
} }
private static final void doImport( private static final boolean doImport(
final PageService pageService, final PageService pageService,
final FormHandle<ConfigurationNode> formHandle) { final FormHandle<ConfigurationNode> formHandle,
final boolean newConfig) {
final Form form = formHandle.getForm(); try {
final EntityKey entityKey = formHandle.getContext().getEntityKey(); final Form form = formHandle.getForm();
final Control fieldControl = form.getFieldControl(API.IMPORT_FILE_ATTR_NAME); final EntityKey entityKey = formHandle.getContext().getEntityKey();
final PageContext context = formHandle.getContext(); final Control fieldControl = form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
if (fieldControl != null && fieldControl instanceof FileUploadSelection) { final PageContext context = formHandle.getContext();
final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl;
final InputStream inputStream = fileUpload.getInputStream();
if (inputStream != null) {
final Configuration configuration = pageService.getRestService()
.getBuilder(ImportExamConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.withHeader(
API.IMPORT_PASSWORD_ATTR_NAME,
form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME))
.withBody(inputStream)
.call()
.get(e -> {
fileUpload.close();
return context.notifyError(e);
});
if (configuration != null) { // Ad-hoc field validation
context.publishInfo(SebExamConfigPropForm.FORM_IMPORT_CONFIRM_TEXT_KEY); formHandle.process(name -> true, field -> field.resetError());
} final String fieldValue = form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME);
} else { if (StringUtils.isBlank(fieldValue)) {
formHandle.getContext().publishPageMessage( form.setFieldError(
new LocTextKey("sebserver.error.unexpected"), Domain.CONFIGURATION_NODE.ATTR_NAME,
new LocTextKey("Please selecte a valid SEB Exam Configuration File")); pageService
.getI18nSupport()
.getText(new LocTextKey("sebserver.form.validation.fieldError.notNull")));
return false;
} else if (fieldValue.length() < 3 || fieldValue.length() > 255) {
form.setFieldError(
Domain.CONFIGURATION_NODE.ATTR_NAME,
pageService
.getI18nSupport()
.getText(new LocTextKey("sebserver.form.validation.fieldError.size",
null,
null,
null,
3,
255)));
return false;
} }
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl;
final InputStream inputStream = fileUpload.getInputStream();
if (inputStream != null) {
final RestCall<Configuration>.RestCallBuilder restCall = (newConfig)
? pageService.getRestService()
.getBuilder(ImportNewExamConfig.class)
: pageService.getRestService()
.getBuilder(ImportExamConfigOnExistingConfig.class);
restCall
.withHeader(
API.IMPORT_PASSWORD_ATTR_NAME,
form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME))
.withBody(inputStream);
if (newConfig) {
restCall
.withHeader(
Domain.CONFIGURATION_NODE.ATTR_NAME,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME))
.withHeader(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION))
.withHeader(
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID));
} else {
restCall.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId);
}
final Result<Configuration> configuration = restCall
.call();
if (!configuration.hasError()) {
context.publishInfo(SebExamConfigPropForm.FORM_IMPORT_CONFIRM_TEXT_KEY);
return true;
}
} else {
formHandle.getContext().publishPageMessage(
new LocTextKey("sebserver.error.unexpected"),
new LocTextKey("Please selecte a valid SEB Exam Configuration File"));
}
}
return false;
} catch (final Exception e) {
formHandle.getContext().notifyError(e);
return true;
} }
} }
@ -99,17 +161,25 @@ public final class SebExamConfigImport {
private final PageService pageService; private final PageService pageService;
private final PageContext pageContext; private final PageContext pageContext;
private final boolean newConfig;
private Form form = null; private Form form = null;
protected ImportFormContext(final PageService pageService, final PageContext pageContext) { protected ImportFormContext(
final PageService pageService,
final PageContext pageContext,
final boolean newConfig) {
this.pageService = pageService; this.pageService = pageService;
this.pageContext = pageContext; this.pageContext = pageContext;
this.newConfig = newConfig;
} }
@Override @Override
public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) { public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) {
final ResourceService resourceService = this.pageService.getResourceService();
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder( final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
this.pageContext.copyOf(parent), 4) this.pageContext.copyOf(parent), 4)
.readonly(false) .readonly(false)
@ -118,6 +188,26 @@ public final class SebExamConfigImport {
SebExamConfigPropForm.FORM_IMPORT_SELECT_TEXT_KEY, SebExamConfigPropForm.FORM_IMPORT_SELECT_TEXT_KEY,
null, null,
API.SEB_FILE_EXTENSION)) API.SEB_FILE_EXTENSION))
.addFieldIf(
() -> this.newConfig,
() -> FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_NAME,
SebExamConfigPropForm.FORM_NAME_TEXT_KEY))
.addFieldIf(
() -> this.newConfig,
() -> FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
.asArea())
.addFieldIf(
() -> this.newConfig,
() -> FormBuilder.singleSelection(
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
SebExamConfigPropForm.FORM_TEMPLATE_TEXT_KEY,
null,
resourceService::getExamConfigTemplateResources))
.addField(FormBuilder.text( .addField(FormBuilder.text(
API.IMPORT_PASSWORD_ATTR_NAME, API.IMPORT_PASSWORD_ATTR_NAME,
SebExamConfigPropForm.FORM_IMPORT_PASSWORD_TEXT_KEY, SebExamConfigPropForm.FORM_IMPORT_PASSWORD_TEXT_KEY,

View file

@ -207,6 +207,11 @@ public class SebExamConfigList implements TemplateComposer {
PageAction::applySingleSelection, EMPTY_SELECTION_TEXT_KEY) PageAction::applySingleSelection, EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> examConfigGrant.im() && configTable.hasAnyContent()) .publishIf(() -> examConfigGrant.im() && configTable.hasAnyContent())
.newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG)
.withExec(SebExamConfigImport.importFunction(this.pageService, true))
.noEventPropagation()
.publishIf(() -> examConfigGrant.im())
// Exam Configuration template actions... // Exam Configuration template actions...
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_NEW) .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_NEW)
.publishIf(examConfigGrant::iw) .publishIf(examConfigGrant::iw)

View file

@ -246,9 +246,9 @@ public class SebExamConfigPropForm implements TemplateComposer {
.noEventPropagation() .noEventPropagation()
.publishIf(() -> modifyGrant && isReadonly) .publishIf(() -> modifyGrant && isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_CONFIG) .newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_EXISTING_CONFIG)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(SebExamConfigImport.importConfigFunction(this.pageService)) .withExec(SebExamConfigImport.importFunction(this.pageService, false))
.noEventPropagation() .noEventPropagation()
.publishIf(() -> modifyGrant && isReadonly && !isAttachedToExam) .publishIf(() -> modifyGrant && isReadonly && !isAttachedToExam)

View file

@ -368,6 +368,10 @@ public enum ActionDefinition {
ImageIcon.SHOW, ImageIcon.SHOW,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW, PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW,
ActionCategory.FORM), ActionCategory.FORM),
SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG(
new LocTextKey("sebserver.examconfig.action.import-config"),
ImageIcon.IMPORT,
ActionCategory.VARIA),
SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST( SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST(
new LocTextKey("sebserver.examconfig.action.list.modify.properties"), new LocTextKey("sebserver.examconfig.action.list.modify.properties"),
@ -407,10 +411,11 @@ public enum ActionDefinition {
new LocTextKey("sebserver.examconfig.action.get-config-key"), new LocTextKey("sebserver.examconfig.action.get-config-key"),
ImageIcon.SECURE, ImageIcon.SECURE,
ActionCategory.FORM), ActionCategory.FORM),
SEB_EXAM_CONFIG_IMPORT_CONFIG( SEB_EXAM_CONFIG_IMPORT_TO_EXISTING_CONFIG(
new LocTextKey("sebserver.examconfig.action.import-config"), new LocTextKey("sebserver.examconfig.action.import-config"),
ImageIcon.IMPORT, ImageIcon.IMPORT,
ActionCategory.FORM), ActionCategory.FORM),
SEB_EXAM_CONFIG_COPY_CONFIG( SEB_EXAM_CONFIG_COPY_CONFIG(
new LocTextKey("sebserver.examconfig.action.copy"), new LocTextKey("sebserver.examconfig.action.copy"),
ImageIcon.COPY, ImageIcon.COPY,

View file

@ -227,6 +227,15 @@ public final class Form implements FormBinding {
.isPresent(); .isPresent();
} }
public void setFieldError(final String fieldName, final String errorMessage) {
final List<FormFieldAccessor> list = this.formFields.get(fieldName);
if (list != null) {
list
.stream()
.forEach(ffa -> ffa.setError(errorMessage));
}
}
public void process( public void process(
final Predicate<String> nameFilter, final Predicate<String> nameFilter,
final Consumer<FormFieldAccessor> processor) { final Consumer<FormFieldAccessor> processor) {

View file

@ -24,9 +24,9 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class ImportExamConfig extends RestCall<Configuration> { public class ImportExamConfigOnExistingConfig extends RestCall<Configuration> {
public ImportExamConfig() { public ImportExamConfigOnExistingConfig() {
super(new TypeKey<>( super(new TypeKey<>(
CallType.UNDEFINED, CallType.UNDEFINED,
EntityType.CONFIGURATION, EntityType.CONFIGURATION,

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class ImportNewExamConfig extends RestCall<Configuration> {
public ImportNewExamConfig() {
super(new TypeKey<>(
CallType.UNDEFINED,
EntityType.CONFIGURATION,
new TypeReference<Configuration>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_OCTET_STREAM,
API.CONFIGURATION_NODE_ENDPOINT + API.CONFIGURATION_IMPORT_PATH_SEGMENT);
}
}

View file

@ -13,6 +13,7 @@ import java.util.Set;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration> { public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration> {
@ -48,6 +49,31 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
* @return the current and reseted follow-up version */ * @return the current and reseted follow-up version */
Result<Configuration> undo(Long configurationNodeId); Result<Configuration> undo(Long configurationNodeId);
/** Restores the attribute values to the default values that have been set for the specified configuration
* on initialization. This are the base default values if the configuration has no template or the default
* values from the template if there is one assigned to the configuration.
*
* In fact. this just gets the initial configuration values and reset the current values with that one
*
* @param configurationNodeId the ConfigurationNode identifier
* @return the Configuration instance for which the attribute values have been reset */
Result<Configuration> restoreToDefaultValues(final Long configurationNodeId);
/** Restores the attribute values to the default values that have been set for the specified configuration
* on initialization. This are the base default values if the configuration has no template or the default
* values from the template if there is one assigned to the configuration.
*
* In fact. this just gets the initial configuration values and reset the current values with that one
*
* @param configuration the Configuration that defines the ConfigurationNode identifier
* @return the Configuration instance for which the attribute values have been reset */
default Result<Configuration> restoreToDefaultValues(final Configuration configuration) {
if (configuration == null) {
return Result.ofError(new NullPointerException("configuration"));
}
return restoreToDefaultValues(configuration.configurationNodeId);
}
/** Restores the current follow-up Configuration to the values of a given Configuration /** Restores the current follow-up Configuration to the values of a given Configuration
* in the history of the specified ConfigurationNode. * in the history of the specified ConfigurationNode.
* *
@ -62,6 +88,17 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
* @return the current follow-up configuration */ * @return the current follow-up configuration */
Result<Configuration> getFollowupConfiguration(Long configNodeId); Result<Configuration> getFollowupConfiguration(Long configNodeId);
/** Use this to get the follow-up configuration for a specified configuration node.
*
* @param configNode ConfigurationNode to get the current follow-up configuration from
* @return the current follow-up configuration */
default Result<Configuration> getFollowupConfiguration(final ConfigurationNode configurationNode) {
if (configurationNode == null) {
return Result.ofError(new NullPointerException("configurationNode"));
}
return getFollowupConfiguration(configurationNode.id);
}
/** Use this to get the last version of a configuration that is not the follow-up. /** Use this to get the last version of a configuration that is not the follow-up.
* *
* @param configNodeId ConfigurationNode identifier to get the last version of configuration from * @param configNodeId ConfigurationNode identifier to get the last version of configuration from

View file

@ -266,6 +266,25 @@ class ConfigurationDAOBatchService {
.flatMap(rec -> restoreToVersion(configurationNodeId, rec.getId())); .flatMap(rec -> restoreToVersion(configurationNodeId, rec.getId()));
} }
Result<Configuration> restoreToDefaultValues(final Long configurationNodeId) {
return Result.tryCatch(() -> {
// get initial version that contains the default values either from base or from template
return this.batchConfigurationRecordMapper.selectIdsByExample()
.where(
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
isEqualTo(configurationNodeId))
.and(
ConfigurationRecordDynamicSqlSupport.version,
isEqualTo(INITIAL_VERSION_NAME))
.build()
.execute()
.stream()
.collect(Utils.toSingleton());
})
.flatMap(configId -> restoreToVersion(configurationNodeId, configId));
}
Result<Configuration> restoreToVersion(final Long configurationNodeId, final Long configId) { Result<Configuration> restoreToVersion(final Long configurationNodeId, final Long configId) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {

View file

@ -193,6 +193,14 @@ public class ConfigurationDAOImpl implements ConfigurationDAO {
.onError(TransactionHandler::rollback); .onError(TransactionHandler::rollback);
} }
@Override
@Transactional
public Result<Configuration> restoreToDefaultValues(final Long configurationNodeId) {
return this.configurationDAOBatchService
.restoreToDefaultValues(configurationNodeId)
.onError(TransactionHandler::rollback);
}
@Override @Override
@Transactional @Transactional
public Result<Configuration> restoreToVersion(final Long configurationNodeId, final Long configId) { public Result<Configuration> restoreToVersion(final Long configurationNodeId, final Long configId) {

View file

@ -118,10 +118,10 @@ public interface SebExamConfigService {
* *
* Then parses the XML and adds each attribute to the new Configuration. * Then parses the XML and adds each attribute to the new Configuration.
* *
* @param configNodeId The identifier of the configuration node on which the import should take place * @param config The Configuration to import the attribute values to
* @param input The InputStream to get the SEB config file as byte-stream * @param input The InputStream to get the SEB config file as byte-stream
* @param password A password is only needed if the file is in an encrypted format * @param password A password is only needed if the file is in an encrypted format
* @return The newly created Configuration instance */ * @return The newly created Configuration instance */
Result<Configuration> importFromSEBFile(Long configNodeId, InputStream input, CharSequence password); Result<Configuration> importFromSEBFile(Configuration config, InputStream input, CharSequence password);
} }

View file

@ -330,16 +330,12 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
@Override @Override
public Result<Configuration> importFromSEBFile( public Result<Configuration> importFromSEBFile(
final Long configNodeId, final Configuration config,
final InputStream input, final InputStream input,
final CharSequence password) { final CharSequence password) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final Configuration newConfig = this.configurationDAO
.saveToHistory(configNodeId)
.getOrThrow();
Future<Exception> streamDecrypted = null; Future<Exception> streamDecrypted = null;
InputStream cryptIn = null; InputStream cryptIn = null;
PipedInputStream plainIn = null; PipedInputStream plainIn = null;
@ -363,16 +359,16 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
// parse XML and import // parse XML and import
this.examConfigIO.importPlainXML( this.examConfigIO.importPlainXML(
unzippedIn, unzippedIn,
newConfig.institutionId, config.institutionId,
newConfig.id); config.id);
return newConfig; return config;
} catch (final Exception e) { } catch (final Exception e) {
log.error("Unexpected error while trying to import SEB Exam Configuration: ", e); log.error("Unexpected error while trying to import SEB Exam Configuration: ", e);
log.debug("Make an undo on the ConfigurationNode to rollback the changes"); log.debug("Make an undo on the ConfigurationNode to rollback the changes");
this.configurationDAO this.configurationDAO
.undo(configNodeId) .undo(config.configurationNodeId)
.getOrThrow(); .getOrThrow();
if (streamDecrypted != null) { if (streamDecrypted != null) {

View file

@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid; import javax.validation.Valid;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
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;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -39,12 +40,14 @@ import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
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.Domain;
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM; import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; 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.sebconfig.ConfigurationNode.ConfigurationType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
@ -222,11 +225,50 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
} }
@RequestMapping( @RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_IMPORT_PATH_SEGMENT, path = API.CONFIGURATION_IMPORT_PATH_SEGMENT,
method = RequestMethod.POST, method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object importExamConfig( public Object importExamConfig(
@RequestHeader(name = Domain.CONFIGURATION_NODE.ATTR_NAME, required = false) final String name,
@RequestHeader(name = Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
required = false) final String description,
@RequestHeader(name = Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID, required = false) final String templateId,
@RequestHeader(name = API.IMPORT_PASSWORD_ATTR_NAME, required = false) final String password,
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
final HttpServletRequest request) throws IOException {
this.checkModifyPrivilege(institutionId);
final SEBServerUser currentUser = this.authorization.getUserService().getCurrentUser();
final ConfigurationNode configurationNode = new ConfigurationNode(
null,
institutionId,
StringUtils.isNotBlank(templateId) ? Long.parseLong(templateId) : null,
name,
description,
ConfigurationType.EXAM_CONFIG,
currentUser.uuid(),
ConfigurationStatus.CONSTRUCTION);
final Configuration followup = this.beanValidationService.validateBean(configurationNode)
.flatMap(this.entityDAO::createNew)
.flatMap(this.configurationDAO::getFollowupConfiguration)
.getOrThrow();
return doImport(password, request, followup);
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_IMPORT_PATH_SEGMENT,
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object importExamConfigOnExistingConfig(
@PathVariable final Long modelId, @PathVariable final Long modelId,
@RequestHeader(name = API.IMPORT_PASSWORD_ATTR_NAME, required = false) final String password, @RequestHeader(name = API.IMPORT_PASSWORD_ATTR_NAME, required = false) final String password,
@RequestParam( @RequestParam(
@ -235,27 +277,15 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
final HttpServletRequest request) throws IOException { final HttpServletRequest request) throws IOException {
final InputStream inputStream = new BufferedInputStream(request.getInputStream()); this.entityDAO.byPK(modelId)
try { .flatMap(this.authorization::checkModify);
return this.sebExamConfigService.importFromSEBFile( final Configuration newConfig = this.configurationDAO
modelId, .saveToHistory(modelId)
inputStream, .flatMap(this.configurationDAO::restoreToDefaultValues)
password) .getOrThrow();
.getOrThrow();
} catch (final Exception e) { return doImport(password, request, newConfig);
// NOTE: It seems that this has to be manually closed on error case
// We expected that this is closed by the API but if this manual close is been left
// some left-overs will affect strange behavior.
// TODO: find a better solution for this
IOUtils.closeQuietly(inputStream);
//throw e;
return new ResponseEntity<>(
Arrays.asList(APIMessage.ErrorMessage.UNEXPECTED.of(e.getMessage())),
Utils.createJsonContentHeader(),
HttpStatus.BAD_REQUEST);
}
} }
@RequestMapping( @RequestMapping(
@ -444,4 +474,31 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
return node; return node;
} }
private Object doImport(
final String password,
final HttpServletRequest request,
final Configuration configuration) throws IOException {
final InputStream inputStream = new BufferedInputStream(request.getInputStream());
try {
return this.sebExamConfigService.importFromSEBFile(
configuration,
inputStream,
password)
.getOrThrow();
} catch (final Exception e) {
// NOTE: It seems that this has to be manually closed on error case
// We expected that this is closed by the API but if this manual close is been left
// some left-overs will affect strange behavior.
// TODO: find a better solution for this
IOUtils.closeQuietly(inputStream);
return new ResponseEntity<>(
Arrays.asList(APIMessage.ErrorMessage.UNEXPECTED.of(e.getMessage())),
Utils.createJsonContentHeader(),
HttpStatus.BAD_REQUEST);
}
}
} }