SEBSERV-44 SEBSERV-45 export of plain XML exam config implemented

This commit is contained in:
anhefti 2019-06-05 09:58:59 +02:00
parent f95485fb7d
commit bbb15bba40
50 changed files with 1176 additions and 328 deletions

View file

@ -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 =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>";
public static final String XML_DOCTYPE_HEADER =
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">";
public static final String XML_PLIST_START_V1 =
"<plist version=\"1.0\">";
public static final String XML_PLIST_END =
"</plist>";
public static final String XML_DICT_START =
"<dict>";
public static final String XML_DICT_END =
"</dict>";
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);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"),

View file

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

View file

@ -89,13 +89,22 @@ public abstract class AbstractInputField<T extends Control> 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("");
}

View file

@ -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<Long, TableValue> rowValues = indexMapping.get(i);
values.add(rowValues);
// addTableRow(rowValues);
});
// for (int i = 0; i < rows.size(); i++) {
// final Map<Long, TableValue> rowValues = indexMapping.get(i);
// values.add(rowValues);
// addTableRow(rowValues);
// }
}
protected void applyFormValues(

View file

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

View file

@ -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<Long, TableValue> 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);
}

View file

@ -105,11 +105,15 @@ public class TableRowFormBuilder implements ModalInputDialogComposer<Map<Long, T
});
return () -> 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(

View file

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

View file

@ -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<String, DownloadServiceHandler> handler;
protected DownloadService(final Collection<DownloadServiceHandler> 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<? extends DownloadServiceHandler> 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();
}
}

View file

@ -13,8 +13,6 @@ import javax.servlet.http.HttpServletResponse;
public interface DownloadServiceHandler {
String getFileName();
void processDownload(final HttpServletRequest request, final HttpServletResponse response);
}

View file

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

View file

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

View file

@ -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<byte[]> {
protected AbstractExportCall(
final TypeKey<byte[]> typeKey,
final HttpMethod httpMethod,
final MediaType contentType,
final String path) {
super(typeKey, httpMethod, contentType, path);
}
@Override
protected Result<byte[]> exchange(final RestCallBuilder builder) {
try {
final ResponseEntity<byte[]> 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);
}
}
}

View file

