From bbb15bba409464a60b8f2054750ce899cdeb8412 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 5 Jun 2019 09:58:59 +0200 Subject: [PATCH] SEBSERV-44 SEBSERV-45 export of plain XML exam config implemented --- .../ch/ethz/seb/sebserver/gbl/Constants.java | 33 ++-- .../ch/ethz/seb/sebserver/gbl/api/API.java | 6 +- .../sebconfig/ConfigurationAttribute.java | 8 +- .../gui/content/SebClientConfigForm.java | 17 ++- .../gui/content/SebClientConfigList.java | 4 +- .../gui/content/SebExamConfigPropForm.java | 27 +++- .../gui/content/action/ActionDefinition.java | 7 +- .../gui/service/examconfig/InputField.java | 39 +++++ .../examconfig/impl/AbstractInputField.java | 11 +- .../impl/AbstractTableFieldBuilder.java | 7 - .../examconfig/impl/PassworFieldBuilder.java | 7 +- .../service/examconfig/impl/TableContext.java | 30 ---- .../examconfig/impl/TableRowFormBuilder.java | 14 +- .../AbstractDownloadServiceHandler.java | 79 ++++++++++ .../gui/service/remote/DownloadService.java | 31 +++- .../remote/DownloadServiceHandler.java | 2 - .../remote/SebClientConfigDownload.java | 81 +--------- .../service/remote/SebExamConfigDownload.java | 38 +++++ .../webservice/api/AbstractExportCall.java | 52 +++++++ .../seb/clientconfig/ExportClientConfig.java | 38 +---- ...ableRowValues.java => ExportPlainXML.java} | 19 ++- .../seb/sebserver/gui/table/EntityTable.java | 52 ++++--- .../seb/sebserver/gui/table/TableFilter.java | 9 +- .../sebserver/gui/widget/WidgetFactory.java | 1 + .../dao/ConfigurationAttributeDAO.java | 5 + .../servicelayer/dao/ConfigurationDAO.java | 10 +- .../dao/ConfigurationValueDAO.java | 51 +++++-- .../servicelayer/dao/FilterMap.java | 3 +- .../impl/ConfigurationAttributeDAOImpl.java | 16 ++ .../dao/impl/ConfigurationDAOImpl.java | 4 +- .../dao/impl/ConfigurationValueDAOImpl.java | 122 +++++++++++---- .../ConfigurationValueValidator.java | 2 +- .../sebconfig/SebClientConfigService.java | 10 +- .../sebconfig/SebExamConfigService.java | 10 +- .../sebconfig/XMLValueConverter.java | 5 +- .../sebconfig/XMLValueConverterService.java | 17 +++ .../impl/SebClientConfigServiceImpl.java | 19 ++- .../impl/SebExamConfigServiceImpl.java | 135 +++++++++++++++- .../converter/ArrayOfStringConverter.java | 79 ++++++++++ .../impl/converter/BooleanConverter.java | 25 ++- .../impl/converter/IntegerConverter.java | 71 +++++++++ .../impl/converter/KioskModeConverter.java | 17 ++- .../impl/converter/StringConverter.java | 72 +++++++++ .../impl/converter/TableConverter.java | 144 ++++++++++++++++++ .../api/ConfigurationNodeController.java | 37 ++++- .../api/ConfigurationValueController.java | 27 ---- .../config/application-demo.properties | 7 +- .../config/application-dev-gui.properties | 3 +- src/main/resources/messages.properties | 1 + src/main/resources/static/images/export.png | Bin 0 -> 180 bytes 50 files changed, 1176 insertions(+), 328 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/AbstractDownloadServiceHandler.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebExamConfigDownload.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/AbstractExportCall.java rename src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/{GetExamConfigTableRowValues.java => ExportPlainXML.java} (60%) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverterService.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/ArrayOfStringConverter.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/IntegerConverter.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/StringConverter.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java create mode 100644 src/main/resources/static/images/export.png diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index 279c8319..a62b7948 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.gbl; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import ch.ethz.seb.sebserver.gbl.util.Utils; + /** Global Constants used in SEB Server web-service as well as in web-gui component */ public final class Constants { @@ -41,17 +43,24 @@ public final class Constants { .forPattern(DEFAULT_DATE_TIME_FORMAT) .withZoneUTC(); -// /** Date-Time formatter without milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss */ -// // TODO check if this works with DEFAULT_DATE_TIME_FORMAT -// @Deprecated -// public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_NO_MILLIS = DateTimeFormat -// .forPattern("yyyy-MM-dd HH:mm:ss") -// .withZoneUTC(); -// -// /** Date-Time formatter with milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss.S */ -// @Deprecated -// public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_MILLIS = DateTimeFormat -// .forPattern("yyyy-MM-dd HH:mm:ss.S") -// .withZoneUTC(); + public static final String XML_VERSION_HEADER = + ""; + public static final String XML_DOCTYPE_HEADER = + ""; + public static final String XML_PLIST_START_V1 = + ""; + public static final String XML_PLIST_END = + ""; + public static final String XML_DICT_START = + ""; + public static final String XML_DICT_END = + ""; + + public static final byte[] XML_VERSION_HEADER_UTF_8 = Utils.toByteArray(XML_VERSION_HEADER); + public static final byte[] XML_DOCTYPE_HEADER_UTF_8 = Utils.toByteArray(XML_DOCTYPE_HEADER); + public static final byte[] XML_PLIST_START_V1_UTF_8 = Utils.toByteArray(XML_PLIST_START_V1); + public static final byte[] XML_PLIST_END_UTF_8 = Utils.toByteArray(XML_PLIST_END); + public static final byte[] XML_DICT_START_UTF_8 = Utils.toByteArray(XML_DICT_START); + public static final byte[] XML_DICT_END_UTF_8 = Utils.toByteArray(XML_DICT_END); } 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 3ac2d325..ebead99b 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 @@ -69,16 +69,12 @@ public final class API { public static final String CONFIGURATION_SAVE_TO_HISTORY_PATH_SEGMENT = "/save_to_history"; public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo"; public static final String CONFIGURATION_RESTORE_FROM_HISTORY_PATH_SEGMENT = "/restore"; - public static final String CONFIGURATION_VALUE_ENDPOINT = "/configuration_value"; public static final String CONFIGURATION_TABLE_VALUE_PATH_SEGMENT = "/table"; - public static final String CONFIGURATION_TABLE_ROW_VALUE_PATH_SEGMENT = - CONFIGURATION_TABLE_VALUE_PATH_SEGMENT + "/row"; - public static final String CONFIGURATION_ATTRIBUTE_ENDPOINT = "/configuration_attribute"; + public static final String CONFIGURATION_PLAIN_XML_DOWNLOAD_PATH_SEGMENT = "/downloadxml"; public static final String ORIENTATION_ENDPOINT = "/orientation"; - public static final String VIEW_ENDPOINT = ORIENTATION_ENDPOINT + "/view"; public static final String EXAM_CONFIGURATION_MAP_ENDPOINT = "/exam_configuration_map"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationAttribute.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationAttribute.java index 2bd1c48e..6c195c93 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationAttribute.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationAttribute.java @@ -32,7 +32,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.CONFIGURATION_ATTRIBUTE; import ch.ethz.seb.sebserver.gbl.model.Entity; @JsonIgnoreProperties(ignoreUnknown = true) -public final class ConfigurationAttribute implements Entity { +public final class ConfigurationAttribute implements Entity, Comparable { private static final Logger log = LoggerFactory.getLogger(ConfigurationAttribute.class); @@ -160,6 +160,12 @@ public final class ConfigurationAttribute implements Entity { return this.defaultValue; } + @Override + public int compareTo(final ConfigurationAttribute attribute) { + // TODO check if this is correct in reference to https://www.safeexambrowser.org/developer/seb-config-key.html + return this.name.compareToIgnoreCase(attribute.name); + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigForm.java index 85fdce16..603f9db4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigForm.java @@ -13,6 +13,7 @@ import org.eclipse.rap.rwt.client.service.UrlLauncher; import org.eclipse.swt.widgets.Composite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -31,6 +32,7 @@ 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.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.page.impl.PageUtils; +import ch.ethz.seb.sebserver.gui.service.remote.DownloadService; import ch.ethz.seb.sebserver.gui.service.remote.SebClientConfigDownload; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ActivateClientConfig; @@ -65,18 +67,21 @@ public class SebClientConfigForm implements TemplateComposer { private final PageService pageService; private final RestService restService; private final CurrentUser currentUser; - private final SebClientConfigDownload sebClientConfigDownload; + private final DownloadService downloadService; + private final String downloadFileName; protected SebClientConfigForm( final PageService pageService, final RestService restService, final CurrentUser currentUser, - final SebClientConfigDownload sebClientConfigDownload) { + final DownloadService downloadService, + @Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) { this.pageService = pageService; this.restService = restService; this.currentUser = currentUser; - this.sebClientConfigDownload = sebClientConfigDownload; + this.downloadService = downloadService; + this.downloadFileName = downloadFileName; } @Override @@ -169,8 +174,10 @@ public class SebClientConfigForm implements TemplateComposer { .newAction(ActionDefinition.SEB_CLIENT_CONFIG_EXPORT) .withEntityKey(entityKey) .withExec(action -> { - final String downloadURL = this.sebClientConfigDownload.downloadSEBClientConfigURL( - entityKey.modelId); + final String downloadURL = this.downloadService.createDownloadURL( + entityKey.modelId, + SebClientConfigDownload.class, + this.downloadFileName); urlLauncher.openURL(downloadURL); return action; }) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java index fb141539..34a6a95d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java @@ -67,7 +67,9 @@ public class SebClientConfigList implements TemplateComposer { new TableFilterAttribute( CriteriaType.DATE, SebClientConfig.FILTER_ATTR_CREATION_DATE, - DateTime.now(DateTimeZone.UTC).minusYears(1).toString(Constants.DEFAULT_DATE_TIME_FORMAT)); + DateTime.now(DateTimeZone.UTC) + .minusYears(1) + .toString(Constants.DEFAULT_DATE_TIME_FORMAT)); private final PageService pageService; private final RestService restService; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java index 75b45229..438736c8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java @@ -8,9 +8,12 @@ package ch.ethz.seb.sebserver.gui.content; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.client.service.UrlLauncher; import org.eclipse.swt.widgets.Composite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -29,6 +32,8 @@ import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; 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.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.remote.DownloadService; +import ch.ethz.seb.sebserver.gui.service.remote.SebExamConfigDownload; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig; @@ -58,15 +63,21 @@ public class SebExamConfigPropForm implements TemplateComposer { private final PageService pageService; private final RestService restService; private final CurrentUser currentUser; + private final DownloadService downloadService; + private final String downloadFileName; protected SebExamConfigPropForm( final PageService pageService, final RestService restService, - final CurrentUser currentUser) { + final CurrentUser currentUser, + final DownloadService downloadService, + @Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) { this.pageService = pageService; this.restService = restService; this.currentUser = currentUser; + this.downloadService = downloadService; + this.downloadFileName = downloadFileName; } @Override @@ -99,6 +110,7 @@ public class SebExamConfigPropForm implements TemplateComposer { } final EntityGrantCheck entityGrant = this.currentUser.entityGrantCheck(examConfig); + final boolean readGrant = entityGrant.r(); final boolean writeGrant = entityGrant.w(); final boolean modifyGrant = entityGrant.m(); final boolean isReadonly = pageContext.isReadonly(); @@ -145,6 +157,7 @@ public class SebExamConfigPropForm implements TemplateComposer { ? this.restService.getRestCall(NewExamConfig.class) : this.restService.getRestCall(SaveExamConfig.class)); + final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class); this.pageService.pageActionBuilder(formContext.clearEntityKeys()) .newAction(ActionDefinition.SEB_EXAM_CONFIG_NEW) @@ -158,6 +171,18 @@ public class SebExamConfigPropForm implements TemplateComposer { .withEntityKey(entityKey) .publishIf(() -> modifyGrant && isReadonly) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_EXPORT_PLAIN_XML) + .withEntityKey(entityKey) + .withExec(action -> { + final String downloadURL = this.downloadService.createDownloadURL( + entityKey.modelId, + SebExamConfigDownload.class, + this.downloadFileName); + urlLauncher.openURL(downloadURL); + return action; + }) + .publishIf(() -> readGrant && isReadonly) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_SAVE) .withEntityKey(entityKey) .withExec(formHandle::processFormSave) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 1869265f..27f65c37 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -324,7 +324,7 @@ public enum ActionDefinition { ActionCategory.FORM), SEB_CLIENT_CONFIG_EXPORT( new LocTextKey("sebserver.clientconfig.action.export"), - ImageIcon.SAVE, + ImageIcon.EXPORT, PageStateDefinition.SEB_CLIENT_CONFIG_VIEW, ActionCategory.FORM), @@ -365,6 +365,11 @@ public enum ActionDefinition { ImageIcon.SAVE, PageStateDefinition.SEB_EXAM_CONFIG_VIEW, ActionCategory.FORM), + SEB_EXAM_CONFIG_EXPORT_PLAIN_XML( + new LocTextKey("sebserver.examconfig.action.export.plainxml"), + ImageIcon.EXPORT, + PageStateDefinition.SEB_EXAM_CONFIG_VIEW, + ActionCategory.FORM), SEB_EXAM_CONFIG_MODIFY_FROM_LIST( new LocTextKey("sebserver.examconfig.action.list.modify"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputField.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputField.java index f00b681c..cd347e54 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputField.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputField.java @@ -17,26 +17,65 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation; /** Adapter interface for SEB Exam Configuration based input fields. */ public interface InputField { + /** Get the underling ConfigurationAttribute of the InputField. + * + * @return the underling ConfigurationAttribute of the InputField. */ ConfigurationAttribute getAttribute(); + /** Get the underling Orientation of the InputField. + * + * @return the underling Orientation of the InputField. */ Orientation getOrientation(); + /** Initialize the field value from a collection of ConfigurationValue. + * The input field searches the given values for a matching value of the input field regarding to its + * ConfigurationAttribute and takes the first found. + * + * @param values collection of available ConfigurationValue + * @return the ConfigurationValue that was used to initialize the field value */ ConfigurationValue initValue(Collection values); + /** Initialize the field value directly by given value and list index. + * + * @param value the value to set as field value + * @param listIndex the list index of the field */ void initValue(final String value, final Integer listIndex); + /** Get the current field value. + * + * @return the current field value. */ String getValue(); + /** get the current human-readable field value. + * + * @return the current human-readable field value. */ String getReadableValue(); + /** Use this to show an error message below the input field. + * This is only possible if the concrete input field has an error label, otherwise ignored + * + * @param errorMessage the error message to display below the input field */ void showError(String errorMessage); + /** Indicated if the input field has an error on the currently set value. + * + * @return true if the input field has an error on the currently set value. */ + boolean hasError(); + + /** Use this to clear any error on the input field. */ void clearError(); + /** Use this to disable the input field. + * + * @param group indicates if instead of the field, the entire group of the field shall be disabled. */ void disable(boolean group); + /** Use this to enable the input field. + * + * @param group indicates if instead of the field, the entire group of the field shall be enabled. */ void enable(boolean group); + /** Use this to set/reset the default value of the underling ConfigurationAttribute to the field value. */ void setDefaultValue(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java index d746862a..d77a8a48 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java @@ -89,13 +89,22 @@ public abstract class AbstractInputField implements InputFiel this.errorLabel.setVisible(true); } + @Override + public boolean hasError() { + if (this.errorLabel == null) { + return false; + } + + return this.errorLabel.isVisible(); + } + @Override public void clearError() { if (this.errorLabel == null) { return; } this.errorLabel.setVisible(false); - this.errorLabel.setText("rfbvgregre"); + this.errorLabel.setText(""); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractTableFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractTableFieldBuilder.java index 29eeaefc..0c450f7c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractTableFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractTableFieldBuilder.java @@ -80,7 +80,6 @@ public abstract class AbstractTableFieldBuilder implements InputFieldBuilder { final TableContext tableContext = new TableContext( this.inputFieldBuilderSupplier, this.widgetFactory, - this.restService, attribute, viewContext); return tableContext; @@ -260,13 +259,7 @@ public abstract class AbstractTableFieldBuilder implements InputFieldBuilder { .forEach(i -> { final Map rowValues = indexMapping.get(i); values.add(rowValues); - // addTableRow(rowValues); }); -// for (int i = 0; i < rows.size(); i++) { -// final Map rowValues = indexMapping.get(i); -// values.add(rowValues); -// addTableRow(rowValues); -// } } protected void applyFormValues( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java index 3d558667..8d84850e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java @@ -85,6 +85,11 @@ public class PassworFieldBuilder implements InputFieldBuilder { final String pwd = passwordInput.getText(); final String confirm = confirmInput.getText(); + if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) { + System.out.println("*********************************** ignore Password set"); + return; + } + if (StringUtils.isBlank(pwd) && StringUtils.isBlank(confirm)) { return; } @@ -132,7 +137,7 @@ public class PassworFieldBuilder implements InputFieldBuilder { @Override protected void setValueToControl(final String value) { // TODO clarify setting some "fake" input when a password is set (like in config tool) - if (this.initValue != null) { + if (value != null) { this.control.setText(value); this.confirm.setText(value); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableContext.java index a9b6887a..a718d36a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableContext.java @@ -8,10 +8,8 @@ package ch.ethz.seb.sebserver.gui.service.examconfig.impl; -import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -20,16 +18,12 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; -import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues.TableValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation; import ch.ethz.seb.sebserver.gui.service.examconfig.InputField; import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder; import ch.ethz.seb.sebserver.gui.service.examconfig.ValueChangeListener; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigTableRowValues; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; public class TableContext { @@ -38,7 +32,6 @@ public class TableContext { private final InputFieldBuilderSupplier inputFieldBuilderSupplier; private final WidgetFactory widgetFactory; - private final RestService restService; public final ConfigurationAttribute attribute; public final Orientation orientation; @@ -50,13 +43,11 @@ public class TableContext { public TableContext( final InputFieldBuilderSupplier inputFieldBuilderSupplier, final WidgetFactory widgetFactory, - final RestService restService, final ConfigurationAttribute attribute, final ViewContext viewContext) { this.inputFieldBuilderSupplier = Objects.requireNonNull(inputFieldBuilderSupplier); this.widgetFactory = Objects.requireNonNull(widgetFactory); - this.restService = Objects.requireNonNull(restService); this.attribute = Objects.requireNonNull(attribute); this.viewContext = Objects.requireNonNull(viewContext); @@ -152,27 +143,6 @@ public class TableContext { return this.inputFieldBuilderSupplier.getInputFieldBuilder(attribute2, orientation); } - public Map getTableRowValues(final int index) { - return this.restService.getBuilder(GetExamConfigTableRowValues.class) - .withQueryParam( - Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID, - this.attribute.getModelId()) - .withQueryParam( - Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ID, - String.valueOf(this.getConfigurationId())) - .withQueryParam( - Domain.CONFIGURATION_VALUE.ATTR_LIST_INDEX, - String.valueOf(index)) - .call() - .get( - error -> log.error("Failed to get table row values: ", error), - () -> Collections.emptyList()) - .stream() - .collect(Collectors.toMap( - val -> val.attributeId, - val -> TableValue.of(val))); - } - public void registerInputField(final InputField inputField) { this.viewContext.registerInputField(inputField); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableRowFormBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableRowFormBuilder.java index 4cdbd105..a46f7418 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableRowFormBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableRowFormBuilder.java @@ -105,11 +105,15 @@ public class TableRowFormBuilder implements ModalInputDialogComposer inputFields.stream() - .map(field -> new TableValue( - field.getAttribute().id, - this.listIndex, - field.getValue())) - .collect(Collectors.toMap(tv -> tv.attributeId, Function.identity())); + .map(field -> (field.hasError()) + ? this.rowValues.get(field.getAttribute().id) + : new TableValue( + field.getAttribute().id, + this.listIndex, + field.getValue())) + .collect(Collectors.toMap( + tv -> tv.attributeId, + Function.identity())); } private InputField createInputField( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/AbstractDownloadServiceHandler.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/AbstractDownloadServiceHandler.java new file mode 100644 index 00000000..f23d18ab --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/AbstractDownloadServiceHandler.java @@ -0,0 +1,79 @@ +/* + * 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; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import ch.ethz.seb.sebserver.gbl.api.API; + +public abstract class AbstractDownloadServiceHandler implements DownloadServiceHandler { + + private static final Logger log = LoggerFactory.getLogger(AbstractDownloadServiceHandler.class); + + @Override + public void processDownload(final HttpServletRequest request, final HttpServletResponse response) { + try { + + final String downloadFileName = request.getParameter(DownloadService.DOWNLOAD_FILE_NAME); + if (StringUtils.isBlank(downloadFileName)) { + log.error( + "Mandatory downloadFileName parameter not found within HttpServletRequest. Download request is ignored"); + return; + } + + log.debug("download requested... trying to get needed parameter from request"); + + final String configId = request.getParameter(API.PARAM_MODEL_ID); + if (StringUtils.isBlank(configId)) { + log.error( + "Mandatory modelId parameter not found within HttpServletRequest. Download request is ignored"); + return; + } + + log.debug( + "Found modelId: {} for {} download. Trying to request webservice...", + configId, + downloadFileName); + + final byte[] configFile = webserviceCall(configId); + + if (configFile == null) { + log.error("No or empty download received from webservice. Download request is ignored"); + return; + } + + log.debug("Sucessfully downloaded from webservice. File size: {}", configFile.length); + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + response.setContentLength(configFile.length); + response.setHeader( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + downloadFileName + "\""); + + log.debug("Write the download data to response output"); + + response.getOutputStream().write(configFile); + + } catch (final Exception e) { + log.error( + "Unexpected error while trying to start download. The download is ignored. Cause: ", + e); + } + } + + protected abstract byte[] webserviceCall(String configId); + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadService.java index cc0791c8..25108563 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadService.java @@ -19,12 +19,15 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.service.ServiceHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; @Lazy @@ -36,13 +39,16 @@ public class DownloadService implements ServiceHandler { public static final String DOWNLOAD_SERVICE_NAME = "DOWNLOAD_SERVICE"; public static final String HANDLER_NAME_PARAMETER = "download-handler-name"; + public static final String DOWNLOAD_FILE_NAME = "download-file-name"; private final Map handler; protected DownloadService(final Collection handler) { this.handler = handler .stream() - .collect(Collectors.toMap(h -> h.getFileName(), Function.identity())); + .collect(Collectors.toMap( + h -> h.getClass().getSimpleName(), + Function.identity())); } @Override @@ -70,4 +76,27 @@ public class DownloadService implements ServiceHandler { .processDownload(request, response); } + public String createDownloadURL( + final String modelId, + final Class handlerClass, + final String downloadFileName) { + + final StringBuilder url = new StringBuilder() + .append(RWT.getServiceManager() + .getServiceHandlerUrl(DownloadService.DOWNLOAD_SERVICE_NAME)) + .append(Constants.FORM_URL_ENCODED_SEPARATOR) + .append(API.PARAM_MODEL_ID) + .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) + .append(modelId) + .append(Constants.FORM_URL_ENCODED_SEPARATOR) + .append(DownloadService.HANDLER_NAME_PARAMETER) + .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) + .append(handlerClass.getSimpleName()) + .append(Constants.FORM_URL_ENCODED_SEPARATOR) + .append(DownloadService.DOWNLOAD_FILE_NAME) + .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) + .append(downloadFileName); + return url.toString(); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadServiceHandler.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadServiceHandler.java index cbc4eb42..e3243106 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadServiceHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadServiceHandler.java @@ -13,8 +13,6 @@ import javax.servlet.http.HttpServletResponse; public interface DownloadServiceHandler { - String getFileName(); - void processDownload(final HttpServletRequest request, final HttpServletResponse response); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebClientConfigDownload.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebClientConfigDownload.java index a4b48a4b..3774d8cc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebClientConfigDownload.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebClientConfigDownload.java @@ -8,20 +8,10 @@ package ch.ethz.seb.sebserver.gui.service.remote; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang3.StringUtils; -import org.eclipse.rap.rwt.RWT; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.stereotype.Component; -import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; @@ -30,9 +20,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig. @Lazy @Component @GuiProfile -public class SebClientConfigDownload implements DownloadServiceHandler { - - private static final Logger log = LoggerFactory.getLogger(SebClientConfigDownload.class); +public class SebClientConfigDownload extends AbstractDownloadServiceHandler { private final RestService restService; public final String downloadFileName; @@ -46,68 +34,11 @@ public class SebClientConfigDownload implements DownloadServiceHandler { } @Override - public String getFileName() { - return this.downloadFileName; - } - - @Override - public void processDownload(final HttpServletRequest request, final HttpServletResponse response) { - try { - - log.debug("download requested... trying to get needed parameter from request"); - - final String configId = request.getParameter(API.PARAM_MODEL_ID); - if (StringUtils.isBlank(configId)) { - log.error( - "Mandatory modelId parameter not found within HttpServletRequest. Download request is ignored"); - return; - } - - log.debug("Found modelId: {} for {} download. Trying to request webservice...", configId, - this.downloadFileName); - - final byte[] configFile = this.restService.getBuilder(ExportClientConfig.class) - .withURIVariable(API.PARAM_MODEL_ID, configId) - .call() - .getOrThrow(); - - if (configFile == null) { - log.error("No or empty SEB Client configuration received from webservice. Download request is ignored"); - return; - } - - log.debug("Sucessfully get SEB Client configuration from webservice. File size: {}", configFile.length); - - response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); - response.setContentLength(configFile.length); - response.setHeader( - HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"" + this.downloadFileName + "\""); - - log.debug("Write the SEB Client configuration to response output"); - - response.getOutputStream().write(configFile); - - } catch (final Exception e) { - log.error( - "Unexpected error while trying to start SEB Client configuration download. The download is ignored. Cause: ", - e); - } - } - - public String downloadSEBClientConfigURL(final String modelId) { - final StringBuilder url = new StringBuilder() - .append(RWT.getServiceManager() - .getServiceHandlerUrl(DownloadService.DOWNLOAD_SERVICE_NAME)) - .append(Constants.FORM_URL_ENCODED_SEPARATOR) - .append(API.PARAM_MODEL_ID) - .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) - .append(modelId) - .append(Constants.FORM_URL_ENCODED_SEPARATOR) - .append(DownloadService.HANDLER_NAME_PARAMETER) - .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) - .append(this.downloadFileName); - return url.toString(); + protected byte[] webserviceCall(final String modelId) { + return this.restService.getBuilder(ExportClientConfig.class) + .withURIVariable(API.PARAM_MODEL_ID, modelId) + .call() + .getOrThrow(); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebExamConfigDownload.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebExamConfigDownload.java new file mode 100644 index 00000000..2f5981e3 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebExamConfigDownload.java @@ -0,0 +1,38 @@ +/* + * 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; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportPlainXML; + +@Lazy +@Component +@GuiProfile +public class SebExamConfigDownload extends AbstractDownloadServiceHandler { + + private final RestService restService; + + protected SebExamConfigDownload(final RestService restService) { + this.restService = restService; + } + + @Override + protected byte[] webserviceCall(final String modelId) { + return this.restService.getBuilder(ExportPlainXML.class) + .withURIVariable(API.PARAM_MODEL_ID, modelId) + .call() + .getOrThrow(); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/AbstractExportCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/AbstractExportCall.java new file mode 100644 index 00000000..fc26f34c --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/AbstractExportCall.java @@ -0,0 +1,52 @@ +/* + * 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; + +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; + +import ch.ethz.seb.sebserver.gbl.util.Result; + +public abstract class AbstractExportCall extends RestCall { + + protected AbstractExportCall( + final TypeKey typeKey, + final HttpMethod httpMethod, + final MediaType contentType, + final String path) { + + super(typeKey, httpMethod, contentType, path); + } + + @Override + protected Result exchange(final RestCallBuilder builder) { + try { + final ResponseEntity responseEntity = this.restService + .getWebserviceAPIRestTemplate() + .exchange( + builder.buildURI(), + this.httpMethod, + builder.buildRequestEntity(), + byte[].class, + builder.getURIVariables()); + + if (responseEntity.getStatusCode() == HttpStatus.OK) { + return Result.of(responseEntity.getBody()); + } + + return Result.ofRuntimeError( + "Error while trying to export from webservice. Response: " + responseEntity); + } catch (final Throwable t) { + return Result.ofError(t); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/ExportClientConfig.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/ExportClientConfig.java index 9a875e6f..b6e314f7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/ExportClientConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/ExportClientConfig.java @@ -10,9 +10,7 @@ package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.type.TypeReference; @@ -20,48 +18,24 @@ 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.profile.GuiProfile; -import ch.ethz.seb.sebserver.gbl.util.Result; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.AbstractExportCall; @Lazy @Component @GuiProfile -public class ExportClientConfig extends RestCall { +public class ExportClientConfig extends AbstractExportCall { protected ExportClientConfig() { super(new TypeKey<>( CallType.UNDEFINED, - EntityType.INSTITUTION, + EntityType.SEB_CLIENT_CONFIGURATION, new TypeReference() { }), HttpMethod.GET, MediaType.APPLICATION_FORM_URLENCODED, - API.SEB_CLIENT_CONFIG_ENDPOINT - + API.SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT - + API.MODEL_ID_VAR_PATH_SEGMENT); - } - - @Override - protected Result exchange(final RestCallBuilder builder) { - try { - final ResponseEntity responseEntity = this.restService - .getWebserviceAPIRestTemplate() - .exchange( - builder.buildURI(), - this.httpMethod, - builder.buildRequestEntity(), - byte[].class, - builder.getURIVariables()); - - if (responseEntity.getStatusCode() == HttpStatus.OK) { - return Result.of(responseEntity.getBody()); - } - - return Result.ofRuntimeError( - "Error while trying to export SEB Config from webservice. Response: " + responseEntity); - } catch (final Throwable t) { - return Result.ofError(t); - } + API.SEB_CLIENT_CONFIG_ENDPOINT + + API.SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT + + API.MODEL_ID_VAR_PATH_SEGMENT); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigTableRowValues.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/ExportPlainXML.java similarity index 60% rename from src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigTableRowValues.java rename to src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/ExportPlainXML.java index d38e4259..c0b2ee9b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigTableRowValues.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/ExportPlainXML.java @@ -8,8 +8,6 @@ package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig; -import java.util.Collection; - import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -19,24 +17,25 @@ 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.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.AbstractExportCall; @Lazy @Component @GuiProfile -public class GetExamConfigTableRowValues extends RestCall> { +public class ExportPlainXML extends AbstractExportCall { - protected GetExamConfigTableRowValues() { + protected ExportPlainXML() { super(new TypeKey<>( - CallType.GET_LIST, - EntityType.CONFIGURATION_VALUE, - new TypeReference>() { + CallType.UNDEFINED, + EntityType.CONFIGURATION_NODE, + new TypeReference() { }), HttpMethod.GET, MediaType.APPLICATION_FORM_URLENCODED, - API.CONFIGURATION_VALUE_ENDPOINT + API.CONFIGURATION_TABLE_ROW_VALUE_PATH_SEGMENT); + API.CONFIGURATION_NODE_ENDPOINT + + API.CONFIGURATION_PLAIN_XML_DOWNLOAD_PATH_SEGMENT + + API.MODEL_ID_VAR_PATH_SEGMENT); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java index 61e6c0aa..6dd97621 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java @@ -183,34 +183,46 @@ public class EntityTable { } public void applyFilter() { - updateTableRows( - this.pageNumber, - this.pageSize, - this.sortColumn, - this.sortOrder); + try { + updateTableRows( + this.pageNumber, + this.pageSize, + this.sortColumn, + this.sortOrder); + } catch (final Exception e) { + log.error("Unexpected error while trying to apply filter: ", e); + } } public void applySort(final String columnName) { - this.sortColumn = columnName; - this.sortOrder = PageSortOrder.ASCENDING; + try { + this.sortColumn = columnName; + this.sortOrder = PageSortOrder.ASCENDING; - updateTableRows( - this.pageNumber, - this.pageSize, - this.sortColumn, - this.sortOrder); + updateTableRows( + this.pageNumber, + this.pageSize, + this.sortColumn, + this.sortOrder); + } catch (final Exception e) { + log.error("Unexpected error while trying to apply sort: ", e); + } } public void changeSortOrder() { - this.sortOrder = (this.sortOrder == PageSortOrder.ASCENDING) - ? PageSortOrder.DESCENDING - : PageSortOrder.ASCENDING; + try { + this.sortOrder = (this.sortOrder == PageSortOrder.ASCENDING) + ? PageSortOrder.DESCENDING + : PageSortOrder.ASCENDING; - updateTableRows( - this.pageNumber, - this.pageSize, - this.sortColumn, - this.sortOrder); + updateTableRows( + this.pageNumber, + this.pageSize, + this.sortColumn, + this.sortOrder); + } catch (final Exception e) { + log.error("Unexpected error while trying to apply sort: ", e); + } } public EntityKey getSingleSelection() { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableFilter.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableFilter.java index 8b20c511..a290dd30 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableFilter.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableFilter.java @@ -292,6 +292,7 @@ public class TableFilter { } } + // NOTE: SWT DateTime month-number starting with 0 and joda DateTime with 1! private class Date extends FilterComponent { private DateTime selector; @@ -310,7 +311,8 @@ public class TableFilter { @Override FilterComponent reset() { final org.joda.time.DateTime now = org.joda.time.DateTime.now(DateTimeZone.UTC); - this.selector.setDate(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth()); + + this.selector.setDate(now.getYear(), now.getMonthOfYear() - 1, now.getDayOfMonth()); return this; } @@ -319,8 +321,9 @@ public class TableFilter { if (this.selector != null) { final org.joda.time.DateTime date = org.joda.time.DateTime.now(DateTimeZone.UTC) .withYear(this.selector.getYear()) - .withMonthOfYear(this.selector.getMonth()) - .withDayOfMonth(this.selector.getDay()); + .withMonthOfYear(this.selector.getMonth() + 1) + .withDayOfMonth(this.selector.getDay()) + .withTimeAtStartOfDay(); return date.toString(Constants.STANDARD_DATE_TIME_FORMATTER); } else { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index e9145a10..95a43791 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -75,6 +75,7 @@ public class WidgetFactory { YES("yes.png"), NO("no.png"), SAVE("save.png"), + EXPORT("export.png"), NEW("new.png"), DELETE("delete.png"), SEARCH("lens.png"), diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationAttributeDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationAttributeDAO.java index 051ee966..55ba826d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationAttributeDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationAttributeDAO.java @@ -8,8 +8,13 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao; +import java.util.Collection; + import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.util.Result; public interface ConfigurationAttributeDAO extends EntityDAO { + Result> getAllRootAttributes(); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java index 83367c88..fc04f059 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java @@ -42,6 +42,10 @@ public interface ConfigurationDAO extends EntityDAO saveToHistory(Long configurationNodeId); + /** Can be used to reset the current follow-up configuration back to the last saved version in the history + * + * @param configurationNodeId ConfigurationNode identifier to apply the undo on + * @return the current and reseted follow-up version */ Result undo(Long configurationNodeId); /** Restores the current follow-up Configuration to the values of a given Configuration @@ -52,6 +56,10 @@ public interface ConfigurationDAO extends EntityDAO restoreToVersion(Long configurationNodeId, Long configId); - Result getFollowupConfiguration(String configNodeId); + /** Use this to get the follow-up configuration for a specified configuration node. + * + * @param configNodeId ConfigurationNode identifier to get the current follow-up configuration from + * @return the current follow-up configuration */ + Result getFollowupConfiguration(Long configNodeId); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java index 138b02d1..d72200b9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java @@ -9,6 +9,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao; import java.util.Collection; +import java.util.List; import java.util.Set; import ch.ethz.seb.sebserver.gbl.model.EntityKey; @@ -23,20 +24,48 @@ public interface ConfigurationValueDAO extends EntityDAO> delete(final Set all) { throw new UnsupportedOperationException( - "Deletion is not supported for ConfigurationValue. A ConfigurationValue get automatically deleted on deletion of a Configuration"); + "Deletion is not supported for ConfigurationValue. A ConfigurationValue get " + + "automatically deleted on deletion of a Configuration"); } - Result getTableValues( - final Long institutionId, - final Long configurationId, - final Long attributeId); + /** Use this to get all ConfigurationValue for a specific configuration and for a all + * root attributes that are not child attributes. + * + * @param institutionId the institution identifier + * @param configurationId the configuration identifier + * @return all ConfigurationValue from given configuration and all root attributes */ + Result> allRootAttributeValues( + Long institutionId, + Long configurationId); + /** Use this to get ConfigurationTableValues for a specified table attribute and configuration. + * + * @param institutionId the institution identifier + * @param configurationId the configuration identifier + * @param attributeId the table attribute to get all values for + * @return ConfigurationTableValues containing all values of specified table attribute and configuration on the + * TableValue format */ + Result getTableValues( + Long institutionId, + Long configurationId, + Long attributeId); + + /** Use this to get an ordered list of all ConfigurationValue of a table attribute on a specified configuration. + * The list is ordered within the row/list index + * + * @param institutionId the institution identifier + * @param configurationId the configuration identifier + * @param attributeId the table attribute to get all values for + * @return an ordered list of all ConfigurationValue of a table attribute on a specified configuration */ + Result>> getOrderedTableValues( + Long institutionId, + Long configurationId, + Long attributeId); + + /** Use this to save all values of a table attribute. + * + * @param value the ConfigurationTableValues instance containing all actual table attribute and value information + * @return the saved table values of the attribute and configuration */ Result saveTableValues(ConfigurationTableValues value); - Result> getTableRowValues( - final Long institutionId, - final Long configurationId, - final Long attributeId, - final Integer rowIndex); - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java index 5a11668a..f8a9b59c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java @@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.util.Utils; @@ -88,7 +89,7 @@ public class FilterMap extends POSTMapper { } public DateTime getSebClientConfigFromTime() { - return getQuizFromTime(); + return Utils.toDateTime(getString(SebClientConfig.FILTER_ATTR_CREATION_DATE)); } public Long getLmsSetupId() { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationAttributeDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationAttributeDAOImpl.java index 337a1a1b..dbab9327 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationAttributeDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationAttributeDAOImpl.java @@ -110,6 +110,22 @@ public class ConfigurationAttributeDAOImpl implements ConfigurationAttributeDAO .collect(Collectors.toList())); } + @Override + @Transactional(readOnly = true) + public Result> getAllRootAttributes() { + return Result.tryCatch(() -> this.configurationAttributeRecordMapper + .selectByExample() + .where( + ConfigurationAttributeRecordDynamicSqlSupport.parentId, + SqlBuilder.isNull()) + .build() + .execute() + .stream() + .map(ConfigurationAttributeDAOImpl::toDomainModel) + .flatMap(DAOLoggingSupport::logAndSkipOnError) + .collect(Collectors.toList())); + } + @Override @Transactional public Result createNew(final ConfigurationAttribute data) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java index 26274530..5680e627 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java @@ -120,12 +120,12 @@ public class ConfigurationDAOImpl implements ConfigurationDAO { @Override @Transactional(readOnly = true) - public Result getFollowupConfiguration(final String configNodeId) { + public Result getFollowupConfiguration(final Long configNodeId) { return Result.tryCatch(() -> { return this.configurationRecordMapper.selectByExample() .where( ConfigurationRecordDynamicSqlSupport.configurationNodeId, - isEqualTo(Long.parseLong(configNodeId))) + isEqualTo(configNodeId)) .and( ConfigurationRecordDynamicSqlSupport.followup, isEqualTo(BooleanUtils.toInteger(true))) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java index 6f34f657..44ef6317 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java @@ -13,7 +13,7 @@ import static org.mybatis.dynamic.sql.SqlBuilder.isIn; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -121,6 +121,35 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO { }); } + @Override + @Transactional(readOnly = true) + public Result> allRootAttributeValues( + final Long institutionId, + final Long configurationId) { + + return Result.tryCatch(() -> this.configurationValueRecordMapper + .selectByExample() + .join(ConfigurationAttributeRecordDynamicSqlSupport.configurationAttributeRecord) + .on( + ConfigurationAttributeRecordDynamicSqlSupport.id, + SqlBuilder.equalTo(ConfigurationValueRecordDynamicSqlSupport.configurationAttributeId)) + .where( + ConfigurationValueRecordDynamicSqlSupport.institutionId, + SqlBuilder.isEqualToWhenPresent(institutionId)) + .and( + ConfigurationValueRecordDynamicSqlSupport.configurationId, + SqlBuilder.isEqualTo(configurationId)) + .and( + ConfigurationAttributeRecordDynamicSqlSupport.parentId, + SqlBuilder.isNull()) + .build() + .execute() + .stream() + .map(ConfigurationValueDAOImpl::toDomainModel) + .flatMap(DAOLoggingSupport::logAndSkipOnError) + .collect(Collectors.toList())); + } + @Override @Transactional public Result createNew(final ConfigurationValue data) { @@ -230,41 +259,72 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO { } @Override - public Result> getTableRowValues( + @Transactional(readOnly = true) + public Result>> getOrderedTableValues( final Long institutionId, final Long configurationId, - final Long attributeId, - final Integer rowIndex) { + final Long attributeId) { - return attributeRecordById(attributeId) - .flatMap(this::getAttributeMapping) - .map(attributeMapping -> { + return Result.tryCatch(() -> { - if (attributeMapping == null || attributeMapping.isEmpty()) { - return Collections.emptyList(); - } + final List attributes = this.configurationAttributeRecordMapper + .selectByExample() + .where( + ConfigurationAttributeRecordDynamicSqlSupport.parentId, + SqlBuilder.isEqualTo(attributeId)) + .build() + .execute() + .stream() + .sorted((r1, r2) -> r1.getName().compareToIgnoreCase(r2.getName())) + .collect(Collectors.toList()); - // get all values of the table for specified row - return this.configurationValueRecordMapper.selectByExample() - .where( - ConfigurationValueRecordDynamicSqlSupport.institutionId, - isEqualTo(institutionId)) - .and( - ConfigurationValueRecordDynamicSqlSupport.configurationId, - isEqualTo(configurationId)) - .and( - ConfigurationValueRecordDynamicSqlSupport.listIndex, - isEqualTo(rowIndex)) - .and( - ConfigurationValueRecordDynamicSqlSupport.configurationAttributeId, - SqlBuilder.isIn(new ArrayList<>(attributeMapping.keySet()))) - .build() - .execute() - .stream() - .map(ConfigurationValueDAOImpl::toDomainModel) - .flatMap(DAOLoggingSupport::logAndSkipOnError) - .collect(Collectors.toList()); - }); + final Map> indexMapping = new HashMap<>(); + this.configurationValueRecordMapper + .selectByExample() + .join(ConfigurationAttributeRecordDynamicSqlSupport.configurationAttributeRecord) + .on( + ConfigurationAttributeRecordDynamicSqlSupport.id, + SqlBuilder.equalTo(ConfigurationValueRecordDynamicSqlSupport.configurationAttributeId)) + .where( + ConfigurationValueRecordDynamicSqlSupport.institutionId, + isEqualTo(institutionId)) + .and( + ConfigurationValueRecordDynamicSqlSupport.configurationId, + isEqualTo(configurationId)) + .and( + ConfigurationAttributeRecordDynamicSqlSupport.parentId, + SqlBuilder.isEqualTo(attributeId)) + .build() + .execute() + .stream() + .forEach(rec -> { + final Map rowValues = indexMapping.computeIfAbsent( + rec.getListIndex(), + key -> new HashMap<>()); + rowValues.put( + rec.getConfigurationAttributeId(), + ConfigurationValueDAOImpl + .toDomainModel(rec) + .getOrThrow()); + }); + + final List> result = new ArrayList<>(); + final List rows = new ArrayList<>(indexMapping.keySet()); + rows.sort((i1, i2) -> i1.compareTo(i2)); + rows + .stream() + .forEach(i -> { + + final Map rowValuesMapping = indexMapping.get(i); + final List rowValues = attributes + .stream() + .map(attr -> rowValuesMapping.get(attr.getId())) + .collect(Collectors.toList()); + result.add(rowValues); + }); + + return result; + }); } // get all attributes of the table (columns) mapped to attribute id diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ConfigurationValueValidator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ConfigurationValueValidator.java index 58e78d46..b4625c61 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ConfigurationValueValidator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ConfigurationValueValidator.java @@ -24,7 +24,7 @@ public interface ConfigurationValueValidator { default void throwValidationError( final ConfigurationValue value, - final ConfigurationAttribute attribute) { + final ConfigurationAttribute attribute) throws FieldValidationException { throw new FieldValidationException( attribute.name, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebClientConfigService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebClientConfigService.java index aca50b24..8efa1c25 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebClientConfigService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebClientConfigService.java @@ -16,12 +16,11 @@ import ch.ethz.seb.sebserver.gbl.util.Result; public interface SebClientConfigService { static String SEB_CLIENT_CONFIG_EXAMPLE_XML = - "\r\n" - + - "\r\n" + - " \r\n" + + " \r\n" + " sebMode\r\n" + " 1\r\n" + + " sebConfigPurpose\r\n" + + " 1" + " sebServerFallback\r\n" + " \r\n" + " sebServerURL\r\n" + @@ -37,8 +36,7 @@ public interface SebClientConfigService { " apiDiscovery\r\n" + " %s\r\n" + " \r\n" + - " \r\n" + - ""; + " \r\n"; String getServerURL(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebExamConfigService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebExamConfigService.java index 5fcaefd0..d7d0ec3a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebExamConfigService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebExamConfigService.java @@ -10,17 +10,21 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig; import java.io.OutputStream; +import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; +/** The base interface and service for all SEB Exam Configuration related functionality. */ public interface SebExamConfigService { - void validate(ConfigurationValue value); + void validate(ConfigurationValue value) throws FieldValidationException; - void validate(ConfigurationTableValues tableValue); + void validate(ConfigurationTableValues tableValue) throws FieldValidationException; - void exportXML(OutputStream out, Long configurationNodeId); + void exportPlainXML(OutputStream out, Long institutionId, Long configurationNodeId); void exportForExam(OutputStream out, Long configExamMappingId); + String generateConfigKey(Long configurationNodeId); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverter.java index c2da8166..ae64ed3e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverter.java @@ -25,12 +25,13 @@ public interface XMLValueConverter { void convertToXML( OutputStream out, ConfigurationAttribute attribute, - ConfigurationValue value) throws IOException; + ConfigurationValue value, + XMLValueConverterService xmlValueConverterService) throws IOException; default String extractName(final ConfigurationAttribute attribute) { final int lastIndexOf = attribute.name.lastIndexOf('.'); if (lastIndexOf > 0) { - return attribute.name.substring(lastIndexOf, attribute.name.length() - 1); + return attribute.name.substring(lastIndexOf + 1, attribute.name.length()); } else { return attribute.name; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverterService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverterService.java new file mode 100644 index 00000000..e9d6b889 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/XMLValueConverterService.java @@ -0,0 +1,17 @@ +/* + * 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.webservice.servicelayer.sebconfig; + +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; + +public interface XMLValueConverterService { + + XMLValueConverter getXMLConverter(ConfigurationAttribute attribute); + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebClientConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebClientConfigServiceImpl.java index 55f7aeb2..618441a5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebClientConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebClientConfigServiceImpl.java @@ -13,6 +13,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.UUID; @@ -33,6 +34,7 @@ import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.util.UriComponentsBuilder; import ch.ethz.seb.sebserver.WebSecurityConfig; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.model.institution.Institution; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; @@ -66,6 +68,7 @@ public class SebClientConfigServiceImpl implements SebClientConfigService { private final String httpScheme; private final String serverAddress; private final String serverPort; + private final String discoveryEndpoint; protected SebClientConfigServiceImpl( final InstitutionDAO institutionDAO, @@ -75,7 +78,8 @@ public class SebClientConfigServiceImpl implements SebClientConfigService { final ZipService zipService, @Value("${sebserver.webservice.http.scheme}") final String httpScheme, @Value("${server.address}") final String serverAddress, - @Value("${server.port}") final String serverPort) { + @Value("${server.port}") final String serverPort, + @Value("${sebserver.webservice.api.exam.endpoint.discovery}") final String discoveryEndpoint) { this.institutionDAO = institutionDAO; this.sebClientConfigDAO = sebClientConfigDAO; @@ -85,6 +89,7 @@ public class SebClientConfigServiceImpl implements SebClientConfigService { this.httpScheme = httpScheme; this.serverAddress = serverAddress; this.serverPort = serverPort; + this.discoveryEndpoint = discoveryEndpoint; } @Override @@ -182,14 +187,22 @@ public class SebClientConfigServiceImpl implements SebClientConfigService { String.valueOf(config.institutionId), plainClientId, plainClientSecret, - "TODO:/exam-api/discovery"); + this.getServerURL() + this.discoveryEndpoint); PipedOutputStream pOut = null; PipedInputStream pIn = null; + try { // zip the plain text - final InputStream plainIn = IOUtils.toInputStream(plainTextConfig, "UTF-8"); + final InputStream plainIn = IOUtils.toInputStream( + Constants.XML_VERSION_HEADER + + Constants.XML_DOCTYPE_HEADER + + Constants.XML_PLIST_START_V1 + + plainTextConfig + + Constants.XML_PLIST_END, + StandardCharsets.UTF_8.name()); + pOut = new PipedOutputStream(); pIn = new PipedInputStream(pOut); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebExamConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebExamConfigServiceImpl.java index 18ac19b9..5e29a13e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebExamConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebExamConfigServiceImpl.java @@ -8,46 +8,95 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; +import java.io.IOException; import java.io.OutputStream; import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; @Lazy @Service @WebServiceProfile -public class SebExamConfigServiceImpl implements SebExamConfigService { +public class SebExamConfigServiceImpl implements SebExamConfigService, XMLValueConverterService { private static final Logger log = LoggerFactory.getLogger(SebExamConfigServiceImpl.class); private final ConfigurationAttributeDAO configurationAttributeDAO; + private final ConfigurationValueDAO configurationValueDAO; + private final ConfigurationDAO configurationDAO; private final Collection validators; - private final Collection converters; + private final Map convertersByAttributeName; + private final Map convertersByAttributeType; protected SebExamConfigServiceImpl( final ConfigurationAttributeDAO configurationAttributeDAO, + final ConfigurationValueDAO configurationValueDAO, + final ConfigurationDAO configurationDAO, final Collection validators, final Collection converters) { this.configurationAttributeDAO = configurationAttributeDAO; + this.configurationValueDAO = configurationValueDAO; + this.configurationDAO = configurationDAO; this.validators = validators; - this.converters = converters; + this.convertersByAttributeName = new HashMap<>(); + this.convertersByAttributeType = new HashMap<>(); + for (final XMLValueConverter converter : converters) { + if (StringUtils.isNoneBlank(converter.name())) { + this.convertersByAttributeName.put(converter.name(), converter); + } + + for (final AttributeType aType : converter.types()) { + if (this.convertersByAttributeType.containsKey(aType)) { + log.warn( + "Unexpected state in inititalization: A XMLValueConverter for AttributeType {} exists already: {}", + aType, + converter); + } + this.convertersByAttributeType.put(aType, converter); + } + } } @Override - public void validate(final ConfigurationValue value) { + public XMLValueConverter getXMLConverter(final ConfigurationAttribute attribute) { + if (this.convertersByAttributeName.containsKey(attribute.name)) { + return this.convertersByAttributeName.get(attribute.name); + } + + if (this.convertersByAttributeType.containsKey(attribute.type)) { + return this.convertersByAttributeType.get(attribute.type); + } + + throw new IllegalStateException("No XMLValueConverter found for attribute: " + attribute); + } + + @Override + public void validate(final ConfigurationValue value) throws FieldValidationException { if (value == null) { log.warn("Validate called with null reference. Ignore this and skip validation"); return; @@ -65,15 +114,79 @@ public class SebExamConfigServiceImpl implements SebExamConfigService { } @Override - public void validate(final ConfigurationTableValues tableValue) { + public void validate(final ConfigurationTableValues tableValue) throws FieldValidationException { // TODO Auto-generated method stub - + throw new UnsupportedOperationException("TODO"); } @Override - public void exportXML(final OutputStream out, final Long configurationNodeId) { - // TODO Auto-generated method stub + public void exportPlainXML( + final OutputStream out, + final Long institutionId, + final Long configurationNodeId) { + // get all defined root configuration attributes + final Map attributes = this.configurationAttributeDAO.getAllRootAttributes() + .getOrThrow() + .stream() + .collect(Collectors.toMap( + ConfigurationAttribute::getId, + Function.identity())); + + final List sortedAttributes = attributes + .values() + .stream() + .sorted() + .collect(Collectors.toList()); + + // get follow-up configurationId for given configurationNodeId + final Long configurationId = this.configurationDAO + .getFollowupConfiguration(configurationNodeId) + .getOrThrow().id; + + // get all values for that attributes for given configurationId + final Map values = this.configurationValueDAO + .allRootAttributeValues(institutionId, configurationId) + .getOrThrow() + .stream() + .collect(Collectors.toMap( + ConfigurationValue::getAttributeId, + Function.identity())); + + try { + // write headers + out.write(Constants.XML_VERSION_HEADER_UTF_8); + out.write(Constants.XML_DOCTYPE_HEADER_UTF_8); + + // plist open + out.write(Constants.XML_PLIST_START_V1_UTF_8); + out.write(Constants.XML_DICT_START_UTF_8); + + // write attributes + for (final ConfigurationAttribute attribute : sortedAttributes) { + final ConfigurationValue configurationValue = values.get(attribute.id); + if (configurationValue != null) { + this.getXMLConverter(attribute).convertToXML( + out, + attribute, + configurationValue, + this); + } + } + + // plist close + out.write(Constants.XML_DICT_END_UTF_8); + out.write(Constants.XML_PLIST_END_UTF_8); + out.flush(); + + } catch (final IOException e) { + log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e); + try { + out.flush(); + } catch (final IOException e1) { + log.error("Unable to flush output stream after error"); + } + } } @Override @@ -82,4 +195,10 @@ public class SebExamConfigServiceImpl implements SebExamConfigService { } + @Override + public String generateConfigKey(final Long configurationNodeId) { + // TODO https://www.safeexambrowser.org/developer/seb-config-key.html + throw new UnsupportedOperationException("TODO"); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/ArrayOfStringConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/ArrayOfStringConverter.java new file mode 100644 index 00000000..a96abcfe --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/ArrayOfStringConverter.java @@ -0,0 +1,79 @@ +/* + * 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.webservice.servicelayer.sebconfig.impl.converter; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; + +@Lazy +@Component +@WebServiceProfile +public class ArrayOfStringConverter implements XMLValueConverter { + + public static final String ATTRIBUTE_NAME = "ExceptionsList"; + + public static final Set SUPPORTED_TYPES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList( + AttributeType.MULTI_CHECKBOX_SELECTION, + AttributeType.MULTI_SELECTION))); + + private static final String TEMPLATE = "%s"; + private static final String TEMPLATE_ENTRY = "%s"; + private static final String TEMPLATE_EMPTY = "%s"; + + @Override + public Set types() { + return SUPPORTED_TYPES; + } + + @Override + public String name() { + return ATTRIBUTE_NAME; + } + + @Override + public void convertToXML( + final OutputStream out, + final ConfigurationAttribute attribute, + final ConfigurationValue value, + final XMLValueConverterService xmlValueConverterService) throws IOException { + + final String val = (value.value != null) ? value.value : attribute.getDefaultValue(); + if (StringUtils.isNoneBlank(val)) { + final String[] values = StringUtils.split(val, Constants.LIST_SEPARATOR); + final StringBuilder sb = new StringBuilder(); + sb.append(String.format(TEMPLATE, extractName(attribute))); + for (final String v : values) { + sb.append(String.format(TEMPLATE_ENTRY, v)); + } + sb.append(""); + out.write(Utils.toByteArray(sb.toString())); + } else { + out.write(Utils.toByteArray(String.format(TEMPLATE_EMPTY, extractName(attribute)))); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/BooleanConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/BooleanConverter.java index b2a90958..9e761ea7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/BooleanConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/BooleanConverter.java @@ -16,20 +16,28 @@ import java.util.HashSet; import java.util.Set; import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; +@Lazy +@Component +@WebServiceProfile public class BooleanConverter implements XMLValueConverter { public static final Set SUPPORTED_TYPES = Collections.unmodifiableSet( new HashSet<>(Arrays.asList( AttributeType.CHECKBOX))); - private static final StringBuilder BUILDER = new StringBuilder(); + private static final String TEMPLATE = "%s<%s />"; @Override public String name() { @@ -45,14 +53,15 @@ public class BooleanConverter implements XMLValueConverter { public void convertToXML( final OutputStream out, final ConfigurationAttribute attribute, - final ConfigurationValue value) throws IOException { + final ConfigurationValue value, + final XMLValueConverterService xmlValueConverterService) throws IOException { + + out.write(Utils.toByteArray( + String.format( + TEMPLATE, + extractName(attribute), + (value.value != null) ? value.value : Constants.FALSE_STRING))); - BUILDER.setLength(0); - out.write(Utils.toByteArray(BUILDER.append("") - .append(extractName(attribute)) - .append("<") - .append((value.value != null) ? value.value : "false") - .append(" />"))); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/IntegerConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/IntegerConverter.java new file mode 100644 index 00000000..e276be46 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/IntegerConverter.java @@ -0,0 +1,71 @@ +/* + * 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.webservice.servicelayer.sebconfig.impl.converter; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; + +@Lazy +@Component +@WebServiceProfile +public class IntegerConverter implements XMLValueConverter { + + public static final Set SUPPORTED_TYPES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList( + AttributeType.INTEGER, + AttributeType.SLIDER, + AttributeType.SINGLE_SELECTION, + AttributeType.RADIO_SELECTION))); + + private static final String TEMPLATE = "%s%s"; + private static final String TEMPLATE_EMPTY = "%s"; + + @Override + public Set types() { + return SUPPORTED_TYPES; + } + + @Override + public String name() { + return StringUtils.EMPTY; + } + + @Override + public void convertToXML( + final OutputStream out, + final ConfigurationAttribute attribute, + final ConfigurationValue value, + final XMLValueConverterService xmlValueConverterService) throws IOException { + + final String val = (value.value != null) ? value.value : attribute.getDefaultValue(); + if (StringUtils.isNoneBlank(val)) { + out.write(Utils.toByteArray(String.format(TEMPLATE, extractName(attribute), val))); + } else { + out.write(Utils.toByteArray(String.format(TEMPLATE_EMPTY, extractName(attribute)))); + } + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/KioskModeConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/KioskModeConverter.java index 63258039..842f02df 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/KioskModeConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/KioskModeConverter.java @@ -8,6 +8,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.converter; +import java.io.IOException; import java.io.OutputStream; import java.util.Collections; import java.util.Set; @@ -15,11 +16,14 @@ import java.util.Set; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; @Lazy @Component @@ -28,6 +32,8 @@ public class KioskModeConverter implements XMLValueConverter { public static final String NAME = "kioskMode"; + private static final String TEMPLATE = "createNewDesktop<%s />killExplorerShell<%s />"; + @Override public String name() { return NAME; @@ -42,10 +48,15 @@ public class KioskModeConverter implements XMLValueConverter { public void convertToXML( final OutputStream out, final ConfigurationAttribute attribute, - final ConfigurationValue value) { - - // TODO Auto-generated method stub + final ConfigurationValue value, + final XMLValueConverterService xmlValueConverterService) throws IOException { + final String val = value.getValue(); + out.write(Utils.toByteArray( + String.format( + TEMPLATE, + (val != null == "0".equals(val)) ? Constants.TRUE_STRING : Constants.FALSE_STRING, + (val != null == "1".equals(val)) ? Constants.TRUE_STRING : Constants.FALSE_STRING))); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/StringConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/StringConverter.java new file mode 100644 index 00000000..f4b48ee1 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/StringConverter.java @@ -0,0 +1,72 @@ +/* + * 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.webservice.servicelayer.sebconfig.impl.converter; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; + +@Lazy +@Component +@WebServiceProfile +public class StringConverter implements XMLValueConverter { + + public static final Set SUPPORTED_TYPES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList( + AttributeType.TEXT_FIELD, + AttributeType.TEXT_AREA, + AttributeType.PASSWORD_FIELD, + AttributeType.DECIMAL, + AttributeType.COMBO_SELECTION))); + + private static final String TEMPLATE = "%s%s"; + private static final String TEMPLATE_EMPTY = "%s"; + + @Override + public Set types() { + return SUPPORTED_TYPES; + } + + @Override + public String name() { + return StringUtils.EMPTY; + } + + @Override + public void convertToXML( + final OutputStream out, + final ConfigurationAttribute attribute, + final ConfigurationValue value, + final XMLValueConverterService xmlValueConverterService) throws IOException { + + final String val = (value.value != null) ? value.value : attribute.getDefaultValue(); + if (StringUtils.isNoneBlank(val)) { + out.write(Utils.toByteArray(String.format(TEMPLATE, extractName(attribute), val))); + } else { + out.write(Utils.toByteArray(String.format(TEMPLATE_EMPTY, extractName(attribute)))); + } + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java new file mode 100644 index 00000000..9e6e44ed --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java @@ -0,0 +1,144 @@ +/* + * 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.webservice.servicelayer.sebconfig.impl.converter; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverter; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.XMLValueConverterService; + +@Lazy +@Component +@WebServiceProfile +public class TableConverter implements XMLValueConverter { + + public static final Set SUPPORTED_TYPES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList( + AttributeType.TABLE, + AttributeType.INLINE_TABLE, + AttributeType.COMPOSITE_TABLE))); + + private static final String KEY_TEMPLATE = "%s"; + private static final byte[] ARRAY_START = Utils.toByteArray(""); + private static final byte[] ARRAY_END = Utils.toByteArray(""); + private static final byte[] DICT_START = Utils.toByteArray(""); + private static final byte[] DICT_END = Utils.toByteArray(""); + private static final byte[] EMPTY_ARRAY = Utils.toByteArray(""); + + private final ConfigurationAttributeDAO configurationAttributeDAO; + private final ConfigurationValueDAO configurationValueDAO; + + public TableConverter( + final ConfigurationAttributeDAO configurationAttributeDAO, + final ConfigurationValueDAO configurationValueDAO) { + + this.configurationAttributeDAO = configurationAttributeDAO; + this.configurationValueDAO = configurationValueDAO; + } + + @Override + public Set types() { + return SUPPORTED_TYPES; + } + + @Override + public String name() { + return StringUtils.EMPTY; + } + + @Override + public void convertToXML( + final OutputStream out, + final ConfigurationAttribute attribute, + final ConfigurationValue value, + final XMLValueConverterService xmlValueConverterService) throws IOException { + + out.write(Utils.toByteArray(String.format(KEY_TEMPLATE, extractName(attribute)))); + + final List> values = this.configurationValueDAO.getOrderedTableValues( + value.institutionId, + value.configurationId, + attribute.id).getOrThrow(); + + if (values == null || values.isEmpty()) { + out.write(EMPTY_ARRAY); + out.flush(); + return; + } + + if (attribute.type != AttributeType.COMPOSITE_TABLE) { + out.write(ARRAY_START); + } + + writeRows( + out, + getAttributes(attribute), + values, + xmlValueConverterService); + + if (attribute.type != AttributeType.COMPOSITE_TABLE) { + out.write(ARRAY_END); + } + + out.flush(); + } + + private Map getAttributes(final ConfigurationAttribute attribute) { + return this.configurationAttributeDAO + .allMatching(new FilterMap().putIfAbsent( + ConfigurationAttribute.FILTER_ATTR_PARENT_ID, + attribute.getModelId())) + .getOrThrow() + .stream() + .collect(Collectors.toMap( + attr -> attr.id, + Function.identity())); + + } + + private void writeRows( + final OutputStream out, + final Map attributeMap, + final List> values, + final XMLValueConverterService xmlValueConverterService) throws IOException { + + for (final List rowValues : values) { + out.write(DICT_START); + for (final ConfigurationValue value : rowValues) { + final ConfigurationAttribute attr = attributeMap.get(value.attributeId); + final XMLValueConverter converter = xmlValueConverterService.getXMLConverter(attr); + converter.convertToXML(out, attr, value, xmlValueConverterService); + } + out.write(DICT_END); + out.flush(); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java index 3c4e86f7..53570297 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java @@ -9,11 +9,15 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; import org.mybatis.dynamic.sql.SqlTable; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; @@ -25,10 +29,12 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNode import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.SEBServerUser; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; @WebServiceProfile @@ -37,6 +43,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe public class ConfigurationNodeController extends EntityController { private final ConfigurationDAO configurationDAO; + private final SebExamConfigService sebExamConfigService; protected ConfigurationNodeController( final AuthorizationService authorization, @@ -45,7 +52,8 @@ public class ConfigurationNodeController extends EntityController downloadPlainXMLConfig( + @PathVariable final Long modelId, + @RequestParam( + name = API.PARAM_INSTITUTION_ID, + required = true, + defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) { + + this.entityDAO.byPK(modelId) + .map(this.authorization::checkRead); + + final StreamingResponseBody stream = out -> this.sebExamConfigService + .exportPlainXML(out, institutionId, modelId); + + return new ResponseEntity<>(stream, HttpStatus.OK); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationValueController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationValueController.java index edef5793..eaf6af84 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationValueController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationValueController.java @@ -8,7 +8,6 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; -import java.util.Collection; import java.util.Objects; import javax.validation.Valid; @@ -108,32 +107,6 @@ public class ConfigurationValueController extends EntityController getTableRowValueBy( - @RequestParam( - name = Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID, - required = true) final Long attributeId, - @RequestParam( - name = Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ID, - required = true) final Long configurationId, - @RequestParam( - name = Domain.CONFIGURATION_VALUE.ATTR_LIST_INDEX, - required = true) final Integer rowIndex) { - - return this.configurationDAO.byPK(configurationId) - .flatMap(this.authorization::checkRead) - .flatMap(config -> this.configurationValueDAO.getTableRowValues( - config.institutionId, - configurationId, - attributeId, - rowIndex)) - .getOrThrow(); - } - @RequestMapping( path = API.CONFIGURATION_TABLE_VALUE_PATH_SEGMENT, method = RequestMethod.PUT, diff --git a/src/main/resources/config/application-demo.properties b/src/main/resources/config/application-demo.properties index c85c0bae..ac256f01 100644 --- a/src/main/resources/config/application-demo.properties +++ b/src/main/resources/config/application-demo.properties @@ -25,8 +25,6 @@ sebserver.webservice.api.pagination.maxPageSize=500 # comma separated list of known possible OpenEdX API access token request endpoints sebserver.lms.openedix.api.token.request.paths=/oauth2/access_token - - sebserver.gui.entrypoint=/gui sebserver.gui.webservice.protocol=http sebserver.gui.webservice.address=0.0.0.0 @@ -36,7 +34,10 @@ sebserver.gui.theme=css/sebserver.css sebserver.gui.list.page.size=20 sebserver.gui.date.displayformat=MM/dd/yyyy HH:mm sebserver.gui.date.displayformat.timezone=|ZZ -sebserver.gui.multilingual=true +sebserver.gui.multilingual=false sebserver.gui.languages=en +sebserver.gui.seb.client.config.download.filename=SebClientSettings.seb +sebserver.gui.seb.exam.config.download.filename=SebClientSettings.seb + diff --git a/src/main/resources/config/application-dev-gui.properties b/src/main/resources/config/application-dev-gui.properties index b06292db..eb7374ec 100644 --- a/src/main/resources/config/application-dev-gui.properties +++ b/src/main/resources/config/application-dev-gui.properties @@ -19,4 +19,5 @@ sebserver.gui.date.displayformat.timezone=|ZZ sebserver.gui.multilingual=true sebserver.gui.languages=en,de -sebserver.gui.seb.client.config.download.filename=SebClientSettings.seb \ No newline at end of file +sebserver.gui.seb.client.config.download.filename=SebClientSettings.seb +sebserver.gui.seb.exam.config.download.filename=SebClientSettings.seb \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 8a419c04..689baea2 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -391,6 +391,7 @@ sebserver.examconfig.action.saveToHistory=Save In History sebserver.examconfig.action.saveToHistory.success=Successfully saved in history sebserver.examconfig.action.undo=Undo sebserver.examconfig.action.undo.success=Successfully reverted to last saved state +sebserver.examconfig.action.export.plainxml=Export XML sebserver.examconfig.form.title.new=New Exam Configuration sebserver.examconfig.form.title=Exam Configuration diff --git a/src/main/resources/static/images/export.png b/src/main/resources/static/images/export.png new file mode 100644 index 0000000000000000000000000000000000000000..d6c0ec9b2cfd2c3fc68a2e35eba528b53eee85ed GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|&H|6fVg?3opai!N