SEBSERV-44 SEBSERV-45 exam config table implementation

This commit is contained in:
anhefti 2019-05-10 14:09:56 +02:00
parent e2b93e5529
commit 8867721a8a
56 changed files with 906 additions and 377 deletions

View file

@ -111,7 +111,8 @@
<linkXRef>false</linkXRef>
<excludes>
<exclude>**/batis/mapper/*.java</exclude>
<exclude>**/batis/model/*.java</exclude>
<exclude>**/batis/model/*.java</exclude>
<exclude name="UselessParentheses"/>
</excludes>
</configuration>
<executions>

View file

@ -71,6 +71,8 @@ public final class API {
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";

View file

@ -24,11 +24,17 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.util.Utils;
/** This class defines API error messages that are created and responded on error and/or exceptional
* cases within the web-service. */
public class APIMessage implements Serializable {
private static final long serialVersionUID = -6858683658311637361L;
/** An enumeration of error messages defining the error code, the HTTP status for the response
* and a short system message. This error message definition can be used to
* generate APIMessages for default errors. */
public enum ErrorMessage {
/** For every unknown or unspecific internal error */
GENERIC("0", HttpStatus.INTERNAL_SERVER_ERROR, "Generic error message"),
UNAUTHORIZED("1000", HttpStatus.UNAUTHORIZED, "UNAUTHORIZED"),
FORBIDDEN("1001", HttpStatus.FORBIDDEN, "FORBIDDEN"),
@ -89,12 +95,19 @@ public class APIMessage implements Serializable {
}
}
/** A specific message code that can be used to identify the type of message */
@JsonProperty("messageCode")
public final String messageCode;
/** A short system message that describes the cause */
@JsonProperty("systemMessage")
public final String systemMessage;
/** Message details */
@JsonProperty("details")
public final String details;
/** A list of additional attributes */
@JsonProperty("attributes")
public final List<String> attributes;
@ -137,6 +150,11 @@ public class APIMessage implements Serializable {
return this.attributes;
}
/** Use this as a conversion from a given FieldError of Spring to a APIMessage
* of type field validation.
*
* @param error FieldError instance
* @return converted APIMessage of type field validation */
public static final APIMessage fieldValidationError(final FieldError error) {
final String[] args = StringUtils.split(error.getDefaultMessage(), ":");
return ErrorMessage.FIELD_VALIDATION.of(error.toString(), args);
@ -165,6 +183,10 @@ public class APIMessage implements Serializable {
return builder.toString();
}
/** This exception can be internal used to wrap a created APIMessage
* within an Exception and throw. The Exception will be caught a the
* APIExceptionHandler endpoint. The APIMessage will be extracted
* and send as response. */
public static class APIMessageException extends RuntimeException {
private static final long serialVersionUID = 1453431210820677296L;
@ -196,6 +218,10 @@ public class APIMessage implements Serializable {
}
}
/** This is used as a field validation exception that creates a APIMessage of filed
* validation. The Exception will be caught a the
* APIExceptionHandler endpoint. The APIMessage will be extracted
* and send as response. */
public static class FieldValidationException extends RuntimeException {
private static final long serialVersionUID = 3324566460573096815L;

View file

@ -46,6 +46,8 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
* @param <T> The of the result of the suppling function */
public final class MemoizingCircuitBreaker<T> implements Supplier<Result<T>> {
// TODO considering invalidation time for memoizing
private static final Logger log = LoggerFactory.getLogger(MemoizingCircuitBreaker.class);
private final CircuitBreaker<T> delegate;

View file

@ -39,7 +39,7 @@ public enum AttributeType {
FILE_UPLOAD(BASE64_BINARY),
/** Table type is a list of composite */
/** Table type is a list of a composite of single types */
TABLE(COMPOSITE_LIST),
;

View file

@ -22,7 +22,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.CONFIGURATION_VALUE;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
@JsonIgnoreProperties(ignoreUnknown = true)
public final class ConfigurationTableValue implements GrantEntity {
public final class ConfigurationTableValues implements GrantEntity {
public static final String ATTR_TABLE_VALUES = "tableValues";
@ -42,7 +42,7 @@ public final class ConfigurationTableValue implements GrantEntity {
public final List<TableValue> values;
@JsonCreator
public ConfigurationTableValue(
public ConfigurationTableValues(
@JsonProperty(CONFIGURATION_VALUE.ATTR_INSTITUTION_ID) final Long institutionId,
@JsonProperty(CONFIGURATION_VALUE.ATTR_CONFIGURATION_ID) final Long configurationId,
@JsonProperty(CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID) final Long attributeId,
@ -88,7 +88,7 @@ public final class ConfigurationTableValue implements GrantEntity {
@Override
public String toString() {
return "ConfigurationTableValue [institutionId=" + this.institutionId + ", configurationId="
return "ConfigurationTableValues [institutionId=" + this.institutionId + ", configurationId="
+ this.configurationId
+ ", attributeId=" + this.attributeId + ", values=" + this.values + "]";
}

View file

@ -17,7 +17,6 @@ import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TabItem;
@ -65,15 +64,8 @@ public class FormBuilder {
this.pageContext = pageContext;
this.form = new Form(pageService.getJSONMapper());
this.formParent = new Composite(pageContext.getParent(), SWT.NONE);
final GridLayout layout = new GridLayout(rows, true);
layout.horizontalSpacing = 10;
layout.verticalSpacing = 10;
layout.marginBottom = 50;
layout.marginLeft = 10;
layout.marginTop = 0;
this.formParent.setLayout(layout);
this.formParent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
this.formParent = this.widgetFactory
.formGrid(pageContext.getParent(), rows);
}
public FormBuilder readonly(final boolean readonly) {

View file

@ -22,6 +22,10 @@ public interface InputField {
void initValue(Collection<ConfigurationValue> values);
void initValue(final String value, final Integer listIndex);
String getValue();
void showError(String errorMessage);
void clearError();

View file

@ -17,6 +17,7 @@ import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gui.service.examconfig.impl.InputFieldBuilderSupplier;
import ch.ethz.seb.sebserver.gui.service.examconfig.impl.ViewContext;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@ -24,6 +25,18 @@ public interface InputFieldBuilder {
String RES_BUNDLE_KEY_PREFIX = "sebserver.examconfig.attribute.";
/** Called by the InputFieldBuilderSupplier bean instance on initialization to avoid
* circular dependencies.
*
* This method must not be called from other then InputFieldBuilderSupplier
* For default this does nothing and a InputFieldBuilder that uses a reference to
* the calling InputFieldBuilderSupplier must override this to get the reference
*
* @param inputFieldBuilderSupplier reference of InputFieldBuilderSupplier */
default void init(final InputFieldBuilderSupplier inputFieldBuilderSupplier) {
// NOOP for default
}
boolean builderFor(
ConfigurationAttribute attribute,
Orientation orientation);

View file

@ -9,7 +9,7 @@
package ch.ethz.seb.sebserver.gui.service.examconfig;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gui.service.examconfig.impl.ViewContext;
public interface ValueChangeListener {
@ -20,6 +20,6 @@ public interface ValueChangeListener {
String value,
int listIndex);
void tableChanged(ConfigurationTableValue tableValue);
void tableChanged(ConfigurationTableValues tableValue);
}

View file

@ -90,12 +90,14 @@ public abstract class AbstractInputField<T extends Control> implements InputFiel
values.stream()
.filter(a -> this.attribute.id.equals(a.attributeId))
.findFirst()
.map(v -> {
this.initValue = v.value;
this.listIndex = (v.listIndex != null) ? v.listIndex : 0;
setValueToControl(this.initValue);
return this.initValue;
});
.ifPresent(v -> initValue(v.value, v.listIndex));
}
@Override
public void initValue(final String value, final Integer listIndex) {
this.initValue = value;
this.listIndex = (listIndex != null) ? listIndex : 0;
setValueToControl(this.initValue);
}
protected void setDefaultValue() {

View file

@ -16,6 +16,7 @@ import org.eclipse.swt.widgets.Composite;
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.Orientation;
@ -66,7 +67,7 @@ public class CheckBoxBuilder implements InputFieldBuilder {
return new CheckboxField(
attribute,
viewContext.attributeMapping.getOrientation(attribute.id),
viewContext.getOrientation(attribute.id),
checkbox);
}
@ -84,6 +85,13 @@ public class CheckBoxBuilder implements InputFieldBuilder {
protected void setValueToControl(final String value) {
this.control.setSelection(Boolean.valueOf(this.initValue));
}
@Override
public String getValue() {
return this.control.getSelection()
? Constants.TRUE_STRING
: Constants.FALSE_STRING;
}
}
}

View file

@ -11,7 +11,6 @@ package ch.ethz.seb.sebserver.gui.service.examconfig.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import org.apache.tomcat.util.buf.StringUtils;
@ -31,7 +30,7 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
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.View;
@ -51,7 +50,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.Ge
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurationValues;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetOrientations;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetViewList;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfigTableValue;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfigTableValues;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfigValue;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@ -66,20 +65,20 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
private final JSONMapper jsonMapper;
private final WidgetFactory widgetFactory;
private final Collection<InputFieldBuilder> inputFieldBuilder;
private final InputFieldBuilderSupplier inputFieldBuilderSupplier;
private final Collection<ValueChangeRule> valueChangeRules;
protected ExamConfigurationServiceImpl(
final RestService restService,
final JSONMapper jsonMapper,
final WidgetFactory widgetFactory,
final Collection<InputFieldBuilder> inputFieldBuilder,
final InputFieldBuilderSupplier inputFieldBuilderSupplier,
final Collection<ValueChangeRule> valueChangeRules) {
this.restService = restService;
this.jsonMapper = jsonMapper;
this.widgetFactory = widgetFactory;
this.inputFieldBuilder = Utils.immutableCollectionOf(inputFieldBuilder);
this.inputFieldBuilderSupplier = inputFieldBuilderSupplier;
this.valueChangeRules = Utils.immutableCollectionOf(valueChangeRules);
}
@ -93,11 +92,7 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
final ConfigurationAttribute attribute,
final Orientation orientation) {
return this.inputFieldBuilder
.stream()
.filter(b -> b.builderFor(attribute, orientation))
.findFirst()
.orElseThrow(() -> new NoSuchElementException("No InputFieldBuilder found for : " + attribute.type));
return this.inputFieldBuilderSupplier.getInputFieldBuilder(attribute, orientation);
}
@Override
@ -169,7 +164,7 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
@Override
public Composite createViewGrid(final Composite parent, final ViewContext viewContext) {
final Composite composite = new Composite(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(viewContext.columns, true);
final GridLayout gridLayout = new GridLayout(viewContext.getColumns(), true);
gridLayout.verticalSpacing = 0;
composite.setLayout(gridLayout);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
@ -179,7 +174,7 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
viewContext,
this);
for (final ConfigurationAttribute attribute : viewContext.attributeMapping.getAttributes()) {
for (final ConfigurationAttribute attribute : viewContext.getAttributes()) {
viewGridBuilder.add(attribute);
}
@ -269,8 +264,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
}
@Override
public void tableChanged(final ConfigurationTableValue tableValue) {
this.restService.getBuilder(SaveExamConfigTableValue.class)
public void tableChanged(final ConfigurationTableValues tableValue) {
this.restService.getBuilder(SaveExamConfigTableValues.class)
.withBody(tableValue)
.call();
}

View file

@ -0,0 +1,47 @@
/*
* 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.examconfig.impl;
import java.util.Collection;
import java.util.NoSuchElementException;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder;
@Lazy
@Service
@GuiProfile
public class InputFieldBuilderSupplier {
private final Collection<InputFieldBuilder> inputFieldBuilder;
protected InputFieldBuilderSupplier(final Collection<InputFieldBuilder> inputFieldBuilder) {
this.inputFieldBuilder = inputFieldBuilder;
inputFieldBuilder
.stream()
.forEach(builder -> builder.init(this));
}
public InputFieldBuilder getInputFieldBuilder(
final ConfigurationAttribute attribute,
final Orientation orientation) {
return this.inputFieldBuilder
.stream()
.filter(b -> b.builderFor(attribute, orientation))
.findFirst()
.orElseThrow(() -> new NoSuchElementException("No InputFieldBuilder found for : " + attribute.type));
}
}

View file

@ -55,7 +55,7 @@ public class LabelBuilder implements InputFieldBuilder {
return new LabelField(
attribute,
viewContext.attributeMapping.getOrientation(attribute.id),
viewContext.getOrientation(attribute.id),
label);
}
@ -74,6 +74,11 @@ public class LabelBuilder implements InputFieldBuilder {
// Does Nothing, Label has no default value
}
@Override
public String getValue() {
return this.control.getText();
}
}
}

View file

@ -61,7 +61,7 @@ public class PassworFieldBuilder implements InputFieldBuilder {
final ConfigurationAttribute attribute,
final ViewContext viewContext) {
final Orientation orientation = viewContext.attributeMapping
final Orientation orientation = viewContext
.getOrientation(attribute.id);
final Composite innerGrid = InputFieldBuilder
.createInnerGrid(parent, orientation);
@ -96,15 +96,7 @@ public class PassworFieldBuilder implements InputFieldBuilder {
return;
}
String hashedPWD;
try {
hashedPWD = hashPassword(pwd);
} catch (final NoSuchAlgorithmException e) {
log.error("Failed to hash password: ", e);
passwordInputField.showError("Failed to hash password");
hashedPWD = null;
}
final String hashedPWD = passwordInputField.getValue();
if (hashedPWD != null) {
passwordInputField.clearError();
viewContext.getValueChangeListener().valueChanged(
@ -122,14 +114,6 @@ public class PassworFieldBuilder implements InputFieldBuilder {
return passwordInputField;
}
private String hashPassword(final String pwd) throws NoSuchAlgorithmException {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] encodedhash = digest.digest(
pwd.getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(encodedhash);
}
static final class PasswordInputField extends AbstractInputField<Text> {
private final Text confirm;
@ -154,6 +138,28 @@ public class PassworFieldBuilder implements InputFieldBuilder {
}
}
@Override
public String getValue() {
String hashedPWD;
try {
hashedPWD = hashPassword(this.control.getText());
} catch (final NoSuchAlgorithmException e) {
log.error("Failed to hash password: ", e);
showError("Failed to hash password");
hashedPWD = null;
}
return hashedPWD;
}
private String hashPassword(final String pwd) throws NoSuchAlgorithmException {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] encodedhash = digest.digest(
pwd.getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(encodedhash);
}
}
}

View file

@ -0,0 +1,186 @@
/*
* 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.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;
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.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 {
private static final Logger log = LoggerFactory.getLogger(TableContext.class);
private final InputFieldBuilderSupplier inputFieldBuilderSupplier;
private final WidgetFactory widgetFactory;
private final RestService restService;
public final ConfigurationAttribute attribute;
public final Orientation orientation;
private final List<ConfigurationAttribute> rowAttributes;
private final List<ConfigurationAttribute> columnAttributes;
private final ViewContext viewContext;
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);
this.orientation = viewContext
.getOrientation(attribute.id);
this.rowAttributes = viewContext.getChildAttributes(attribute.id)
.stream()
.sorted(rowAttributeComparator(viewContext))
.collect(Collectors.toList());
this.columnAttributes = this.rowAttributes
.stream()
.filter(attr -> viewContext.getOrientation(attr.id).xPosition > 0)
.sorted(columnAttributeComparator(viewContext))
.collect(Collectors.toList());
}
public InputFieldBuilderSupplier getInputFieldBuilderSupplier() {
return this.inputFieldBuilderSupplier;
}
public WidgetFactory getWidgetFactory() {
return this.widgetFactory;
}
public ConfigurationAttribute getAttribute() {
return this.attribute;
}
public Orientation getOrientation() {
return this.orientation;
}
public Orientation getOrientation(final Long attributeId) {
return this.viewContext.getOrientation(attributeId);
}
public List<ConfigurationAttribute> getRowAttributes() {
return this.rowAttributes;
}
public List<ConfigurationAttribute> getColumnAttributes() {
return this.columnAttributes;
}
public ViewContext getViewContext() {
return this.viewContext;
}
public ValueChangeListener getValueChangeListener() {
return this.viewContext.getValueChangeListener();
}
public Long getInstitutionId() {
return this.viewContext.getInstitutionId();
}
public Long getConfigurationId() {
return this.viewContext.getConfigurationId();
}
public ConfigurationAttribute getAttribute(final Long attributeId) {
return this.viewContext.getAttribute(attributeId);
}
public void flushInputFields(final Set<Long> attributeIds) {
this.viewContext.flushInputFields(attributeIds);
}
public InputFieldBuilder getInputFieldBuilder(
final ConfigurationAttribute attribute2,
final Orientation orientation) {
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);
}
private Comparator<ConfigurationAttribute> rowAttributeComparator(final ViewContext viewContext) {
return (a1, a2) -> {
try {
final Orientation o1 = viewContext.getOrientation(a1.id);
final Orientation o2 = viewContext.getOrientation(a2.id);
return o1.yPosition.compareTo(o2.yPosition);
} catch (final Exception e) {
log.warn("Failed to get Orientations of ConfigurationAttribute to compare: ", e);
return -1;
}
};
}
private Comparator<ConfigurationAttribute> columnAttributeComparator(final ViewContext viewContext) {
return (a1, a2) -> {
try {
final Orientation o1 = viewContext.getOrientation(a1.id);
final Orientation o2 = viewContext.getOrientation(a2.id);
return o1.xPosition.compareTo(o2.xPosition);
} catch (final Exception e) {
log.warn("Failed to get Orientations of ConfigurationAttribute to compare: ", e);
return -1;
}
};
}
}

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.examconfig.impl.table;
package ch.ethz.seb.sebserver.gui.service.examconfig.impl;
import java.util.ArrayList;
import java.util.Collection;
@ -31,19 +31,18 @@ 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.ConfigurationTableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue.TableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues.TableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.examconfig.ExamConfigurationService;
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.impl.AbstractInputField;
import ch.ethz.seb.sebserver.gui.service.examconfig.impl.ViewContext;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
@ -56,12 +55,23 @@ public class TableFieldBuilder implements InputFieldBuilder {
private static final String ROW_VALUE_KEY = "RowValues";
private final RestService restService;
private final WidgetFactory widgetFactory;
private InputFieldBuilderSupplier inputFieldBuilderSupplier;
public TableFieldBuilder(final WidgetFactory widgetFactory) {
protected TableFieldBuilder(
final RestService restService,
final WidgetFactory widgetFactory) {
this.restService = restService;
this.widgetFactory = widgetFactory;
}
@Override
public void init(final InputFieldBuilderSupplier inputFieldBuilderSupplier) {
this.inputFieldBuilderSupplier = inputFieldBuilderSupplier;
}
@Override
public boolean builderFor(
final ConfigurationAttribute attribute,
@ -81,31 +91,26 @@ public class TableFieldBuilder implements InputFieldBuilder {
final ViewContext viewContext) {
final I18nSupport i18nSupport = viewContext.getI18nSupport();
final Orientation orientation = viewContext.attributeMapping
.getOrientation(attribute.id);
final List<ConfigurationAttribute> childAttributes =
viewContext.attributeMapping.childAttributeMapping.get(attribute.id);
final List<ConfigurationAttribute> columnAttributes = childAttributes
.stream()
.filter(attr -> viewContext.attributeMapping.getOrientation(attr.id).xPosition > 0)
.sorted((attr1, attr2) -> viewContext.attributeMapping.getOrientation(attr1.id).xPosition.compareTo(
viewContext.attributeMapping.getOrientation(attr2.id).xPosition))
.collect(Collectors.toList());
final TableContext tableContext = new TableContext(
this.inputFieldBuilderSupplier,
this.widgetFactory,
this.restService,
attribute,
viewContext);
final Table table = new Table(parent, SWT.NONE | SWT.H_SCROLL);
table.setLayout(new GridLayout());
final GridData gridData = new GridData(
SWT.FILL, SWT.FILL,
true, false,
(orientation != null) ? orientation.width() : 1,
(orientation != null) ? orientation.height() : 1);
gridData.heightHint = orientation.height * 40;
(tableContext.orientation != null) ? tableContext.orientation.width() : 1,
(tableContext.orientation != null) ? tableContext.orientation.height() : 1);
gridData.heightHint = tableContext.orientation.height * 40;
table.setLayoutData(gridData);
table.setHeaderVisible(true);
table.addListener(SWT.Resize, this::adaptColumnWidth);
for (final ConfigurationAttribute columnAttribute : columnAttributes) {
for (final ConfigurationAttribute columnAttribute : tableContext.getColumnAttributes()) {
final TableColumn column = new TableColumn(table, SWT.NONE);
final String text = i18nSupport.getText(
ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + columnAttribute.name,
@ -116,13 +121,8 @@ public class TableFieldBuilder implements InputFieldBuilder {
}
final TableInputField tableField = new TableInputField(
this.widgetFactory,
attribute,
orientation,
table,
childAttributes,
columnAttributes,
viewContext);
tableContext,
table);
TableColumn column = new TableColumn(table, SWT.NONE);
column.setImage(ImageIcon.ADD_BOX.getImage(parent.getDisplay()));
@ -175,31 +175,21 @@ public class TableFieldBuilder implements InputFieldBuilder {
static final class TableInputField extends AbstractInputField<Table> {
private final List<ConfigurationAttribute> childAttributes;
private final List<ConfigurationAttribute> columnAttributes;
private final ViewContext viewContext;
private final WidgetFactory widgetFactory;
private final TableContext tableContext;
private List<Map<Long, TableValue>> values;
TableInputField(
final WidgetFactory widgetFactory,
final ConfigurationAttribute attribute,
final Orientation orientation,
final Table control,
final List<ConfigurationAttribute> childAttributes,
final List<ConfigurationAttribute> columnAttributes,
final ViewContext viewContext) {
final TableContext tableContext,
final Table control) {
super(attribute, orientation, control, null);
this.childAttributes = childAttributes;
this.columnAttributes = columnAttributes;
this.viewContext = viewContext;
this.widgetFactory = widgetFactory;
super(tableContext.attribute, tableContext.orientation, control, null);
this.tableContext = tableContext;
}
@Override
public void initValue(final Collection<ConfigurationValue> values) {
clearTable();
// get all child values as TableValues
final List<TableValue> tableValues = values.stream()
.filter(this::isChildValue)
@ -229,22 +219,21 @@ public class TableFieldBuilder implements InputFieldBuilder {
private boolean isChildValue(final ConfigurationValue value) {
return this.attribute.id.equals(
this.viewContext.attributeMapping.attributeIdMapping
.get(value.attributeId).parentId);
this.tableContext.getAttribute(value.attributeId).parentId);
}
private void deleteRow(final int selectionIndex) {
this.control.remove(selectionIndex);
this.values.remove(selectionIndex);
// send new values to web-service
this.viewContext.getValueChangeListener()
this.tableContext.getValueChangeListener()
.tableChanged(extractTableValue());
}
private void addRow() {
final int index = this.values.size();
// create new values form default values
final Map<Long, TableValue> rowValues = this.childAttributes
final Map<Long, TableValue> rowValues = this.tableContext.getRowAttributes()
.stream()
.map(attr -> new TableValue(attr.id, index, attr.defaultValue))
.collect(Collectors.toMap(
@ -254,6 +243,9 @@ public class TableFieldBuilder implements InputFieldBuilder {
this.values.add(rowValues);
addTableRow(rowValues);
this.control.layout();
// send new values to web-service
this.tableContext.getValueChangeListener()
.tableChanged(extractTableValue());
}
private void addTableRow(final Map<Long, TableValue> rowValues) {
@ -278,88 +270,105 @@ public class TableFieldBuilder implements InputFieldBuilder {
}
private void applyRowValues(
final int rowIndex,
final Map<Long, TableValue> rowValues) {
// set the new values
this.values.set(rowIndex, rowValues);
// update table row
applyTableRowValues(rowIndex);
}
// private void applyRowValues(
// final int rowIndex,
// final Map<Long, TableValue> rowValues) {
//
// // set the new values
// this.values.set(rowIndex, rowValues);
// // update table row
// applyTableRowValues(rowIndex);
//
// }
private void applyTableRowValues(final int index) {
final TableItem item = this.control.getItem(index);
final Map<Long, TableValue> rowValues = this.values.get(index);
int cellIndex = 0;
for (final ConfigurationAttribute attr : this.columnAttributes) {
final String value = rowValues.containsKey(attr.id)
? rowValues.get(attr.id).value
: null;
item.setText(cellIndex, value);
for (final ConfigurationAttribute attr : this.tableContext.getColumnAttributes()) {
if (rowValues.containsKey(attr.id)) {
item.setText(cellIndex, rowValues.get(attr.id).value);
}
cellIndex++;
}
item.setData(ROW_VALUE_KEY, item);
// send new values to web-service
this.viewContext.getValueChangeListener()
.tableChanged(extractTableValue());
}
private void openForm(final int selectionIndex) {
final Map<Long, TableValue> rowValues = this.values.get(selectionIndex);
final ModalInputDialog<Map<Long, TableValue>> dialog = new ModalInputDialog<>(
this.control.getShell(),
this.widgetFactory);
final TableRowFormBuilder builder = new TableRowFormBuilder(
this.tableContext,
rowValues,
selectionIndex);
final TableRowFormBuilder builder = new TableRowFormBuilder(rowValues);
dialog.open(
new LocTextKey("Title"),
v -> System.out.println("Values Applied"),
builder);
new ModalInputDialog<Map<Long, TableValue>>(
this.control.getShell(),
this.tableContext.getWidgetFactory())
.setDialogWidth(500)
.open(
new LocTextKey("Title"),
values -> applyFormValues(values, selectionIndex),
builder);
}
private ConfigurationTableValue extractTableValue() {
private void applyFormValues(final Map<Long, TableValue> values, final int index) {
final Map<Long, TableValue> tableRowValues = this.tableContext.getTableRowValues(index);
if (tableRowValues == null || tableRowValues.isEmpty()) {
return;
}
this.values.remove(index);
this.values.add(index, tableRowValues);
applyTableRowValues(index);
}
private ConfigurationTableValues extractTableValue() {
final List<TableValue> collect = this.values
.stream()
.flatMap(map -> map.values().stream())
.collect(Collectors.toList());
return new ConfigurationTableValue(
this.viewContext.getInstitutionId(),
this.viewContext.getConfigurationId(),
return new ConfigurationTableValues(
this.tableContext.getInstitutionId(),
this.tableContext.getConfigurationId(),
this.attribute.id,
collect);
}
@Override
protected void setDefaultValue() {
// NOTE this just empty the list for now
// TODO do we need default values for lists?
clearTable();
final List<TableValue> values = new ArrayList<>();
this.tableContext.getValueChangeListener().tableChanged(
new ConfigurationTableValues(
this.tableContext.getInstitutionId(),
this.tableContext.getConfigurationId(),
this.attribute.id,
values));
}
private void clearTable() {
this.control.setSelection(-1);
if (this.control.getItemCount() > 0) {
for (final TableItem item : this.control.getItems()) {
item.dispose();
}
}
final List<TableValue> values = new ArrayList<>();
this.viewContext.getValueChangeListener().tableChanged(
new ConfigurationTableValue(
this.viewContext.getInstitutionId(),
this.viewContext.getConfigurationId(),
this.attribute.id,
values));
}
@Override
protected void setValueToControl(final String value) {
throw new UnsupportedOperationException();
}
@Override
public String getValue() {
throw new UnsupportedOperationException();
}
}
}

View file

@ -0,0 +1,124 @@
/*
* 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.examconfig.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
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.TableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gui.service.examconfig.ExamConfigurationService;
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.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
public class TableRowFormBuilder implements ModalInputDialogComposer<Map<Long, TableValue>> {
private final TableContext tableContext;
private final Map<Long, TableValue> rowValues;
private final int listIndex;
public TableRowFormBuilder(
final TableContext tableContext,
final Map<Long, TableValue> rowValues,
final int listIndex) {
this.tableContext = tableContext;
this.rowValues = rowValues;
this.listIndex = listIndex;
}
@Override
public Supplier<Map<Long, TableValue>> compose(final Composite parent) {
final List<InputField> inputFields = new ArrayList<>();
final Composite grid = this.tableContext
.getWidgetFactory()
.formGrid(parent, 2);
final GridLayout layout = (GridLayout) grid.getLayout();
layout.verticalSpacing = 0;
for (final ConfigurationAttribute attribute : this.tableContext.getRowAttributes()) {
createLabel(grid, attribute);
inputFields.add(createInputField(grid, attribute));
}
// when the pop-up gets closed we have to remove the input fields from the view context
grid.addDisposeListener(event -> {
this.tableContext.flushInputFields(this.rowValues.keySet());
});
return () -> inputFields.stream()
.map(field -> new TableValue(
field.getAttribute().id,
this.listIndex,
field.getValue()))
.collect(Collectors.toMap(tv -> tv.attributeId, Function.identity()));
}
private InputField createInputField(
final Composite parent,
final ConfigurationAttribute attribute) {
if (attribute.type == AttributeType.TABLE) {
throw new UnsupportedOperationException(
"Table type is currently not supported within a table row form view!");
}
final Orientation orientation = this.tableContext
.getOrientation(attribute.id);
final InputFieldBuilder inputFieldBuilder = this.tableContext
.getInputFieldBuilder(attribute, orientation);
final InputField inputField = inputFieldBuilder.createInputField(
parent,
attribute,
this.tableContext.getViewContext());
inputField.initValue(
this.rowValues.get(attribute.id).value,
this.listIndex);
// we have to register the input field within the ViewContext to receive error messages
this.tableContext.registerInputField(inputField);
return inputField;
}
private void createLabel(final Composite parent, final ConfigurationAttribute attribute) {
final LocTextKey locTextKey = new LocTextKey(
ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX +
attribute.name);
final Label label = this.tableContext
.getWidgetFactory()
.labelLocalized(parent, locTextKey, attribute.name);
final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, true, false);
gridData.verticalIndent = 4;
label.setLayoutData(gridData);
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key);
}
}

View file

@ -50,7 +50,7 @@ public class TextFieldBuilder implements InputFieldBuilder {
final ConfigurationAttribute attribute,
final ViewContext viewContext) {
final Orientation orientation = viewContext.attributeMapping
final Orientation orientation = viewContext
.getOrientation(attribute.id);
final Composite innerGrid = InputFieldBuilder
.createInnerGrid(parent, orientation);
@ -100,6 +100,11 @@ public class TextFieldBuilder implements InputFieldBuilder {
protected void setValueToControl(final String value) {
this.control.setText(value);
}
@Override
public String getValue() {
return this.control.getText();
}
}
}

View file

@ -10,13 +10,13 @@ package ch.ethz.seb.sebserver.gui.service.examconfig.impl;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
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.View;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputField;
import ch.ethz.seb.sebserver.gui.service.examconfig.ValueChangeListener;
@ -24,11 +24,11 @@ import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
public final class ViewContext {
public final Configuration configuration;
public final View view;
public final int columns, rows;
private final Configuration configuration;
private final View view;
private final int columns, rows;
public final AttributeMapping attributeMapping;
private final AttributeMapping attributeMapping;
private final Map<Long, InputField> inputFieldMapping;
private final ValueChangeListener valueChangeListener;
private final I18nSupport i18nSupport;
@ -86,9 +86,36 @@ public final class ViewContext {
return this.rows;
}
public List<ConfigurationAttribute> getChildAttributes(final ConfigurationAttribute attribute) {
// TODO Auto-generated method stub
return null;
public Configuration getConfiguration() {
return this.configuration;
}
public View getView() {
return this.view;
}
//
// public AttributeMapping getAttributeMapping() {
// return this.attributeMapping;
// }
public Collection<ConfigurationAttribute> getChildAttributes(final Long id) {
return this.attributeMapping.childAttributeMapping.get(id);
}
public Collection<ConfigurationAttribute> getAttributes() {
return this.attributeMapping.getAttributes();
}
public ConfigurationAttribute getAttribute(final Long attributeId) {
return this.attributeMapping.getAttribute(attributeId);
}
public Collection<Orientation> getOrientationsOfGroup(final ConfigurationAttribute attribute) {
return this.attributeMapping.getOrientationsOfGroup(attribute);
}
public Orientation getOrientation(final Long attributeId) {
return this.attributeMapping.getOrientation(attributeId);
}
public ValueChangeListener getValueChangeListener() {
@ -126,4 +153,16 @@ public final class ViewContext {
.forEach(field -> field.initValue(values));
}
/** Removes all registered InputFields with the given attribute ids
*
* @param values Collection of attribute ids */
void flushInputFields(final Collection<Long> values) {
if (values == null) {
return;
}
values.stream()
.forEach(attrId -> this.inputFieldMapping.remove(attrId));
}
}

View file

@ -13,6 +13,7 @@ import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
@ -30,6 +31,7 @@ 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.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
public class ViewGridBuilder {
@ -49,7 +51,7 @@ public class ViewGridBuilder {
this.examConfigurationService = examConfigurationService;
this.parent = parent;
this.viewContext = viewContext;
this.grid = new CellFieldBuilderAdapter[viewContext.rows][viewContext.columns];
this.grid = new CellFieldBuilderAdapter[viewContext.getRows()][viewContext.getColumns()];
this.registeredGroups = new HashSet<>();
}
@ -59,7 +61,7 @@ public class ViewGridBuilder {
return this;
}
final Orientation orientation = this.viewContext.attributeMapping
final Orientation orientation = this.viewContext
.getOrientation(attribute.id);
// create group builder
@ -101,6 +103,10 @@ public class ViewGridBuilder {
}
case LEFT: {
this.grid[ypos][xpos - 1] = labelBuilder(attribute, orientation);
// special case for password, also add confirm label
if (attribute.type == AttributeType.PASSWORD_FIELD) {
this.grid[ypos + 1][xpos - 1] = passwordConfirmLabel(attribute, orientation);
}
break;
}
case TOP: {
@ -179,10 +185,6 @@ public class ViewGridBuilder {
final ConfigurationAttribute attribute,
final Orientation orientation) {
if (attribute.type == AttributeType.PASSWORD_FIELD) {
return passwordConfirmLabel(attribute, orientation);
}
return new CellFieldBuilderAdapter() {
@Override
public void createCell(final ViewGridBuilder builder) {
@ -192,6 +194,7 @@ public class ViewGridBuilder {
ViewGridBuilder.this.parent,
new LocTextKey(ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + attribute.name),
attribute.name);
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
switch (orientation.title) {
@ -211,6 +214,7 @@ public class ViewGridBuilder {
}
}
label.setLayoutData(gridData);
}
};
}
@ -232,6 +236,7 @@ public class ViewGridBuilder {
label.setAlignment(SWT.LEFT);
gridData.verticalIndent = 10;
label.setLayoutData(gridData);
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key);
}
};
}
@ -254,7 +259,7 @@ public class ViewGridBuilder {
this.builder = builder;
this.attribute = attribute;
this.orientationsOfGroup =
builder.viewContext.attributeMapping.getOrientationsOfGroup(attribute);
builder.viewContext.getOrientationsOfGroup(attribute);
for (final Orientation o : this.orientationsOfGroup) {
this.x = (this.x < o.xpos()) ? o.xpos() : this.x;
this.x = (this.y < o.ypos()) ? o.ypos() : this.y;
@ -291,7 +296,7 @@ public class ViewGridBuilder {
final Orientation orientation,
final InputFieldBuilder inputFieldBuilder) {
final ConfigurationAttribute attr = this.builder.viewContext.attributeMapping
final ConfigurationAttribute attr = this.builder.viewContext
.getAttribute(orientation.attributeId);
final InputField inputField = inputFieldBuilder.createInputField(

View file

@ -1,36 +0,0 @@
/*
* 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.examconfig.impl.table;
import java.util.Map;
import java.util.function.Supplier;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue.TableValue;
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
public class TableRowFormBuilder implements ModalInputDialogComposer<Map<Long, TableValue>> {
private final Map<Long, TableValue> rowValues;
public TableRowFormBuilder(final Map<Long, TableValue> rowValues) {
this.rowValues = rowValues;
}
@Override
public Supplier<Map<Long, TableValue>> compose(final Composite parent) {
final Label test = new Label(parent, SWT.NONE);
test.setText("TEST");
return () -> null;
}
}

View file

@ -139,13 +139,10 @@ public class I18nSupportImpl implements I18nSupport {
.toString(this.displayDateFormatter);
final UserInfo userInfo = this.currentUser.get();
if (userInfo.timeZone != null && !userInfo.timeZone.equals(DateTimeZone.UTC)) {
if (userInfo != null && userInfo.timeZone != null) {
return dateTimeStringUTC + date
.withZone(userInfo.timeZone)
.toString(this.timeZoneFormatter);
}
return dateTimeStringUTC;
if (userInfo != null && userInfo.timeZone != null && !userInfo.timeZone.equals(DateTimeZone.UTC)) {
return dateTimeStringUTC + date
.withZone(userInfo.timeZone)
.toString(this.timeZoneFormatter);
} else {
return dateTimeStringUTC;
}

View file

@ -39,6 +39,7 @@ public class ModalInputDialog<T> extends Dialog {
new LocTextKey("sebserver.overall.action.close");
private final WidgetFactory widgetFactory;
private int dialogWidth = 400;
public ModalInputDialog(
final Shell parent,
@ -48,6 +49,11 @@ public class ModalInputDialog<T> extends Dialog {
this.widgetFactory = widgetFactory;
}
public ModalInputDialog<T> setDialogWidth(final int dialogWidth) {
this.dialogWidth = dialogWidth;
return this;
}
public void open(
final LocTextKey title,
final Consumer<T> callback,
@ -65,6 +71,8 @@ public class ModalInputDialog<T> extends Dialog {
main.setLayout(new GridLayout());
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
gridData.horizontalSpan = 2;
gridData.widthHint = this.dialogWidth;
main.setLayoutData(gridData);
final Supplier<T> valueSuppier = contentComposer.compose(main);

View file

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

View file

@ -17,20 +17,20 @@ 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.ConfigurationTableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class SaveExamConfigTableValue extends RestCall<ConfigurationTableValue> {
public class SaveExamConfigTableValues extends RestCall<ConfigurationTableValues> {
protected SaveExamConfigTableValue() {
protected SaveExamConfigTableValues() {
super(new TypeKey<>(
CallType.SAVE,
EntityType.CONFIGURATION_VALUE,
new TypeReference<ConfigurationTableValue>() {
new TypeReference<ConfigurationTableValues>() {
}),
HttpMethod.PUT,
MediaType.APPLICATION_JSON_UTF8,

View file

@ -175,6 +175,19 @@ public class WidgetFactory {
return defaultPageLayout;
}
public Composite formGrid(final Composite parent, final int rows) {
final Composite grid = new Composite(parent, SWT.NONE);
final GridLayout layout = new GridLayout(rows, true);
layout.horizontalSpacing = 10;
layout.verticalSpacing = 10;
layout.marginBottom = 50;
layout.marginLeft = 10;
layout.marginTop = 0;
grid.setLayout(layout);
grid.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
return grid;
}
public Button buttonLocalized(final Composite parent, final String locTextKey) {
final Button button = new Button(parent, SWT.NONE);
this.injectI18n(button, new LocTextKey(locTextKey));

View file

@ -12,6 +12,8 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
/** Permission denied exception that refers to the checked entity type, privilege and
* the user identifier of the user that did request the permission */
public class PermissionDeniedException extends RuntimeException {
private static final long serialVersionUID = 5333137812363042580L;

View file

@ -22,7 +22,10 @@ import org.springframework.util.CollectionUtils;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
/** SEBServerUser defines web-service internal user-account based authentication principal */
/** SEBServerUser defines web-service internal user-account based authentication principal
*
* This implements Spring's UserDetails and CredentialsContainer to act as a principal
* within internal authentication and authorization processes. */
public final class SEBServerUser implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = 5726250141482925769L;

View file

@ -108,7 +108,7 @@ public interface BulkActionSupportDAO<T extends Entity> {
return bulkAction.sources
.stream()
.map(selectionFunction) // apply select function for each source key
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip) // handle and skip results with error
.flatMap(DAOLoggingSupport::logAndSkipOnError) // handle and skip results with error
.flatMap(Collection::stream) // Flatten stream of Collection in to one stream
.collect(Collectors.toSet());
}

View file

@ -12,7 +12,7 @@ import java.util.Collection;
import java.util.Set;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -26,11 +26,17 @@ public interface ConfigurationValueDAO extends EntityDAO<ConfigurationValue, Con
"Deletion is not supported for ConfigurationValue. A ConfigurationValue get automatically deleted on deletion of a Configuration");
}
Result<ConfigurationTableValue> getTableValue(
Result<ConfigurationTableValues> getTableValues(
final Long institutionId,
final Long attributeId,
final Long configurationId);
final Long configurationId,
final Long attributeId);
Result<ConfigurationTableValue> saveTableValue(ConfigurationTableValue value);
Result<ConfigurationTableValues> saveTableValues(ConfigurationTableValues value);
Result<Collection<ConfigurationValue>> getTableRowValues(
final Long institutionId,
final Long configurationId,
final Long attributeId,
final Integer rowIndex);
}

View file

@ -20,7 +20,7 @@ public final class DAOLoggingSupport {
public static final Logger log = LoggerFactory.getLogger(DAOLoggingSupport.class);
public static <T> Stream<T> logUnexpectedErrorAndSkip(final Result<T> result) {
public static <T> Stream<T> logAndSkipOnError(final Result<T> result) {
return Result.skipOnError(
result.onError(error -> log.error("Unexpected error. Object processing is skipped: ", error)));
}

View file

@ -20,6 +20,7 @@ public final class ResourceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 8319235723086949618L;
/** The entity key of the resource that was requested */
public final EntityKey entityKey;
public ResourceNotFoundException(final EntityType entityType, final String modelId) {

View file

@ -82,7 +82,7 @@ public class ConfigurationAttributeDAOImpl implements ConfigurationAttributeDAO
.execute()
.stream()
.map(ConfigurationAttributeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -105,7 +105,7 @@ public class ConfigurationAttributeDAOImpl implements ConfigurationAttributeDAO
.execute()
.stream()
.map(ConfigurationAttributeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}

View file

@ -84,7 +84,7 @@ public class ConfigurationDAOImpl implements ConfigurationDAO {
.execute()
.stream()
.map(ConfigurationDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -113,7 +113,7 @@ public class ConfigurationDAOImpl implements ConfigurationDAO {
.execute()
.stream()
.map(ConfigurationDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}

View file

@ -98,7 +98,7 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
.execute()
.stream()
.map(ConfigurationNodeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -133,7 +133,7 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
.execute()
.stream()
.map(ConfigurationNodeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@ -362,21 +362,18 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
.execute()
.stream()
.forEach(attrRec -> {
final boolean bigValue = ConfigurationValueDAOImpl.isBigValue(attrRec);
final String value = templateValues.getOrDefault(
attrRec.getId(),
attrRec.getDefaultValue());
//if (StringUtils.isNoneBlank(value)) {
this.configurationValueRecordMapper.insert(new ConfigurationValueRecord(
null,
configNode.institutionId,
config.getId(),
attrRec.getId(),
0,
bigValue ? null : value,
bigValue ? value : null));
//}
value,
null));
});
return configNode;

View file

@ -13,6 +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.List;
import java.util.Map;
import java.util.Set;
@ -27,10 +28,8 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeValueType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue.TableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues.TableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -101,7 +100,7 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
.execute()
.stream()
.map(ConfigurationValueDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@ -116,7 +115,7 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
.execute()
.stream()
.map(ConfigurationValueDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -133,15 +132,14 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
final String value = (data.value != null)
? data.value
: attributeRecord.getDefaultValue();
final boolean bigValue = isBigValue(attributeRecord);
final ConfigurationValueRecord newRecord = new ConfigurationValueRecord(
null,
data.institutionId,
data.configurationId,
data.attributeId,
data.listIndex,
(bigValue) ? null : value,
(bigValue) ? value : null);
value,
null);
this.configurationValueRecordMapper.insert(newRecord);
return newRecord;
@ -178,15 +176,14 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
id = data.id;
}
final boolean bigValue = isBigValue(attributeRecord);
final ConfigurationValueRecord newRecord = new ConfigurationValueRecord(
id,
null,
null,
null,
data.listIndex,
(bigValue) ? null : data.value,
(bigValue) ? data.value : null);
data.value,
null);
this.configurationValueRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.configurationValueRecordMapper.selectByPrimaryKey(id);
@ -197,28 +194,15 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
@Override
@Transactional(readOnly = true)
public Result<ConfigurationTableValue> getTableValue(
public Result<ConfigurationTableValues> getTableValues(
final Long institutionId,
final Long attributeId,
final Long configurationId) {
final Long configurationId,
final Long attributeId) {
return attributeRecordById(attributeId)
.map(attributeRecord -> {
// get all attributes of the table (columns)
final List<ConfigurationAttributeRecord> columnAttributes =
this.configurationAttributeRecordMapper.selectByExample()
.where(
ConfigurationAttributeRecordDynamicSqlSupport.parentId,
isEqualTo(attributeRecord.getId()))
.build()
.execute();
final Map<Long, ConfigurationAttributeRecord> attributeMapping = columnAttributes
.stream()
.collect(Collectors.toMap(attr -> attr.getId(), Function.identity()));
// get all values of the table and group them by attribute and sorted by list/row index
.flatMap(this::getAttributeMapping)
.map(attributeMapping -> {
// get all values of the table
final List<TableValue> values = this.configurationValueRecordMapper.selectByExample()
.where(
ConfigurationValueRecordDynamicSqlSupport.institutionId,
@ -232,10 +216,13 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
.build()
.execute()
.stream()
.map(value -> getTableValue(value, attributeMapping))
.map(value -> new TableValue(
value.getConfigurationAttributeId(),
value.getListIndex(),
value.getValue()))
.collect(Collectors.toList());
return new ConfigurationTableValue(
return new ConfigurationTableValues(
institutionId,
configurationId,
attributeId,
@ -243,23 +230,66 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
});
}
private TableValue getTableValue(
final ConfigurationValueRecord value,
final Map<Long, ConfigurationAttributeRecord> attributeMapping) {
@Override
public Result<Collection<ConfigurationValue>> getTableRowValues(
final Long institutionId,
final Long configurationId,
final Long attributeId,
final Integer rowIndex) {
final Long configurationAttributeId = value.getConfigurationAttributeId();
final ConfigurationAttributeRecord configurationAttributeRecord = attributeMapping
.get(configurationAttributeId);
final boolean bigValue = isBigValue(configurationAttributeRecord);
return new TableValue(
value.getConfigurationAttributeId(),
value.getListIndex(),
bigValue ? value.getText() : value.getValue());
return attributeRecordById(attributeId)
.flatMap(this::getAttributeMapping)
.map(attributeMapping -> {
if (attributeMapping == null || attributeMapping.isEmpty()) {
return Collections.emptyList();
}
// 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());
});
}
// get all attributes of the table (columns) mapped to attribute id
private Result<Map<Long, ConfigurationAttributeRecord>> getAttributeMapping(
final ConfigurationAttributeRecord attributeRecord) {
return Result.tryCatch(() -> {
final List<ConfigurationAttributeRecord> columnAttributes =
this.configurationAttributeRecordMapper.selectByExample()
.where(
ConfigurationAttributeRecordDynamicSqlSupport.parentId,
isEqualTo(attributeRecord.getId()))
.build()
.execute();
return columnAttributes
.stream()
.collect(Collectors.toMap(attr -> attr.getId(), Function.identity()));
});
}
@Override
@Transactional
public Result<ConfigurationTableValue> saveTableValue(final ConfigurationTableValue value) {
public Result<ConfigurationTableValues> saveTableValues(final ConfigurationTableValues value) {
return checkInstitutionalIntegrity(value)
.map(this::checkFollowUpIntegrity)
.flatMap(val -> attributeRecordById(val.attributeId))
@ -294,15 +324,14 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
// then add the new values
for (final TableValue tableValue : value.values) {
final ConfigurationAttributeRecord columnAttr = attributeMap.get(tableValue.attributeId);
final boolean bigValue = isBigValue(columnAttr);
final ConfigurationValueRecord valueRecord = new ConfigurationValueRecord(
null,
value.institutionId,
value.configurationId,
columnAttr.getId(),
tableValue.listIndex,
(bigValue) ? null : tableValue.value,
(bigValue) ? tableValue.value : null);
tableValue.value,
null);
this.configurationValueRecordMapper.insert(valueRecord);
}
@ -342,23 +371,16 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
}
private static Result<ConfigurationValue> toDomainModel(final ConfigurationValueRecord record) {
return Result.tryCatch(() -> new ConfigurationValue(
record.getId(),
record.getInstitutionId(),
record.getConfigurationId(),
record.getConfigurationAttributeId(),
record.getListIndex(),
record.getValue()));
}
return Result.tryCatch(() -> {
public static boolean isBigValue(final ConfigurationAttributeRecord attributeRecord) {
try {
final AttributeType type = AttributeType.valueOf(attributeRecord.getType());
return type.attributeValueType == AttributeValueType.LARGE_TEXT
|| type.attributeValueType == AttributeValueType.BASE64_BINARY;
} catch (final Exception e) {
return false;
}
return new ConfigurationValue(
record.getId(),
record.getInstitutionId(),
record.getConfigurationId(),
record.getConfigurationAttributeId(),
record.getListIndex(),
record.getValue());
});
}
private Result<ConfigurationValue> checkInstitutionalIntegrity(final ConfigurationValue data) {
@ -371,7 +393,7 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
});
}
private Result<ConfigurationTableValue> checkInstitutionalIntegrity(final ConfigurationTableValue data) {
private Result<ConfigurationTableValues> checkInstitutionalIntegrity(final ConfigurationTableValues data) {
return Result.tryCatch(() -> {
final ConfigurationRecord r = this.configurationRecordMapper.selectByPrimaryKey(data.configurationId);
if (r.getInstitutionId().longValue() != data.institutionId.longValue()) {
@ -381,7 +403,7 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
});
}
private ConfigurationTableValue checkFollowUpIntegrity(final ConfigurationTableValue data) {
private ConfigurationTableValues checkFollowUpIntegrity(final ConfigurationTableValues data) {
checkFollowUp(data.configurationId);
return data;
}

View file

@ -86,7 +86,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
.execute()
.stream()
.map(ExamConfigurationMapDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -112,7 +112,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
.execute()
.stream()
.map(ExamConfigurationMapDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}

View file

@ -97,7 +97,7 @@ public class IndicatorDAOImpl implements IndicatorDAO {
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList());
});
@ -113,7 +113,7 @@ public class IndicatorDAOImpl implements IndicatorDAO {
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -219,7 +219,7 @@ public class IndicatorDAOImpl implements IndicatorDAO {
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}

View file

@ -87,7 +87,7 @@ public class InstitutionDAOImpl implements InstitutionDAO {
return records.stream()
.map(InstitutionDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -110,7 +110,7 @@ public class InstitutionDAOImpl implements InstitutionDAO {
.execute()
.stream()
.map(InstitutionDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@ -244,7 +244,7 @@ public class InstitutionDAOImpl implements InstitutionDAO {
.execute()
.stream()
.map(InstitutionDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}

View file

@ -94,7 +94,7 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
return records.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -125,7 +125,7 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList());
});
@ -266,7 +266,7 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}

View file

@ -72,7 +72,7 @@ public class OrientationDAOImpl implements OrientationDAO {
.execute()
.stream()
.map(OrientationDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -98,7 +98,7 @@ public class OrientationDAOImpl implements OrientationDAO {
.execute()
.stream()
.map(OrientationDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}

View file

@ -94,7 +94,7 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
return records.stream()
.map(SebClientConfigDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -125,7 +125,7 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
.execute()
.stream()
.map(SebClientConfigDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList());
});
@ -246,7 +246,7 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
.execute()
.stream()
.map(SebClientConfigDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}

View file

@ -243,7 +243,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
.execute()
.stream()
.map(UserActivityLogDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -312,7 +312,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
.execute()
.stream()
.map(UserActivityLogDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(_predicate)
.collect(Collectors.toList());
@ -329,7 +329,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
.execute()
.stream()
.map(UserActivityLogDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}

View file

@ -146,7 +146,7 @@ public class UserDAOImpl implements UserDAO {
return records.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -184,7 +184,7 @@ public class UserDAOImpl implements UserDAO {
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(_predicate)
.collect(Collectors.toList());
});
@ -362,7 +362,7 @@ public class UserDAOImpl implements UserDAO {
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}

View file

@ -69,7 +69,7 @@ public class ViewDAOImpl implements ViewDAO {
.execute()
.stream()
.map(ViewDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@ -89,7 +89,7 @@ public class ViewDAOImpl implements ViewDAO {
.execute()
.stream()
.map(ViewDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}

View file

@ -8,13 +8,13 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
public interface SebExamConfigService {
void validate(ConfigurationValue value);
void validate(ConfigurationTableValue tableValue);
void validate(ConfigurationTableValues tableValue);
}

View file

@ -9,13 +9,14 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import java.util.Collection;
import java.util.Objects;
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.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue;
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;
@ -27,6 +28,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigServ
@WebServiceProfile
public class SebExamConfigServiceImpl implements SebExamConfigService {
private static final Logger log = LoggerFactory.getLogger(SebExamConfigServiceImpl.class);
private final ConfigurationAttributeDAO configurationAttributeDAO;
private final Collection<ConfigurationValueValidator> validators;
@ -40,7 +43,10 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
@Override
public void validate(final ConfigurationValue value) {
Objects.requireNonNull(value);
if (value == null) {
log.warn("Validate called with null reference. Ignore this and skip validation");
return;
}
final ConfigurationAttribute attribute = this.configurationAttributeDAO.byPK(value.attributeId)
.getOrThrow();
@ -53,7 +59,7 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
}
@Override
public void validate(final ConfigurationTableValue tableValue) {
public void validate(final ConfigurationTableValues tableValue) {
// TODO Auto-generated method stub
}

View file

@ -8,19 +8,21 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
public class IllegalAPIArgumentException extends RuntimeException {
/** This exception shall be used on additional validations of API attribute constraints.
* Throwing an APIConstraintViolationException will lead to a HTTP 400 Bad Request response. */
public class APIConstraintViolationException extends RuntimeException {
private static final long serialVersionUID = 3732727447520974727L;
public IllegalAPIArgumentException() {
public APIConstraintViolationException() {
super();
}
public IllegalAPIArgumentException(final String message, final Throwable cause) {
public APIConstraintViolationException(final String message, final Throwable cause) {
super(message, cause);
}
public IllegalAPIArgumentException(final String message) {
public APIConstraintViolationException(final String message) {
super(message);
}

View file

@ -111,9 +111,9 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
.createErrorResponse(ex.getMessage());
}
@ExceptionHandler(IllegalAPIArgumentException.class)
@ExceptionHandler(APIConstraintViolationException.class)
public ResponseEntity<Object> handleIllegalAPIArgumentException(
final IllegalAPIArgumentException ex,
final APIConstraintViolationException ex,
final WebRequest request) {
log.warn("Illegal API Argument Exception: ", ex);

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Collection;
import java.util.Objects;
import javax.validation.Valid;
@ -24,7 +25,7 @@ import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue;
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.gbl.util.Result;
@ -90,7 +91,7 @@ public class ConfigurationValueController extends EntityController<Configuration
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ConfigurationTableValue getTableValueBy(
public ConfigurationTableValues getTableValueBy(
@RequestParam(
name = Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID,
required = true) final Long attributeId,
@ -100,10 +101,36 @@ public class ConfigurationValueController extends EntityController<Configuration
return this.configurationDAO.byPK(configurationId)
.flatMap(this.authorization::checkRead)
.flatMap(config -> this.configurationValueDAO.getTableValue(
.flatMap(config -> this.configurationValueDAO.getTableValues(
config.institutionId,
configurationId,
attributeId))
.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,
configurationId))
rowIndex))
.getOrThrow();
}
@ -112,12 +139,12 @@ public class ConfigurationValueController extends EntityController<Configuration
method = RequestMethod.PUT,
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ConfigurationTableValue savePut(
@Valid @RequestBody final ConfigurationTableValue tableValue) {
public ConfigurationTableValues savePut(
@Valid @RequestBody final ConfigurationTableValues tableValue) {
return this.configurationDAO.byPK(tableValue.configurationId)
.flatMap(this.authorization::checkModify)
.flatMap(config -> this.configurationValueDAO.saveTableValue(tableValue))
.flatMap(config -> this.configurationValueDAO.saveTableValues(tableValue))
.getOrThrow();
}
@ -149,7 +176,7 @@ public class ConfigurationValueController extends EntityController<Configuration
_entity.attributeId != null &&
_entity.listIndex != null;
if (!idSet && !idsSet) {
throw new IllegalAPIArgumentException(
throw new APIConstraintViolationException(
"Missing some mandatory attributes. Either id must be set or all of configurationId, attributeId and listIndex");
}

View file

@ -355,7 +355,7 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
return this.beanValidationService.validateBean(entity);
} else {
return Result.ofError(
new IllegalAPIArgumentException("Model identifier already defined: " + entity.getModelId()));
new APIConstraintViolationException("Model identifier already defined: " + entity.getModelId()));
}
}
@ -363,7 +363,7 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
if (entity.getModelId() != null) {
return Result.of(entity);
} else {
return Result.ofError(new IllegalAPIArgumentException("Missing model identifier"));
return Result.ofError(new APIConstraintViolationException("Missing model identifier"));
}
}

View file

@ -98,7 +98,7 @@ public class SebClientConfigController extends ActivatableEntityController<SebCl
Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID);
if (institutionId == null) {
throw new IllegalAPIArgumentException("Institution identifier is missing");
throw new APIConstraintViolationException("Institution identifier is missing");
}
postParams.putIfAbsent(

View file

@ -156,7 +156,7 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
// check of institution of UserInfo is active. Otherwise save is not valid
if (!this.beanValidationService
.isActive(new EntityKey(userInfo.getInstitutionId(), EntityType.INSTITUTION))) {
throw new IllegalAPIArgumentException(
throw new APIConstraintViolationException(
"User within an inactive institution cannot be created nor modified");
}
@ -168,7 +168,7 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
if (userRolesOfAccount.contains(UserRole.SEB_SERVER_ADMIN) &&
!rolesOfCurrentUser.contains(UserRole.SEB_SERVER_ADMIN)) {
throw new IllegalAPIArgumentException(
throw new APIConstraintViolationException(
"The current user cannot edit a User-Account of heigher role pased rank: "
+ UserRole.SEB_SERVER_ADMIN);
}
@ -182,7 +182,7 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
.orElse(null);
if (nonePublicRole != null) {
throw new IllegalAPIArgumentException(
throw new APIConstraintViolationException(
"The current user has not the privilege to create a User-Account with none public role: "
+ nonePublicRole);
}

View file

@ -1,32 +0,0 @@
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>sebMode</key>
<integer>1</integer>
<key>sebServerFallback</key>
<true />
<key>sebServerURL</key>
<string>http://localhost:8080</string>
<key>sebServerConfiguration</key>
<array>
<dict>
<key>institution</key>
<string>1</string>
<key>clientName</key>
<string>+Ra01]`-4e*$(=@=</string>
<key>clientSecret</key>
<string>(+8Jl-#l8t(XHi%hxi88HXsjC{BE1F(TgdbQSzz=TpR]%_fJ~IjkdAL^YzfV*()t</string>
<key>accessTokenEndpoint</key>
<string>/oauth/token</string>
<key>handshakeEndpoint</key>
<string>/exam-api/v1/handshake</string>
<key>examConfigEndpoint</key>
<string>/exam-api/v1/examconfig</string>
<key>pingEndpoint</key>
<string>/exam-api/v1/sebping</string>
<key>eventEndpoint</key>
<string>/exam-api/v1/sebevent</string>
</dict>
</array>
</dict>
</plist>