@ -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<byte[]> {
public class ExportClientConfig extends AbstractExportCall {
protected ExportClientConfig() {
super(new TypeKey<>(
CallType.UNDEFINED,
EntityType.INSTITUTION,
EntityType.SEB_CLIENT_CONFIGURATION,
new TypeReference<byte[]>() {
}),
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<byte[]> exchange(final RestCallBuilder builder) {
try {
final ResponseEntity<byte[]> 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);
}
}

View file

@ -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<Collection<ConfigurationValue>> {
public class ExportPlainXML extends AbstractExportCall {
protected GetExamConfigTableRowValues() {
protected ExportPlainXML() {
super(new TypeKey<>(
CallType.GET_LIST,
EntityType.CONFIGURATION_VALUE,
new TypeReference<Collection<ConfigurationValue>>() {
CallType.UNDEFINED,
EntityType.CONFIGURATION_NODE,
new TypeReference<byte[]>() {
}),
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);
}
}

View file

@ -183,34 +183,46 @@ public class EntityTable<ROW extends Entity> {
}
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() {

View file

@ -292,6 +292,7 @@ public class TableFilter<ROW extends Entity> {
}
}
// 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<ROW extends Entity> {
@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<ROW extends Entity> {
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 {

View file

@ -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"),

View file

@ -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<ConfigurationAttribute, ConfigurationAttribute> {
Result<Collection<ConfigurationAttribute>> getAllRootAttributes();
}

View file

@ -42,6 +42,10 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
* @return the new follow-up Configuration model */
Result<Configuration> 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<Configuration> 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<Configuration, Configuration
* @return the follow-up Configuration with restored values */
Result<Configuration> restoreToVersion(Long configurationNodeId, Long configId);
Result<Configuration> 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<Configuration> getFollowupConfiguration(Long configNodeId);
}

View file

@ -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<ConfigurationValue, Con
@Override
default Result<Collection<EntityKey>> delete(final Set<EntityKey> 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<ConfigurationTableValues> 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<Collection<ConfigurationValue>> 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<ConfigurationTableValues> 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<List<List<ConfigurationValue>>> 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<ConfigurationTableValues> saveTableValues(ConfigurationTableValues value);
Result<Collection<ConfigurationValue>> getTableRowValues(
final Long institutionId,
final Long configurationId,
final Long attributeId,
final Integer rowIndex);
}

View file

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

View file

@ -110,6 +110,22 @@ public class ConfigurationAttributeDAOImpl implements ConfigurationAttributeDAO
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ConfigurationAttribute>> 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<ConfigurationAttribute> createNew(final ConfigurationAttribute data) {

View file

@ -120,12 +120,12 @@ public class ConfigurationDAOImpl implements ConfigurationDAO {
@Override
@Transactional(readOnly = true)
public Result<Configuration> getFollowupConfiguration(final String configNodeId) {
public Result<Configuration> 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)))

View file

@ -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<Collection<ConfigurationValue>> 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<ConfigurationValue> createNew(final ConfigurationValue data) {
@ -230,41 +259,72 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
}
@Override
public Result<Collection<ConfigurationValue>> getTableRowValues(
@Transactional(readOnly = true)
public Result<List<List<ConfigurationValue>>> 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<ConfigurationAttributeRecord> 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<Integer, Map<Long, ConfigurationValue>> 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<Long, ConfigurationValue> rowValues = indexMapping.computeIfAbsent(
rec.getListIndex(),
key -> new HashMap<>());
rowValues.put(
rec.getConfigurationAttributeId(),
ConfigurationValueDAOImpl
.toDomainModel(rec)
.getOrThrow());
});
final List<List<ConfigurationValue>> result = new ArrayList<>();
final List<Integer> rows = new ArrayList<>(indexMapping.keySet());
rows.sort((i1, i2) -> i1.compareTo(i2));
rows
.stream()
.forEach(i -> {
final Map<Long, ConfigurationValue> rowValuesMapping = indexMapping.get(i);
final List<ConfigurationValue> 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

View file

@ -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,

View file

@ -16,12 +16,11 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
public interface SebClientConfigService {
static String SEB_CLIENT_CONFIG_EXAMPLE_XML =
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"
+
"<plist version=\"1.0\">\r\n" +
" <dict>\r\n" +
" <dict>\r\n" +
" <key>sebMode</key>\r\n" +
" <integer>1</integer>\r\n" +
" <key>sebConfigPurpose</key>\r\n" +
" <integer>1</integer>" +
" <key>sebServerFallback</key>\r\n" +
" <true />\r\n" +
" <key>sebServerURL</key>\r\n" +
@ -37,8 +36,7 @@ public interface SebClientConfigService {
" <key>apiDiscovery</key>\r\n" +
" <string>%s</string>\r\n" +
" </dict>\r\n" +
" </dict>\r\n" +
"</plist>";
" </dict>\r\n";
String getServerURL();

View file

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

View file

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

View file

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

View file

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

View file

@ -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<ConfigurationValueValidator> validators;
private final Collection<XMLValueConverter> converters;
private final Map<String, XMLValueConverter> convertersByAttributeName;
private final Map<AttributeType, XMLValueConverter> convertersByAttributeType;
protected SebExamConfigServiceImpl(
final ConfigurationAttributeDAO configurationAttributeDAO,
final ConfigurationValueDAO configurationValueDAO,
final ConfigurationDAO configurationDAO,
final Collection<ConfigurationValueValidator> validators,
final Collection<XMLValueConverter> 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<Long, ConfigurationAttribute> attributes = this.configurationAttributeDAO.getAllRootAttributes()
.getOrThrow()
.stream()
.collect(Collectors.toMap(
ConfigurationAttribute::getId,
Function.identity()));
final List<ConfigurationAttribute> 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<Long, ConfigurationValue> 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");
}
}

View file

@ -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<AttributeType> SUPPORTED_TYPES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(
AttributeType.MULTI_CHECKBOX_SELECTION,
AttributeType.MULTI_SELECTION)));
private static final String TEMPLATE = "<key>%s</key><array>";
private static final String TEMPLATE_ENTRY = "<string>%s</string>";
private static final String TEMPLATE_EMPTY = "<key>%s</key><array></array>";
@Override
public Set<AttributeType> 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("</array>");
out.write(Utils.toByteArray(sb.toString()));
} else {
out.write(Utils.toByteArray(String.format(TEMPLATE_EMPTY, extractName(attribute))));
}
}
}

View file

@ -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<AttributeType> SUPPORTED_TYPES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(
AttributeType.CHECKBOX)));
private static final StringBuilder BUILDER = new StringBuilder();
private static final String TEMPLATE = "<key>%s</key><%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("<key>")
.append(extractName(attribute))
.append("<")
.append((value.value != null) ? value.value : "false")
.append(" />")));
}
}

View file

@ -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<AttributeType> SUPPORTED_TYPES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(
AttributeType.INTEGER,
AttributeType.SLIDER,
AttributeType.SINGLE_SELECTION,
AttributeType.RADIO_SELECTION)));
private static final String TEMPLATE = "<key>%s</key><integer>%s</integer>";
private static final String TEMPLATE_EMPTY = "<key>%s</key><integer />";
@Override
public Set<AttributeType> 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))));
}
}
}

View file

@ -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 = "<key>createNewDesktop</key><%s /><key>killExplorerShell</key><%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)));
}
}

View file

@ -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<AttributeType> 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 = "<key>%s</key><string>%s</string>";
private static final String TEMPLATE_EMPTY = "<key>%s</key><string />";
@Override
public Set<AttributeType> 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))));
}
}
}

View file

@ -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<AttributeType> SUPPORTED_TYPES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(
AttributeType.TABLE,
AttributeType.INLINE_TABLE,
AttributeType.COMPOSITE_TABLE)));
private static final String KEY_TEMPLATE = "<key>%s</key>";
private static final byte[] ARRAY_START = Utils.toByteArray("<array>");
private static final byte[] ARRAY_END = Utils.toByteArray("</array>");
private static final byte[] DICT_START = Utils.toByteArray("<dict>");
private static final byte[] DICT_END = Utils.toByteArray("</dict>");
private static final byte[] EMPTY_ARRAY = Utils.toByteArray("<array />");
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<AttributeType> 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<List<ConfigurationValue>> 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<Long, ConfigurationAttribute> 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<Long, ConfigurationAttribute> attributeMap,
final List<List<ConfigurationValue>> values,
final XMLValueConverterService xmlValueConverterService) throws IOException {
for (final List<ConfigurationValue> 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();
}
}
}

View file

@ -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<ConfigurationNode, ConfigurationNode> {
private final ConfigurationDAO configurationDAO;
private final SebExamConfigService sebExamConfigService;
protected ConfigurationNodeController(
final AuthorizationService authorization,
@ -45,7 +52,8 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService,
final BeanValidationService beanValidationService,
final ConfigurationDAO configurationDAO) {
final ConfigurationDAO configurationDAO,
final SebExamConfigService sebExamConfigService) {
super(authorization,
bulkActionService,
@ -55,6 +63,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
beanValidationService);
this.configurationDAO = configurationDAO;
this.sebExamConfigService = sebExamConfigService;
}
@Override
@ -75,16 +84,36 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Configuration getFollowup(@PathVariable final String modelId) {
public Configuration getFollowup(@PathVariable final Long configNodeId) {
this.entityDAO
.byModelId(modelId)
.byPK(configNodeId)
.flatMap(this::checkModifyAccess)
.getOrThrow();
return this.configurationDAO
.getFollowupConfiguration(modelId)
.getFollowupConfiguration(configNodeId)
.getOrThrow();
}
@RequestMapping(
path = API.CONFIGURATION_PLAIN_XML_DOWNLOAD_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<StreamingResponseBody> 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);
}
}

View file

@ -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<Configuration
.getOrThrow();
}
@RequestMapping(
path = API.CONFIGURATION_TABLE_ROW_VALUE_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Collection<ConfigurationValue> 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,

View file

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

View file

@ -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
sebserver.gui.seb.client.config.download.filename=SebClientSettings.seb
sebserver.gui.seb.exam.config.download.filename=SebClientSettings.seb

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B