fallback attributes and new password attribute handling

This commit is contained in:
anhefti 2020-02-19 17:10:21 +01:00
parent 26561288c9
commit 7238369550
52 changed files with 11407 additions and 10482 deletions

View file

@ -120,6 +120,8 @@ public final class Constants {
public static final int GZIP_ID2 = 0x8B;
public static final int GZIP_CM = 8;
public static final String SHA_256 = "SHA-256";
public static final RGB WHITE_RGB = new RGB(255, 255, 255);
public static final RGB BLACK_RGB = new RGB(0, 0, 0);

View file

@ -1,178 +1,187 @@
/*
* 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.gbl.api;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.util.Utils;
public class POSTMapper {
public static final POSTMapper EMPTY_MAP = new POSTMapper(null);
protected final MultiValueMap<String, String> params;
public POSTMapper(final MultiValueMap<String, String> params) {
super();
this.params = params != null
? new LinkedMultiValueMap<>(params)
: new LinkedMultiValueMap<>();
}
public String getString(final String name) {
return Utils.decodeFormURL_UTF_8(this.params.getFirst(name));
}
public char[] getCharArray(final String name) {
final String value = getString(name);
if (value == null || value.length() <= 0) {
return new char[] {};
}
return value.toCharArray();
}
public CharSequence getCharSequence(final String name) {
return CharBuffer.wrap(getCharArray(name));
}
public Long getLong(final String name) {
final String value = this.params.getFirst(name);
if (StringUtils.isBlank(value)) {
return null;
}
return Long.parseLong(value);
}
public Integer getInteger(final String name) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
return Integer.parseInt(value);
}
public Locale getLocale(final String name) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
return Locale.forLanguageTag(value);
}
public boolean getBoolean(final String name) {
return BooleanUtils.toBoolean(this.params.getFirst(name));
}
public Boolean getBooleanObject(final String name) {
return BooleanUtils.toBooleanObject(this.params.getFirst(name));
}
public Integer getBooleanAsInteger(final String name) {
final Boolean booleanObject = getBooleanObject(name);
if (booleanObject == null) {
return null;
}
return BooleanUtils.toIntegerObject(booleanObject);
}
public DateTimeZone getDateTimeZone(final String name) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
try {
return DateTimeZone.forID(value);
} catch (final Exception e) {
return null;
}
}
public Set<String> getStringSet(final String name) {
final List<String> list = this.params.get(name);
if (list == null) {
return Collections.emptySet();
}
return Utils.immutableSetOf(list);
}
public <T extends Enum<T>> T getEnum(final String name, final Class<T> type, final T defaultValue) {
final T result = getEnum(name, type);
if (result == null) {
return defaultValue;
}
return result;
}
public <T extends Enum<T>> T getEnum(final String name, final Class<T> type) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
try {
return Enum.valueOf(type, value);
} catch (final Exception e) {
return null;
}
}
public DateTime getDateTime(final String name) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
return Utils.toDateTime(value);
}
public List<Threshold> getThresholds() {
final List<String> thresholdStrings = this.params.get(Domain.THRESHOLD.REFERENCE_NAME);
if (thresholdStrings == null || thresholdStrings.isEmpty()) {
return Collections.emptyList();
}
return thresholdStrings.stream()
.map(ts -> {
try {
final String[] split = StringUtils.split(ts, Constants.EMBEDDED_LIST_SEPARATOR);
return new Threshold(Double.parseDouble(split[0]), split[1]);
} catch (final Exception e) {
return null;
}
})
.collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
public <T extends POSTMapper> T putIfAbsent(final String name, final String value) {
this.params.putIfAbsent(name, Arrays.asList(value));
return (T) this;
}
}
/*
* 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.gbl.api;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.util.Utils;
public class POSTMapper {
public static final POSTMapper EMPTY_MAP = new POSTMapper(null);
protected final MultiValueMap<String, String> params;
public POSTMapper(final MultiValueMap<String, String> params) {
super();
this.params = params != null
? new LinkedMultiValueMap<>(params)
: new LinkedMultiValueMap<>();
}
public String getString(final String name) {
return Utils.decodeFormURL_UTF_8(this.params.getFirst(name));
}
public char[] getCharArray(final String name) {
final String value = getString(name);
if (value == null || value.length() <= 0) {
return new char[] {};
}
return value.toCharArray();
}
public CharSequence getCharSequence(final String name) {
return CharBuffer.wrap(getCharArray(name));
}
public Long getLong(final String name) {
final String value = this.params.getFirst(name);
if (StringUtils.isBlank(value)) {
return null;
}
return Long.parseLong(value);
}
public Short getShort(final String name) {
final String value = this.params.getFirst(name);
if (StringUtils.isBlank(value)) {
return null;
}
return Short.parseShort(value);
}
public Integer getInteger(final String name) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
return Integer.parseInt(value);
}
public Locale getLocale(final String name) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
return Locale.forLanguageTag(value);
}
public boolean getBoolean(final String name) {
return BooleanUtils.toBoolean(this.params.getFirst(name));
}
public Boolean getBooleanObject(final String name) {
return BooleanUtils.toBooleanObject(this.params.getFirst(name));
}
public Integer getBooleanAsInteger(final String name) {
final Boolean booleanObject = getBooleanObject(name);
if (booleanObject == null) {
return null;
}
return BooleanUtils.toIntegerObject(booleanObject);
}
public DateTimeZone getDateTimeZone(final String name) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
try {
return DateTimeZone.forID(value);
} catch (final Exception e) {
return null;
}
}
public Set<String> getStringSet(final String name) {
final List<String> list = this.params.get(name);
if (list == null) {
return Collections.emptySet();
}
return Utils.immutableSetOf(list);
}
public <T extends Enum<T>> T getEnum(final String name, final Class<T> type, final T defaultValue) {
final T result = getEnum(name, type);
if (result == null) {
return defaultValue;
}
return result;
}
public <T extends Enum<T>> T getEnum(final String name, final Class<T> type) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
try {
return Enum.valueOf(type, value);
} catch (final Exception e) {
return null;
}
}
public DateTime getDateTime(final String name) {
final String value = this.params.getFirst(name);
if (value == null) {
return null;
}
return Utils.toDateTime(value);
}
public List<Threshold> getThresholds() {
final List<String> thresholdStrings = this.params.get(Domain.THRESHOLD.REFERENCE_NAME);
if (thresholdStrings == null || thresholdStrings.isEmpty()) {
return Collections.emptyList();
}
return thresholdStrings.stream()
.map(ts -> {
try {
final String[] split = StringUtils.split(ts, Constants.EMBEDDED_LIST_SEPARATOR);
return new Threshold(Double.parseDouble(split[0]), split[1]);
} catch (final Exception e) {
return null;
}
})
.collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
public <T extends POSTMapper> T putIfAbsent(final String name, final String value) {
this.params.putIfAbsent(name, Arrays.asList(value));
return (T) this;
}
}

View file

@ -1,200 +1,337 @@
/*
* 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.gbl.model.sebconfig;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.URL;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_CLIENT_CONFIGURATION;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
public final class SebClientConfig implements GrantEntity, Activatable {
public static final String ATTR_FALLBACK_START_URL = "fallback_start_url";
public static final String ATTR_CONFIRM_ENCRYPT_SECRET = "confirm_encrypt_secret";
public static final String FILTER_ATTR_CREATION_DATE = "creation_date";
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID)
public final Long id;
@NotNull
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID)
public final Long institutionId;
@NotNull(message = "clientconfig:name:notNull")
@Size(min = 3, max = 255, message = "clientconfig:name:size:{min}:{max}:${validatedValue}")
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME)
public final String name;
@JsonProperty(ATTR_FALLBACK_START_URL)
@URL(message = "clientconfig:fallback_start_url:invalidURL")
public final String fallbackStartURL;
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE)
public final DateTime date;
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET)
public final CharSequence encryptSecret;
@JsonProperty(ATTR_CONFIRM_ENCRYPT_SECRET)
public final CharSequence confirmEncryptSecret;
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE)
public final Boolean active;
@JsonCreator
public SebClientConfig(
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID) final Long id,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID) final Long institutionId,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME) final String name,
@JsonProperty(ATTR_FALLBACK_START_URL) final String fallbackStartURL,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE) final DateTime date,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret,
@JsonProperty(ATTR_CONFIRM_ENCRYPT_SECRET) final CharSequence confirmEncryptSecret,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active) {
this.id = id;
this.institutionId = institutionId;
this.name = name;
this.fallbackStartURL = fallbackStartURL;
this.date = date;
this.encryptSecret = encryptSecret;
this.confirmEncryptSecret = confirmEncryptSecret;
this.active = active;
}
public SebClientConfig(final Long institutionId, final POSTMapper postParams) {
this.id = null;
this.institutionId = institutionId;
this.name = postParams.getString(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME);
this.fallbackStartURL = postParams.getString(ATTR_FALLBACK_START_URL);
this.date = postParams.getDateTime(Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE);
this.encryptSecret = postParams.getCharSequence(Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET);
this.confirmEncryptSecret = postParams.getCharSequence(ATTR_CONFIRM_ENCRYPT_SECRET);
this.active = false;
}
@Override
public EntityType entityType() {
return EntityType.SEB_CLIENT_CONFIGURATION;
}
@Override
public String getName() {
return this.name;
}
public String getFallbackStartURL() {
return this.fallbackStartURL;
}
@Override
public String getModelId() {
return (this.id != null)
? String.valueOf(this.id)
: null;
}
@Override
public boolean isActive() {
return this.active;
}
@Override
public Long getInstitutionId() {
return this.institutionId;
}
public Long getId() {
return this.id;
}
public DateTime getDate() {
return this.date;
}
@JsonIgnore
public CharSequence getEncryptSecret() {
return this.encryptSecret;
}
@JsonIgnore
public CharSequence getConfirmEncryptSecret() {
return this.confirmEncryptSecret;
}
@JsonIgnore
public boolean hasEncryptionSecret() {
return this.encryptSecret != null && this.encryptSecret.length() > 0;
}
public Boolean getActive() {
return this.active;
}
@Override
public Entity printSecureCopy() {
return new SebClientConfig(
this.id,
this.institutionId,
this.name,
this.fallbackStartURL,
this.date,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
this.active);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("SebClientConfig [id=");
builder.append(this.id);
builder.append(", institutionId=");
builder.append(this.institutionId);
builder.append(", name=");
builder.append(this.name);
builder.append(", fallbackStartURL=");
builder.append(this.fallbackStartURL);
builder.append(", date=");
builder.append(this.date);
builder.append(", active=");
builder.append(this.active);
builder.append("]");
return builder.toString();
}
public static final SebClientConfig createNew(final Long institutionId) {
return new SebClientConfig(
null,
institutionId,
null,
null,
DateTime.now(DateTimeZone.UTC),
null,
null,
false);
}
}
/*
* 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.gbl.model.sebconfig;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.URL;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_CLIENT_CONFIGURATION;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
public final class SebClientConfig implements GrantEntity, Activatable {
public static final String ATTR_CONFIG_PURPOSE = "sebConfigPurpose";
public static final String ATTR_FALLBACK = "sebServerFallback ";
public static final String ATTR_FALLBACK_START_URL = "startURL";
public static final String ATTR_FALLBACK_TIMEOUT = "sebServerFallbackTimeout";
public static final String ATTR_FALLBACK_ATTEMPTS = "sebServerFallbackAttempts";
public static final String ATTR_FALLBACK_ATTEMPT_INTERVAL = "sebServerFallbackAttemptInterval";
public static final String ATTR_FALLBACK_PASSWORD = "sebServerFallbackPasswordHash";
public static final String ATTR_FALLBACK_PASSWORD_CONFIRM = "sebServerFallbackPasswordHashConfirm";
public static final String ATTR_QUIT_PASSWORD = "hashedQuitPassword";
public static final String ATTR_QUIT_PASSWORD_CONFIRM = "hashedQuitPasswordConfirm";
public static final String ATTR_ENCRYPT_SECRET_CONFIRM = "confirm_encrypt_secret";
public static final String FILTER_ATTR_CREATION_DATE = "creation_date";
public enum ConfigPurpose {
START_EXAM,
CONFIGURE_CLIENT
}
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID)
public final Long id;
@NotNull
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID)
public final Long institutionId;
@NotNull(message = "clientconfig:name:notNull")
@Size(min = 3, max = 255, message = "clientconfig:name:size:{min}:{max}:${validatedValue}")
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME)
public final String name;
@NotNull(message = "clientconfig:sebConfigPurpose:notNull")
@JsonProperty(ATTR_CONFIG_PURPOSE)
public final ConfigPurpose configPurpose;
@JsonProperty(ATTR_FALLBACK)
public final Boolean fallback;
@JsonProperty(ATTR_FALLBACK_START_URL)
@URL(message = "clientconfig:startURL:invalidURL")
public final String fallbackStartURL;
@JsonProperty(ATTR_FALLBACK_TIMEOUT)
public final Long fallbackTimeout;
@JsonProperty(ATTR_FALLBACK_ATTEMPTS)
public final Short fallbackAttempts;
@JsonProperty(ATTR_FALLBACK_ATTEMPT_INTERVAL)
public final Short fallbackAttemptInterval;
@JsonProperty(ATTR_FALLBACK_PASSWORD)
public final CharSequence fallbackPassword;
@JsonProperty(ATTR_FALLBACK_PASSWORD_CONFIRM)
public final CharSequence fallbackPasswordConfirm;
@JsonProperty(ATTR_QUIT_PASSWORD)
public final CharSequence quitPassword;
@JsonProperty(ATTR_QUIT_PASSWORD_CONFIRM)
public final CharSequence quitPasswordConfirm;
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE)
public final DateTime date;
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET)
public final CharSequence encryptSecret;
@JsonProperty(ATTR_ENCRYPT_SECRET_CONFIRM)
public final CharSequence encryptSecretConfirm;
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE)
public final Boolean active;
@JsonCreator
public SebClientConfig(
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID) final Long id,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID) final Long institutionId,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME) final String name,
@JsonProperty(ATTR_CONFIG_PURPOSE) final ConfigPurpose configPurpose,
@JsonProperty(ATTR_FALLBACK) final Boolean fallback,
@JsonProperty(ATTR_FALLBACK_START_URL) final String fallbackStartURL,
@JsonProperty(ATTR_FALLBACK_TIMEOUT) final Long fallbackTimeout,
@JsonProperty(ATTR_FALLBACK_ATTEMPTS) final Short fallbackAttempts,
@JsonProperty(ATTR_FALLBACK_ATTEMPT_INTERVAL) final Short fallbackAttemptInterval,
@JsonProperty(ATTR_FALLBACK_PASSWORD) final CharSequence fallbackPassword,
@JsonProperty(ATTR_FALLBACK_PASSWORD_CONFIRM) final CharSequence fallbackPasswordConfirm,
@JsonProperty(ATTR_QUIT_PASSWORD) final CharSequence quitPassword,
@JsonProperty(ATTR_QUIT_PASSWORD_CONFIRM) final CharSequence quitPasswordConfirm,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE) final DateTime date,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret,
@JsonProperty(ATTR_ENCRYPT_SECRET_CONFIRM) final CharSequence encryptSecretConfirm,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active) {
this.id = id;
this.institutionId = institutionId;
this.name = name;
this.configPurpose = configPurpose;
this.fallback = fallback;
this.fallbackStartURL = fallbackStartURL;
this.fallbackTimeout = fallbackTimeout;
this.fallbackAttempts = fallbackAttempts;
this.fallbackAttemptInterval = fallbackAttemptInterval;
this.fallbackPassword = fallbackPassword;
this.fallbackPasswordConfirm = fallbackPasswordConfirm;
this.quitPassword = quitPassword;
this.quitPasswordConfirm = quitPasswordConfirm;
this.date = date;
this.encryptSecret = encryptSecret;
this.encryptSecretConfirm = encryptSecretConfirm;
this.active = active;
}
public SebClientConfig(final Long institutionId, final POSTMapper postParams) {
this.id = null;
this.institutionId = institutionId;
this.name = postParams.getString(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME);
this.configPurpose = postParams.getEnum(ATTR_CONFIG_PURPOSE, ConfigPurpose.class);
this.fallback = postParams.getBoolean(ATTR_FALLBACK);
this.fallbackStartURL = postParams.getString(ATTR_FALLBACK_START_URL);
this.fallbackTimeout = postParams.getLong(ATTR_FALLBACK_TIMEOUT);
this.fallbackAttempts = postParams.getShort(ATTR_FALLBACK_ATTEMPTS);
this.fallbackAttemptInterval = postParams.getShort(ATTR_FALLBACK_ATTEMPT_INTERVAL);
this.fallbackPassword = postParams.getCharSequence(ATTR_FALLBACK_PASSWORD);
this.fallbackPasswordConfirm = postParams.getCharSequence(ATTR_FALLBACK_PASSWORD_CONFIRM);
this.quitPassword = postParams.getCharSequence(ATTR_QUIT_PASSWORD);
this.quitPasswordConfirm = postParams.getCharSequence(ATTR_QUIT_PASSWORD_CONFIRM);
this.date = postParams.getDateTime(Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE);
this.encryptSecret = postParams.getCharSequence(Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET);
this.encryptSecretConfirm = postParams.getCharSequence(ATTR_ENCRYPT_SECRET_CONFIRM);
this.active = false;
}
@Override
public EntityType entityType() {
return EntityType.SEB_CLIENT_CONFIGURATION;
}
@Override
public String getName() {
return this.name;
}
public String getFallbackStartURL() {
return this.fallbackStartURL;
}
@Override
public String getModelId() {
return (this.id != null)
? String.valueOf(this.id)
: null;
}
@Override
public boolean isActive() {
return this.active;
}
@Override
public Long getInstitutionId() {
return this.institutionId;
}
public Long getId() {
return this.id;
}
public ConfigPurpose getConfigPurpose() {
return configPurpose;
}
public Boolean getFallback() {
return fallback;
}
public Long getFallbackTimeout() {
return fallbackTimeout;
}
public Short getFallbackAttempts() {
return fallbackAttempts;
}
public Short getFallbackAttemptInterval() {
return fallbackAttemptInterval;
}
public CharSequence getFallbackPassword() {
return fallbackPassword;
}
@JsonIgnore
public CharSequence getFallbackPasswordConfirm() {
return fallbackPasswordConfirm;
}
public CharSequence getQuitPassword() {
return quitPassword;
}
@JsonIgnore
public CharSequence getQuitPasswordConfirm() {
return quitPasswordConfirm;
}
public DateTime getDate() {
return this.date;
}
public CharSequence getEncryptSecret() {
return this.encryptSecret;
}
@JsonIgnore
public CharSequence getEncryptSecretConfirm() {
return this.encryptSecretConfirm;
}
@JsonIgnore
public boolean hasEncryptionSecret() {
return this.encryptSecret != null && this.encryptSecret.length() > 0;
}
@JsonIgnore
public boolean hasFallbackPassword() {
return this.fallbackPassword != null && this.fallbackPassword.length() > 0;
}
@JsonIgnore
public boolean hasQuitPassword() {
return this.quitPassword != null && this.quitPassword.length() > 0;
}
public Boolean getActive() {
return this.active;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("SebClientConfig{");
sb.append("id=").append(id);
sb.append(", institutionId=").append(institutionId);
sb.append(", name='").append(name).append('\'');
sb.append(", configPurpose=").append(configPurpose);
sb.append(", fallback=").append(fallback);
sb.append(", fallbackStartURL='").append(fallbackStartURL).append('\'');
sb.append(", fallbackTimeout=").append(fallbackTimeout);
sb.append(", fallbackAttempts=").append(fallbackAttempts);
sb.append(", fallbackAttemptInterval=").append(fallbackAttemptInterval);
sb.append(", fallbackPassword=").append(fallbackPassword);
sb.append(", fallbackPasswordConfirm=").append(fallbackPasswordConfirm);
sb.append(", date=").append(date);
sb.append(", encryptSecret=").append(encryptSecret);
sb.append(", encryptSecretConfirm=").append(encryptSecretConfirm);
sb.append(", active=").append(active);
sb.append('}');
return sb.toString();
}
@Override
public Entity printSecureCopy() {
return new SebClientConfig(
this.id,
this.institutionId,
this.name,
this.configPurpose,
this.fallback,
this.fallbackStartURL,
this.fallbackTimeout,
this.fallbackAttempts,
this.fallbackAttemptInterval,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
this.date,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
this.active);
}
public static SebClientConfig createNew(final Long institutionId) {
return new SebClientConfig(
null,
institutionId,
null,
ConfigPurpose.START_EXAM,
false,
null,
null,
null,
null,
null,
null,
null,
null,
DateTime.now(DateTimeZone.UTC),
null,
null,
false);
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2020 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.gbl.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.keygen.KeyGenerators;
import org.springframework.stereotype.Service;
@Lazy
@Service
public class Cryptor {
private static final Logger log = LoggerFactory.getLogger(Cryptor.class);
public static final String SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY = "sebserver.webservice.internalSecret";
private final Environment environment;
public Cryptor(final Environment environment) {
this.environment = environment;
}
public CharSequence encrypt(final CharSequence text) {
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return encrypt(text, secret);
}
public CharSequence decrypt(final CharSequence text) {
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return decrypt(text, secret);
}
public static CharSequence encrypt(final CharSequence text, final CharSequence secret) {
if (text == null) {
throw new IllegalArgumentException("Text has null reference");
}
if (secret == null) {
log.warn("No internal secret supplied: skip encryption");
return text;
}
try {
final CharSequence salt = KeyGenerators.string().generateKey();
final CharSequence cipher = Encryptors
.delux(secret, salt)
.encrypt(text.toString());
return new StringBuilder(cipher)
.append(salt);
} catch (final Exception e) {
log.error("Failed to encrypt text: ", e);
throw e;
}
}
public static CharSequence decrypt(final CharSequence cipher, final CharSequence secret) {
if (cipher == null) {
throw new IllegalArgumentException("Cipher has null reference");
}
if (secret == null) {
log.warn("No internal secret supplied: skip decryption");
return cipher;
}
try {
final int length = cipher.length();
final int cipherTextLength = length - 16;
final CharSequence salt = cipher.subSequence(cipherTextLength, length);
final CharSequence cipherText = cipher.subSequence(0, cipherTextLength);
return Encryptors
.delux(secret, salt)
.decrypt(cipherText.toString());
} catch (final Exception e) {
log.error("Failed to decrypt text: ", e);
throw e;
}
}
}

View file

@ -14,6 +14,8 @@ import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -28,6 +30,7 @@ import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.eclipse.swt.graphics.RGB;
@ -478,6 +481,20 @@ public final class Utils {
return (text == null) ? null : Constants.PERCENTAGE + text + Constants.PERCENTAGE;
}
public static String hash_SHA_256_Base_16(final CharSequence chars) {
if (chars == null) {
return null;
}
try {
final MessageDigest digest = MessageDigest.getInstance(Constants.SHA_256);
final byte[] encodedHash = digest.digest(toByteArray(chars));
return Hex.encodeHexString(encodedHash);
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException("Failed to hash text: ", nsae);
}
}
@SuppressWarnings("unchecked")
public static <T> Predicate<T> truePredicate() {
return (Predicate<T>) TRUE_PREDICATE;

View file

@ -213,7 +213,7 @@ final class ExamToConfigBindingForm {
String.valueOf(examConfigurationMap.configurationNodeId),
resourceService::examConfigurationSelectionResources)
.withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService))
.mandatory())
.mandatory())
.addField(FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
@ -228,14 +228,15 @@ final class ExamToConfigBindingForm {
resourceService.localizedExamConfigStatusName(examConfigurationMap))
.readonly(true))
.addField(FormBuilder.text(
.addField(FormBuilder.password(
Domain.EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET,
FORM_ENCRYPT_SECRET_TEXT_KEY)
.asPasswordField())
.addField(FormBuilder.text(
FORM_ENCRYPT_SECRET_TEXT_KEY,
examConfigurationMap.encryptSecret))
.addField(FormBuilder.password(
ExamConfigurationMap.ATTR_CONFIRM_ENCRYPT_SECRET,
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY)
.asPasswordField())
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY,
examConfigurationMap.encryptSecret))
.build();

View file

@ -8,8 +8,14 @@
package ch.ethz.seb.sebserver.gui.content;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.UrlLauncher;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
@ -42,6 +48,10 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Lazy
@Component
@GuiProfile
@ -53,15 +63,51 @@ public class SebClientConfigForm implements TemplateComposer {
new LocTextKey("sebserver.clientconfig.form.title");
private static final LocTextKey FORM_NAME_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.name");
private static final LocTextKey FORM_FALLBACK_URL_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.fallback-url");
private static final LocTextKey FORM_DATE_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.date");
private static final LocTextKey CLIENT_PURPOSE_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.sebConfigPurpose");
private static final LocTextKey FALLBACK_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.fallback");
private static final LocTextKey FALLBACK_URL_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.fallback-url");
private static final LocTextKey FALLBACK_TIMEOUT_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackTimeout");
private static final LocTextKey FALLBACK_ATTEMPTS_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackAttempts");
private static final LocTextKey FALLBACK_ATTEMPT_INTERVAL_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackAttemptInterval");
private static final LocTextKey FALLBACK_PASSWORD_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackPasswordHash");
private static final LocTextKey FALLBACK_PASSWORD_CONFIRM_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackPasswordHash");
private static final LocTextKey QUIT_PASSWORD_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.hashedQuitPassword");
private static final LocTextKey QUIT_PASSWORD_CONFIRM_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.hashedQuitPassword.confirm");
private static final LocTextKey FORM_ENCRYPT_SECRET_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.encryptSecret");
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.encryptSecret.confirm");
private static final Set<String> FALLBACK_ATTRIBUTES = new HashSet<>(Arrays.asList(
SebClientConfig.ATTR_FALLBACK_START_URL,
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
SebClientConfig.ATTR_FALLBACK_PASSWORD,
SebClientConfig.ATTR_FALLBACK_PASSWORD_CONFIRM,
SebClientConfig.ATTR_QUIT_PASSWORD,
SebClientConfig.ATTR_QUIT_PASSWORD_CONFIRM
));
private static final String FALLBACK_DEFAULT_TIME = String.valueOf(2 * Constants.MINUTE_IN_MILLIS);
private static final String FALLBACK_DEFAULT_ATTEMPTS = String.valueOf(5);
private static final String FALLBACK_DEFAULT_ATTEMPT_INTERVAL = String.valueOf(2 * Constants.SECOND_IN_MILLIS);
private final PageService pageService;
private final RestService restService;
private final CurrentUser currentUser;
@ -131,32 +177,147 @@ public class SebClientConfigForm implements TemplateComposer {
.putStaticValue(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID,
String.valueOf(clientConfig.getInstitutionId()))
.addField(FormBuilder.text(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME,
FORM_NAME_TEXT_KEY,
clientConfig.name))
.addField(FormBuilder.text(
SebClientConfig.ATTR_FALLBACK_START_URL,
FORM_FALLBACK_URL_TEXT_KEY,
clientConfig.fallbackStartURL))
.addFieldIf(() -> !isNew,
() -> FormBuilder.text(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE,
FORM_DATE_TEXT_KEY,
i18nSupport.formatDisplayDateWithTimeZone(clientConfig.date))
.readonly(true))
.addField(FormBuilder.text(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME,
FORM_NAME_TEXT_KEY,
clientConfig.name)
.mandatory(!isReadonly))
.addField(FormBuilder.singleSelection(
SebClientConfig.ATTR_CONFIG_PURPOSE,
CLIENT_PURPOSE_TEXT_KEY,
clientConfig.configPurpose != null
? clientConfig.configPurpose.name()
: SebClientConfig.ConfigPurpose.START_EXAM.name(),
() -> pageService.getResourceService().sebClientConfigPurposeResources())
.mandatory(!isReadonly))
.withDefaultSpanInput(3)
.addField(FormBuilder.password(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET,
FORM_ENCRYPT_SECRET_TEXT_KEY)
.asPasswordField())
FORM_ENCRYPT_SECRET_TEXT_KEY,
clientConfig.getEncryptSecret()))
.withDefaultSpanEmptyCell(3)
.addFieldIf(
() -> !isReadonly,
() -> FormBuilder.password(
SebClientConfig.ATTR_ENCRYPT_SECRET_CONFIRM,
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY,
clientConfig.getEncryptSecret()))
.addField(FormBuilder.checkbox(
SebClientConfig.ATTR_FALLBACK,
FALLBACK_TEXT_KEY,
clientConfig.fallback != null
? clientConfig.fallback.toString()
: Constants.FALSE_STRING)
)
.withDefaultSpanInput(5)
.addField(FormBuilder.text(
SebClientConfig.ATTR_CONFIRM_ENCRYPT_SECRET,
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY)
.asPasswordField())
SebClientConfig.ATTR_FALLBACK_START_URL,
FALLBACK_URL_TEXT_KEY,
clientConfig.fallbackStartURL)
.mandatory(!isReadonly))
.withDefaultSpanEmptyCell(1)
.withDefaultSpanInput(2)
.addField(FormBuilder.text(
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
FALLBACK_ATTEMPTS_TEXT_KEY,
clientConfig.fallbackAttempts != null
? String.valueOf(clientConfig.fallbackAttempts)
: FALLBACK_DEFAULT_ATTEMPTS)
.asNumber(this::checkNaturalNumber)
.mandatory(!isReadonly))
.withDefaultSpanEmptyCell(0)
.withEmptyCellSeparation(false)
.withDefaultSpanLabel(1)
.addField(FormBuilder.text(
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
FALLBACK_ATTEMPT_INTERVAL_TEXT_KEY,
clientConfig.fallbackAttemptInterval != null
? String.valueOf(clientConfig.fallbackAttemptInterval)
: FALLBACK_DEFAULT_ATTEMPT_INTERVAL)
.asNumber(this::checkNaturalNumber)
.mandatory(!isReadonly))
.withEmptyCellSeparation(true)
.withDefaultSpanEmptyCell(1)
.withDefaultSpanLabel(2)
.addField(FormBuilder.text(
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
FALLBACK_TIMEOUT_TEXT_KEY,
clientConfig.fallbackTimeout != null
? String.valueOf(clientConfig.fallbackTimeout)
: FALLBACK_DEFAULT_TIME)
.asNumber(this::checkNaturalNumber)
.mandatory(!isReadonly))
.withEmptyCellSeparation(true)
.withDefaultSpanEmptyCell(4)
.withDefaultSpanInput(2)
.withDefaultSpanLabel(2)
.addField(FormBuilder.password(
SebClientConfig.ATTR_FALLBACK_PASSWORD,
FALLBACK_PASSWORD_TEXT_KEY,
clientConfig.getFallbackPassword()))
.withEmptyCellSeparation(false)
.withDefaultSpanLabel(1)
.addField(FormBuilder.password(
SebClientConfig.ATTR_QUIT_PASSWORD,
QUIT_PASSWORD_TEXT_KEY,
clientConfig.getQuitPassword()))
.withEmptyCellSeparation(true)
.withDefaultSpanEmptyCell(1)
.withDefaultSpanInput(2)
.withDefaultSpanLabel(2)
.addFieldIf(
() -> !isReadonly,
() -> FormBuilder.password(
SebClientConfig.ATTR_FALLBACK_PASSWORD_CONFIRM,
FALLBACK_PASSWORD_CONFIRM_TEXT_KEY,
clientConfig.getFallbackPasswordConfirm()))
.withEmptyCellSeparation(false)
.withDefaultSpanLabel(1)
.addFieldIf(
() -> !isReadonly,
() -> FormBuilder.password(
SebClientConfig.ATTR_QUIT_PASSWORD_CONFIRM,
QUIT_PASSWORD_CONFIRM_TEXT_KEY,
clientConfig.getQuitPasswordConfirm()))
.buildFor((isNew)
? this.restService.getRestCall(NewClientConfig.class)
: this.restService.getRestCall(SaveClientConfig.class));
formHandle.process(
FALLBACK_ATTRIBUTES::contains,
ffa -> ffa.setVisible(BooleanUtils.isTrue(clientConfig.fallback))
);
formHandle.getForm().getFieldInput(SebClientConfig.ATTR_FALLBACK)
.addListener(SWT.Selection, event -> {
formHandle.process(
FALLBACK_ATTRIBUTES::contains,
ffa -> ffa.setVisible(((Button) event.widget).getSelection())
);
});
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
@ -208,4 +369,14 @@ public class SebClientConfigForm implements TemplateComposer {
.publishIf(() -> !isReadonly);
}
private void checkNaturalNumber(String value) {
if (StringUtils.isBlank(value)) {
return;
}
long num = Long.parseLong(value);
if (num < 0) {
throw new PageMessageException("Number must be positive");
}
}
}

View file

@ -1,281 +1,281 @@
/*
* 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.content;
import java.io.InputStream;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfigOnExistingConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportNewExamConfig;
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
final class SebExamConfigImportPopup {
private final static PageMessageException MISSING_PASSWORD = new PageMessageException(
new LocTextKey("sebserver.examconfig.action.import.missing-password"));
static Function<PageAction, PageAction> importFunction(
final PageService pageService,
final boolean newConfig) {
return action -> {
final ModalInputDialog<FormHandle<ConfigurationNode>> dialog =
new ModalInputDialog<FormHandle<ConfigurationNode>>(
action.pageContext().getParent().getShell(),
pageService.getWidgetFactory())
.setLargeDialogWidth();
final ImportFormContext importFormContext = new ImportFormContext(
pageService,
action.pageContext(),
newConfig);
dialog.open(
SebExamConfigPropForm.FORM_IMPORT_TEXT_KEY,
(Predicate<FormHandle<ConfigurationNode>>) formHandle -> doImport(
pageService,
formHandle,
newConfig),
importFormContext::cancelUpload,
importFormContext);
return action;
};
}
private static final boolean doImport(
final PageService pageService,
final FormHandle<ConfigurationNode> formHandle,
final boolean newConfig) {
try {
final Form form = formHandle.getForm();
final EntityKey entityKey = formHandle.getContext().getEntityKey();
final Control fieldControl = form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
final PageContext context = formHandle.getContext();
// Ad-hoc field validation
formHandle.process(name -> true, field -> field.resetError());
final String fieldValue = form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME);
if (StringUtils.isBlank(fieldValue)) {
form.setFieldError(
Domain.CONFIGURATION_NODE.ATTR_NAME,
pageService
.getI18nSupport()
.getText(new LocTextKey("sebserver.form.validation.fieldError.notNull")));
return false;
} else if (fieldValue.length() < 3 || fieldValue.length() > 255) {
form.setFieldError(
Domain.CONFIGURATION_NODE.ATTR_NAME,
pageService
.getI18nSupport()
.getText(new LocTextKey("sebserver.form.validation.fieldError.size",
null,
null,
null,
3,
255)));
return false;
}
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl;
final InputStream inputStream = fileUpload.getInputStream();
if (inputStream != null) {
final RestCall<Configuration>.RestCallBuilder restCall = (newConfig)
? pageService.getRestService()
.getBuilder(ImportNewExamConfig.class)
: pageService.getRestService()
.getBuilder(ImportExamConfigOnExistingConfig.class);
restCall
.withHeader(
API.IMPORT_PASSWORD_ATTR_NAME,
form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME))
.withBody(inputStream);
if (newConfig) {
restCall
.withHeader(
Domain.CONFIGURATION_NODE.ATTR_NAME,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME))
.withHeader(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION))
.withHeader(
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID));
} else {
restCall.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId);
}
final Result<Configuration> configuration = restCall
.call();
if (!configuration.hasError()) {
context.publishInfo(SebExamConfigPropForm.FORM_IMPORT_CONFIRM_TEXT_KEY);
if (newConfig) {
final PageAction action = pageService.pageActionBuilder(context)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG)
.create();
pageService.firePageEvent(
new ActionEvent(action),
action.pageContext());
}
return true;
} else {
final Exception error = configuration.getError();
if (error instanceof RestCallError) {
((RestCallError) error)
.getErrorMessages()
.stream()
.findFirst()
.ifPresent(message -> {
if (APIMessage.ErrorMessage.MISSING_PASSWORD.isOf(message)) {
formHandle
.getContext()
.publishPageMessage(MISSING_PASSWORD);
} else {
formHandle
.getContext()
.notifyImportError(EntityType.CONFIGURATION_NODE, error);
}
});
return true;
}
formHandle.getContext().notifyError(
SebExamConfigPropForm.FORM_TITLE,
configuration.getError());
return true;
}
} else {
formHandle.getContext().publishPageMessage(
new LocTextKey("sebserver.error.unexpected"),
new LocTextKey("Please selecte a valid SEB Exam Configuration File"));
}
}
return false;
} catch (final Exception e) {
formHandle.getContext().notifyError(SebExamConfigPropForm.FORM_TITLE, e);
return true;
}
}
private static final class ImportFormContext implements ModalInputDialogComposer<FormHandle<ConfigurationNode>> {
private final PageService pageService;
private final PageContext pageContext;
private final boolean newConfig;
private Form form = null;
protected ImportFormContext(
final PageService pageService,
final PageContext pageContext,
final boolean newConfig) {
this.pageService = pageService;
this.pageContext = pageContext;
this.newConfig = newConfig;
}
@Override
public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) {
final Composite grid = this.pageService.getWidgetFactory()
.createPopupScrollComposite(parent);
final ResourceService resourceService = this.pageService.getResourceService();
final List<Tuple<String>> examConfigTemplateResources = resourceService.getExamConfigTemplateResources();
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
this.pageContext.copyOf(grid))
.readonly(false)
.addField(FormBuilder.fileUpload(
API.IMPORT_FILE_ATTR_NAME,
SebExamConfigPropForm.FORM_IMPORT_SELECT_TEXT_KEY,
null,
API.SEB_FILE_EXTENSION))
.addFieldIf(
() -> this.newConfig,
() -> FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_NAME,
SebExamConfigPropForm.FORM_NAME_TEXT_KEY))
.addFieldIf(
() -> this.newConfig,
() -> FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
.asArea())
.addFieldIf(
() -> this.newConfig && !examConfigTemplateResources.isEmpty(),
() -> FormBuilder.singleSelection(
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
SebExamConfigPropForm.FORM_TEMPLATE_TEXT_KEY,
null,
resourceService::getExamConfigTemplateResources))
.addField(FormBuilder.text(
API.IMPORT_PASSWORD_ATTR_NAME,
SebExamConfigPropForm.FORM_IMPORT_PASSWORD_TEXT_KEY,
"").asPasswordField())
.build();
this.form = formHandle.getForm();
return () -> formHandle;
}
void cancelUpload() {
if (this.form != null) {
final Control fieldControl = this.form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
((FileUploadSelection) fieldControl).close();
}
}
}
}
}
/*
* 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.content;
import java.io.InputStream;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfigOnExistingConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportNewExamConfig;
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
final class SebExamConfigImportPopup {
private final static PageMessageException MISSING_PASSWORD = new PageMessageException(
new LocTextKey("sebserver.examconfig.action.import.missing-password"));
static Function<PageAction, PageAction> importFunction(
final PageService pageService,
final boolean newConfig) {
return action -> {
final ModalInputDialog<FormHandle<ConfigurationNode>> dialog =
new ModalInputDialog<FormHandle<ConfigurationNode>>(
action.pageContext().getParent().getShell(),
pageService.getWidgetFactory())
.setLargeDialogWidth();
final ImportFormContext importFormContext = new ImportFormContext(
pageService,
action.pageContext(),
newConfig);
dialog.open(
SebExamConfigPropForm.FORM_IMPORT_TEXT_KEY,
(Predicate<FormHandle<ConfigurationNode>>) formHandle -> doImport(
pageService,
formHandle,
newConfig),
importFormContext::cancelUpload,
importFormContext);
return action;
};
}
private static final boolean doImport(
final PageService pageService,
final FormHandle<ConfigurationNode> formHandle,
final boolean newConfig) {
try {
final Form form = formHandle.getForm();
final EntityKey entityKey = formHandle.getContext().getEntityKey();
final Control fieldControl = form.getFieldInput(API.IMPORT_FILE_ATTR_NAME);
final PageContext context = formHandle.getContext();
// Ad-hoc field validation
formHandle.process(name -> true, field -> field.resetError());
final String fieldValue = form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME);
if (StringUtils.isBlank(fieldValue)) {
form.setFieldError(
Domain.CONFIGURATION_NODE.ATTR_NAME,
pageService
.getI18nSupport()
.getText(new LocTextKey("sebserver.form.validation.fieldError.notNull")));
return false;
} else if (fieldValue.length() < 3 || fieldValue.length() > 255) {
form.setFieldError(
Domain.CONFIGURATION_NODE.ATTR_NAME,
pageService
.getI18nSupport()
.getText(new LocTextKey("sebserver.form.validation.fieldError.size",
null,
null,
null,
3,
255)));
return false;
}
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl;
final InputStream inputStream = fileUpload.getInputStream();
if (inputStream != null) {
final RestCall<Configuration>.RestCallBuilder restCall = (newConfig)
? pageService.getRestService()
.getBuilder(ImportNewExamConfig.class)
: pageService.getRestService()
.getBuilder(ImportExamConfigOnExistingConfig.class);
restCall
.withHeader(
API.IMPORT_PASSWORD_ATTR_NAME,
form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME))
.withBody(inputStream);
if (newConfig) {
restCall
.withHeader(
Domain.CONFIGURATION_NODE.ATTR_NAME,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME))
.withHeader(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION))
.withHeader(
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID));
} else {
restCall.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId);
}
final Result<Configuration> configuration = restCall
.call();
if (!configuration.hasError()) {
context.publishInfo(SebExamConfigPropForm.FORM_IMPORT_CONFIRM_TEXT_KEY);
if (newConfig) {
final PageAction action = pageService.pageActionBuilder(context)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG)
.create();
pageService.firePageEvent(
new ActionEvent(action),
action.pageContext());
}
return true;
} else {
final Exception error = configuration.getError();
if (error instanceof RestCallError) {
((RestCallError) error)
.getErrorMessages()
.stream()
.findFirst()
.ifPresent(message -> {
if (APIMessage.ErrorMessage.MISSING_PASSWORD.isOf(message)) {
formHandle
.getContext()
.publishPageMessage(MISSING_PASSWORD);
} else {
formHandle
.getContext()
.notifyImportError(EntityType.CONFIGURATION_NODE, error);
}
});
return true;
}
formHandle.getContext().notifyError(
SebExamConfigPropForm.FORM_TITLE,
configuration.getError());
return true;
}
} else {
formHandle.getContext().publishPageMessage(
new LocTextKey("sebserver.error.unexpected"),
new LocTextKey("Please selecte a valid SEB Exam Configuration File"));
}
}
return false;
} catch (final Exception e) {
formHandle.getContext().notifyError(SebExamConfigPropForm.FORM_TITLE, e);
return true;
}
}
private static final class ImportFormContext implements ModalInputDialogComposer<FormHandle<ConfigurationNode>> {
private final PageService pageService;
private final PageContext pageContext;
private final boolean newConfig;
private Form form = null;
protected ImportFormContext(
final PageService pageService,
final PageContext pageContext,
final boolean newConfig) {
this.pageService = pageService;
this.pageContext = pageContext;
this.newConfig = newConfig;
}
@Override
public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) {
final Composite grid = this.pageService.getWidgetFactory()
.createPopupScrollComposite(parent);
final ResourceService resourceService = this.pageService.getResourceService();
final List<Tuple<String>> examConfigTemplateResources = resourceService.getExamConfigTemplateResources();
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
this.pageContext.copyOf(grid))
.readonly(false)
.addField(FormBuilder.fileUpload(
API.IMPORT_FILE_ATTR_NAME,
SebExamConfigPropForm.FORM_IMPORT_SELECT_TEXT_KEY,
null,
API.SEB_FILE_EXTENSION))
.addFieldIf(
() -> this.newConfig,
() -> FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_NAME,
SebExamConfigPropForm.FORM_NAME_TEXT_KEY))
.addFieldIf(
() -> this.newConfig,
() -> FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
.asArea())
.addFieldIf(
() -> this.newConfig && !examConfigTemplateResources.isEmpty(),
() -> FormBuilder.singleSelection(
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
SebExamConfigPropForm.FORM_TEMPLATE_TEXT_KEY,
null,
resourceService::getExamConfigTemplateResources))
.addField(FormBuilder.text(
API.IMPORT_PASSWORD_ATTR_NAME,
SebExamConfigPropForm.FORM_IMPORT_PASSWORD_TEXT_KEY,
"").asPasswordField())
.build();
this.form = formHandle.getForm();
return () -> formHandle;
}
void cancelUpload() {
if (this.form != null) {
final Control fieldControl = this.form.getFieldInput(API.IMPORT_FILE_ATTR_NAME);
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
((FileUploadSelection) fieldControl).close();
}
}
}
}
}

View file

@ -1,47 +1,54 @@
/*
* 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.form;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
public class CheckboxFieldBuilder extends FieldBuilder<String> {
protected CheckboxFieldBuilder(final String name, final LocTextKey label, final String value) {
super(name, label, value);
}
@Override
void build(final FormBuilder builder) {
final boolean readonly = builder.readonly || this.readonly;
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final Button checkbox = builder.widgetFactory.buttonLocalized(
fieldGrid,
SWT.CHECK,
null, null);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
checkbox.setLayoutData(gridData);
checkbox.setSelection(BooleanUtils.toBoolean(this.value));
if (readonly) {
checkbox.setEnabled(false);
}
builder.form.putField(this.name, titleLabel, checkbox);
}
}
/*
* 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.form;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import java.util.function.Consumer;
public class CheckboxFieldBuilder extends FieldBuilder<String> {
protected CheckboxFieldBuilder(
final String name,
final LocTextKey label,
final String value) {
super(name, label, value);
}
@Override
void build(final FormBuilder builder) {
final boolean readonly = builder.readonly || this.readonly;
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final Button checkbox = builder.widgetFactory.buttonLocalized(
fieldGrid,
SWT.CHECK,
null, null);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
checkbox.setLayoutData(gridData);
checkbox.setSelection(BooleanUtils.toBoolean(this.value));
if (readonly) {
checkbox.setEnabled(false);
}
builder.form.putField(this.name, titleLabel, checkbox);
}
}

View file

@ -1,238 +1,239 @@
/*
* 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.form;
import java.util.function.BooleanSupplier;
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 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 abstract class FieldBuilder<T> {
public static final LocTextKey MANDATORY_TEXT_KEY = new LocTextKey("sebserver.form.mandatory");
public static final String TOOLTIP_KEY_SUFFIX_LABEL = ".tooltip";
public static final String TOOLTIP_KEY_SUFFIX_LEFT = ".tooltip.left";
public static final String TOOLTIP_KEY_SUFFIX_RIGHT = ".tooltip.right";
int spanLabel = -1;
int spanInput = -1;
int spanEmptyCell = -1;
int titleValign = SWT.TOP;
Boolean autoEmptyCellSeparation = null;
String group = null;
boolean readonly = false;
boolean visible = true;
String defaultLabel = null;
boolean isMandatory = false;
final String name;
final LocTextKey label;
final LocTextKey tooltipLabel;
final LocTextKey tooltipKeyLeft;
final LocTextKey tooltipKeyRight;
final T value;
protected FieldBuilder(final String name, final LocTextKey label, final T value) {
this.name = name;
this.label = label;
this.value = value;
this.tooltipLabel = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_LABEL) : null;
this.tooltipKeyLeft = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_LEFT) : null;
this.tooltipKeyRight = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_RIGHT) : null;
}
public FieldBuilder<T> withDefaultLabel(final String defaultLabel) {
this.defaultLabel = defaultLabel;
return this;
}
public FieldBuilder<T> withLabelSpan(final int span) {
this.spanLabel = span;
return this;
}
public FieldBuilder<T> mandatory() {
this.isMandatory = true;
return this;
}
public FieldBuilder<T> mandatory(final boolean mandatory) {
this.isMandatory = mandatory;
return this;
}
public FieldBuilder<T> withInputSpan(final int span) {
this.spanInput = span;
return this;
}
public FieldBuilder<T> withEmptyCellSpan(final int span) {
this.spanEmptyCell = span;
return this;
}
public FieldBuilder<T> withEmptyCellSeparation(final boolean separation) {
this.autoEmptyCellSeparation = separation;
return this;
}
public FieldBuilder<T> withGroup(final String group) {
this.group = group;
return this;
}
public FieldBuilder<T> readonly(final boolean readonly) {
this.readonly = readonly;
return this;
}
public FieldBuilder<T> visibleIf(final boolean visible) {
this.visible = visible;
return this;
}
public FieldBuilder<T> readonlyIf(final BooleanSupplier readonly) {
this.readonly = readonly != null && readonly.getAsBoolean();
return this;
}
abstract void build(FormBuilder builder);
protected static Label createTitleLabel(
final Composite parent,
final FormBuilder builder,
final FieldBuilder<?> fieldBuilder) {
if (fieldBuilder.label == null) {
return null;
}
final Composite infoGrid = new Composite(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(4, false);
gridLayout.verticalSpacing = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
gridLayout.marginRight = 0;
infoGrid.setLayout(gridLayout);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.horizontalSpan = (fieldBuilder.spanLabel > 0) ? fieldBuilder.spanLabel : 1;
infoGrid.setLayoutData(gridData);
if (fieldBuilder.tooltipKeyLeft != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipKeyLeft, ""))) {
final Label info = builder.widgetFactory.imageButton(
WidgetFactory.ImageIcon.HELP,
infoGrid,
fieldBuilder.tooltipKeyLeft);
info.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
}
final boolean hasLabelTooltip = (fieldBuilder.tooltipLabel != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipLabel, "")));
final Label label = labelLocalized(
builder.widgetFactory,
infoGrid,
fieldBuilder.label,
fieldBuilder.defaultLabel,
(hasLabelTooltip) ? fieldBuilder.tooltipLabel : null,
1,
fieldBuilder.titleValign);
if (fieldBuilder.isMandatory) {
final Label mandatory = builder.widgetFactory.imageButton(
WidgetFactory.ImageIcon.MANDATORY,
infoGrid,
MANDATORY_TEXT_KEY);
mandatory.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
}
if (fieldBuilder.tooltipKeyRight != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipKeyRight, ""))) {
final Label info = builder.widgetFactory.imageButton(
WidgetFactory.ImageIcon.HELP,
infoGrid,
fieldBuilder.tooltipKeyRight);
info.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
}
return label;
}
public static Label labelLocalized(
final WidgetFactory widgetFactory,
final Composite parent,
final LocTextKey locTextKey,
final String defaultText,
final int hspan) {
return labelLocalized(widgetFactory, parent, locTextKey, defaultText, null, hspan, SWT.CENTER);
}
public static final Label labelLocalized(
final WidgetFactory widgetFactory,
final Composite parent,
final LocTextKey locTextKey,
final String defaultText,
final LocTextKey tooltipTextKey,
final int hspan,
final int verticalAlignment) {
final LocTextKey labelKey = StringUtils.isNotBlank(defaultText)
? new LocTextKey(defaultText)
: locTextKey;
final Label label = widgetFactory.labelLocalized(
parent,
labelKey,
tooltipTextKey);
final GridData gridData = new GridData(SWT.LEFT, verticalAlignment, false, false, hspan, 1);
gridData.heightHint = FormBuilder.FORM_ROW_HEIGHT;
label.setLayoutData(gridData);
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key);
return label;
}
public static Composite createFieldGrid(final Composite parent, final int hspan) {
final Composite fieldGrid = new Composite(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout();
gridLayout.verticalSpacing = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
gridLayout.marginRight = 0;
fieldGrid.setLayout(gridLayout);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.horizontalSpan = hspan;
fieldGrid.setLayoutData(gridData);
return fieldGrid;
}
public static Label createErrorLabel(final Composite innerGrid) {
final Label errorLabel = new Label(innerGrid, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
errorLabel.setLayoutData(gridData);
errorLabel.setVisible(false);
errorLabel.setData(RWT.CUSTOM_VARIANT, CustomVariant.ERROR.key);
return errorLabel;
}
/*
* 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.form;
import java.util.function.BooleanSupplier;
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.Control;
import org.eclipse.swt.widgets.Label;
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 abstract class FieldBuilder<T> {
public static final LocTextKey MANDATORY_TEXT_KEY = new LocTextKey("sebserver.form.mandatory");
public static final String TOOLTIP_KEY_SUFFIX_LABEL = ".tooltip";
public static final String TOOLTIP_KEY_SUFFIX_LEFT = ".tooltip.left";
public static final String TOOLTIP_KEY_SUFFIX_RIGHT = ".tooltip.right";
int spanLabel = -1;
int spanInput = -1;
int spanEmptyCell = -1;
int titleValign = SWT.TOP;
Boolean autoEmptyCellSeparation = null;
String group = null;
boolean readonly = false;
boolean visible = true;
String defaultLabel = null;
boolean isMandatory = false;
final String name;
final LocTextKey label;
final LocTextKey tooltipLabel;
final LocTextKey tooltipKeyLeft;
final LocTextKey tooltipKeyRight;
final T value;
protected FieldBuilder(final String name, final LocTextKey label, final T value) {
this.name = name;
this.label = label;
this.value = value;
this.tooltipLabel = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_LABEL) : null;
this.tooltipKeyLeft = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_LEFT) : null;
this.tooltipKeyRight = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_RIGHT) : null;
}
public FieldBuilder<T> withDefaultLabel(final String defaultLabel) {
this.defaultLabel = defaultLabel;
return this;
}
public FieldBuilder<T> withLabelSpan(final int span) {
this.spanLabel = span;
return this;
}
public FieldBuilder<T> mandatory() {
this.isMandatory = true;
return this;
}
public FieldBuilder<T> mandatory(final boolean mandatory) {
this.isMandatory = mandatory;
return this;
}
public FieldBuilder<T> withInputSpan(final int span) {
this.spanInput = span;
return this;
}
public FieldBuilder<T> withEmptyCellSpan(final int span) {
this.spanEmptyCell = span;
return this;
}
public FieldBuilder<T> withEmptyCellSeparation(final boolean separation) {
this.autoEmptyCellSeparation = separation;
return this;
}
public FieldBuilder<T> withGroup(final String group) {
this.group = group;
return this;
}
public FieldBuilder<T> readonly(final boolean readonly) {
this.readonly = readonly;
return this;
}
public FieldBuilder<T> visibleIf(final boolean visible) {
this.visible = visible;
return this;
}
public FieldBuilder<T> readonlyIf(final BooleanSupplier readonly) {
this.readonly = readonly != null && readonly.getAsBoolean();
return this;
}
abstract void build(FormBuilder builder);
protected static Control createTitleLabel(
final Composite parent,
final FormBuilder builder,
final FieldBuilder<?> fieldBuilder) {
if (fieldBuilder.label == null) {
return null;
}
final Composite infoGrid = new Composite(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(4, false);
gridLayout.verticalSpacing = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
gridLayout.marginRight = 0;
infoGrid.setLayout(gridLayout);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.horizontalSpan = (fieldBuilder.spanLabel > 0) ? fieldBuilder.spanLabel : 1;
infoGrid.setLayoutData(gridData);
if (fieldBuilder.tooltipKeyLeft != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipKeyLeft, ""))) {
final Label info = builder.widgetFactory.imageButton(
WidgetFactory.ImageIcon.HELP,
infoGrid,
fieldBuilder.tooltipKeyLeft);
info.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
}
final boolean hasLabelTooltip = (fieldBuilder.tooltipLabel != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipLabel, "")));
final Label label = labelLocalized(
builder.widgetFactory,
infoGrid,
fieldBuilder.label,
fieldBuilder.defaultLabel,
(hasLabelTooltip) ? fieldBuilder.tooltipLabel : null,
1,
fieldBuilder.titleValign);
if (fieldBuilder.isMandatory) {
final Label mandatory = builder.widgetFactory.imageButton(
WidgetFactory.ImageIcon.MANDATORY,
infoGrid,
MANDATORY_TEXT_KEY);
mandatory.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
}
if (fieldBuilder.tooltipKeyRight != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipKeyRight, ""))) {
final Label info = builder.widgetFactory.imageButton(
WidgetFactory.ImageIcon.HELP,
infoGrid,
fieldBuilder.tooltipKeyRight);
info.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
}
return infoGrid;
}
public static Label labelLocalized(
final WidgetFactory widgetFactory,
final Composite parent,
final LocTextKey locTextKey,
final String defaultText,
final int hspan) {
return labelLocalized(widgetFactory, parent, locTextKey, defaultText, null, hspan, SWT.CENTER);
}
public static Label labelLocalized(
final WidgetFactory widgetFactory,
final Composite parent,
final LocTextKey locTextKey,
final String defaultText,
final LocTextKey tooltipTextKey,
final int hspan,
final int verticalAlignment) {
final LocTextKey labelKey = StringUtils.isNotBlank(defaultText)
? new LocTextKey(defaultText)
: locTextKey;
final Label label = widgetFactory.labelLocalized(
parent,
labelKey,
tooltipTextKey);
final GridData gridData = new GridData(SWT.LEFT, verticalAlignment, false, false, hspan, 1);
gridData.heightHint = FormBuilder.FORM_ROW_HEIGHT;
label.setLayoutData(gridData);
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key);
return label;
}
public static Composite createFieldGrid(final Composite parent, final int hspan) {
final Composite fieldGrid = new Composite(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout();
gridLayout.verticalSpacing = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
gridLayout.marginRight = 0;
fieldGrid.setLayout(gridLayout);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.horizontalSpan = hspan;
fieldGrid.setLayoutData(gridData);
return fieldGrid;
}
public static Label createErrorLabel(final Composite innerGrid) {
final Label errorLabel = new Label(innerGrid, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
errorLabel.setLayoutData(gridData);
errorLabel.setVisible(false);
errorLabel.setData(RWT.CUSTOM_VARIANT, CustomVariant.ERROR.key);
return errorLabel;
}
}

View file

@ -1,52 +1,53 @@
/*
* 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.form;
import java.util.Collection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
public class FileUploadFieldBuilder extends FieldBuilder<String> {
private final Collection<String> supportedFiles;
FileUploadFieldBuilder(
final String name,
final LocTextKey label,
final String value,
final Collection<String> supportedFiles) {
super(name, label, value);
this.supportedFiles = supportedFiles;
}
@Override
void build(final FormBuilder builder) {
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final FileUploadSelection fileUpload = builder.widgetFactory.fileUploadSelection(
fieldGrid,
builder.readonly || this.readonly,
this.supportedFiles);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
fileUpload.setLayoutData(gridData);
fileUpload.setFileName(this.value);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, fileUpload, errorLabel);
builder.setFieldVisible(this.visible, this.name);
}
}
/*
* 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.form;
import java.util.Collection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
public class FileUploadFieldBuilder extends FieldBuilder<String> {
private final Collection<String> supportedFiles;
FileUploadFieldBuilder(
final String name,
final LocTextKey label,
final String value,
final Collection<String> supportedFiles) {
super(name, label, value);
this.supportedFiles = supportedFiles;
}
@Override
void build(final FormBuilder builder) {
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final FileUploadSelection fileUpload = builder.widgetFactory.fileUploadSelection(
fieldGrid,
builder.readonly || this.readonly,
this.supportedFiles);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
fileUpload.setLayoutData(gridData);
fileUpload.setFileName(this.value);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, fileUpload, errorLabel);
builder.setFieldVisible(this.visible, this.name);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,294 +1,306 @@
/*
* Copyright (c) 2018 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.form;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TabItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
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.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
public class FormBuilder {
public static final int FORM_ROW_HEIGHT = 25;
private static final Logger log = LoggerFactory.getLogger(FormBuilder.class);
final I18nSupport i18nSupport;
final PageService pageService;
final WidgetFactory widgetFactory;
public final PageContext pageContext;
public final Composite formParent;
public final Form form;
boolean readonly = false;
private int defaultSpanLabel = 2;
private int defaultSpanInput = 5;
private int defaultSpanEmptyCell = 1;
private boolean emptyCellSeparation = true;
public FormBuilder(
final PageService pageService,
final PageContext pageContext,
final int rows) {
this.i18nSupport = pageService.getI18nSupport();
this.pageService = pageService;
this.widgetFactory = pageService.getWidgetFactory();
this.pageContext = pageContext;
this.form = new Form(pageService.getJSONMapper());
this.formParent = this.widgetFactory.formGrid(
pageContext.getParent(),
rows);
}
public FormBuilder readonly(final boolean readonly) {
this.readonly = readonly;
return this;
}
public FormBuilder setVisible(final boolean visible, final String group) {
this.form.setVisible(visible, group);
return this;
}
public void setFieldVisible(final boolean visible, final String fieldName) {
this.form.setFieldVisible(visible, fieldName);
}
public FormBuilder setControl(final TabItem instTab) {
instTab.setControl(this.formParent);
return this;
}
public FormBuilder withDefaultSpanLabel(final int span) {
this.defaultSpanLabel = span;
return this;
}
public FormBuilder withDefaultSpanInput(final int span) {
this.defaultSpanInput = span;
return this;
}
public FormBuilder withDefaultSpanEmptyCell(final int span) {
this.defaultSpanEmptyCell = span;
return this;
}
public FormBuilder withEmptyCellSeparation(final boolean separation) {
this.emptyCellSeparation = separation;
return this;
}
public FormBuilder addEmptyCellIf(final BooleanSupplier condition) {
if (condition != null && condition.getAsBoolean()) {
return addEmptyCell();
}
return this;
}
public FormBuilder addEmptyCell() {
return addEmptyCell(1);
}
public FormBuilder addEmptyCell(final int span) {
empty(this.formParent, span, 1);
return this;
}
public FormBuilder putStaticValueIf(final BooleanSupplier condition, final String name, final String value) {
if (condition != null && condition.getAsBoolean()) {
return putStaticValue(name, value);
}
return this;
}
public FormBuilder putStaticValue(final String name, final String value) {
try {
this.form.putStatic(name, value);
} catch (final Exception e) {
log.error("Failed to put static field value to json object: ", e);
}
return this;
}
public FormBuilder addFieldIf(
final BooleanSupplier condition,
final Supplier<FieldBuilder<?>> templateSupplier) {
if (condition.getAsBoolean()) {
return addField(templateSupplier.get());
}
return this;
}
public FormBuilder addField(final FieldBuilder<?> template) {
template.spanLabel = (template.spanLabel < 0) ? this.defaultSpanLabel : template.spanLabel;
template.spanInput = (template.spanInput < 0) ? this.defaultSpanInput : template.spanInput;
template.spanEmptyCell = (template.spanEmptyCell < 0) ? this.defaultSpanEmptyCell : template.spanEmptyCell;
template.autoEmptyCellSeparation = (template.autoEmptyCellSeparation != null)
? template.autoEmptyCellSeparation
: this.emptyCellSeparation;
if (template.autoEmptyCellSeparation && this.form.hasFields()) {
addEmptyCell(template.spanEmptyCell);
}
template.build(this);
if (StringUtils.isNotBlank(template.group)) {
this.form.addToGroup(template.group, template.name);
}
return this;
}
public <T extends Entity> FormHandle<T> build() {
return buildFor(null);
}
public <T extends Entity> FormHandle<T> buildFor(final RestCall<T> post) {
return new FormHandle<>(
this.pageService,
this.pageContext,
this.form,
post);
}
private void empty(final Composite parent, final int hspan, final int vspan) {
final Label empty = new Label(parent, SWT.LEFT);
final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, false, false, hspan, vspan);
gridData.minimumWidth = 0;
gridData.widthHint = 0;
empty.setLayoutData(gridData);
}
public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label) {
return new CheckboxFieldBuilder(name, label, null);
}
public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label, final String value) {
return new CheckboxFieldBuilder(name, label, value);
}
public static TextFieldBuilder text(final String name) {
return new TextFieldBuilder(name, null, null);
}
public static TextFieldBuilder text(final String name, final LocTextKey label) {
return new TextFieldBuilder(name, label, null);
}
public static TextFieldBuilder text(final String name, final LocTextKey label, final String value) {
return new TextFieldBuilder(name, label, value);
}
public static TextFieldBuilder text(final String name, final LocTextKey label,
final Supplier<String> valueSupplier) {
return new TextFieldBuilder(name, label, valueSupplier.get());
}
public static SelectionFieldBuilder singleSelection(
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(Selection.Type.SINGLE, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder multiSelection(
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(Selection.Type.MULTI, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder multiCheckboxSelection(
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(Selection.Type.MULTI_CHECKBOX, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder multiComboSelection(
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(Selection.Type.MULTI_COMBO, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder colorSelection(
final String name,
final LocTextKey label,
final String value) {
return new SelectionFieldBuilder(Selection.Type.COLOR, name, label, value, null);
}
public static ThresholdListBuilder thresholdList(
final String name,
final LocTextKey label,
final Indicator indicator) {
return new ThresholdListBuilder(
name,
label,
indicator.thresholds);
}
public static ImageUploadFieldBuilder imageUpload(final String name, final LocTextKey label, final String value) {
return new ImageUploadFieldBuilder(name, label, value);
}
public static FileUploadFieldBuilder fileUpload(
final String name,
final LocTextKey label,
final String value,
final String... supportedFiles) {
return new FileUploadFieldBuilder(
name,
label,
value,
(supportedFiles != null) ? Arrays.asList(supportedFiles) : Collections.emptyList());
}
}
/*
* Copyright (c) 2018 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.form;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Supplier;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TabItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
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.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
public class FormBuilder {
public static final int FORM_ROW_HEIGHT = 25;
private static final Logger log = LoggerFactory.getLogger(FormBuilder.class);
final Cryptor cryptor;
final I18nSupport i18nSupport;
final PageService pageService;
final WidgetFactory widgetFactory;
public final PageContext pageContext;
public final Composite formParent;
public final Form form;
boolean readonly = false;
private int defaultSpanLabel = 2;
private int defaultSpanInput = 5;
private int defaultSpanEmptyCell = 1;
private boolean emptyCellSeparation = true;
public FormBuilder(
final PageService pageService,
final PageContext pageContext,
final Cryptor cryptor,
final int rows) {
this.cryptor = cryptor;
this.i18nSupport = pageService.getI18nSupport();
this.pageService = pageService;
this.widgetFactory = pageService.getWidgetFactory();
this.pageContext = pageContext;
this.form = new Form(pageService.getJSONMapper(), cryptor);
this.formParent = this.widgetFactory.formGrid(
pageContext.getParent(),
rows);
}
public FormBuilder readonly(final boolean readonly) {
this.readonly = readonly;
return this;
}
public FormBuilder setVisible(final boolean visible, final String group) {
this.form.setVisible(visible, group);
return this;
}
public void setFieldVisible(final boolean visible, final String fieldName) {
this.form.setFieldVisible(visible, fieldName);
}
public FormBuilder setControl(final TabItem instTab) {
instTab.setControl(this.formParent);
return this;
}
public FormBuilder withDefaultSpanLabel(final int span) {
this.defaultSpanLabel = span;
return this;
}
public FormBuilder withDefaultSpanInput(final int span) {
this.defaultSpanInput = span;
return this;
}
public FormBuilder withDefaultSpanEmptyCell(final int span) {
this.defaultSpanEmptyCell = span;
return this;
}
public FormBuilder withEmptyCellSeparation(final boolean separation) {
this.emptyCellSeparation = separation;
return this;
}
public FormBuilder addEmptyCellIf(final BooleanSupplier condition) {
if (condition != null && condition.getAsBoolean()) {
return addEmptyCell();
}
return this;
}
public FormBuilder addEmptyCell() {
return addEmptyCell(1);
}
public FormBuilder addEmptyCell(final int span) {
empty(this.formParent, span, 1);
return this;
}
public FormBuilder putStaticValueIf(final BooleanSupplier condition, final String name, final String value) {
if (condition != null && condition.getAsBoolean()) {
return putStaticValue(name, value);
}
return this;
}
public FormBuilder putStaticValue(final String name, final String value) {
try {
this.form.putStatic(name, value);
} catch (final Exception e) {
log.error("Failed to put static field value to json object: ", e);
}
return this;
}
public FormBuilder addFieldIf(
final BooleanSupplier condition,
final Supplier<FieldBuilder<?>> templateSupplier) {
if (condition.getAsBoolean()) {
return addField(templateSupplier.get());
}
return this;
}
public FormBuilder addField(final FieldBuilder<?> template) {
template.spanLabel = (template.spanLabel < 0) ? this.defaultSpanLabel : template.spanLabel;
template.spanInput = (template.spanInput < 0) ? this.defaultSpanInput : template.spanInput;
template.spanEmptyCell = (template.spanEmptyCell < 0) ? this.defaultSpanEmptyCell : template.spanEmptyCell;
template.autoEmptyCellSeparation = (template.autoEmptyCellSeparation != null)
? template.autoEmptyCellSeparation
: this.emptyCellSeparation;
if (template.autoEmptyCellSeparation && this.form.hasFields()) {
addEmptyCell(template.spanEmptyCell);
}
template.build(this);
if (StringUtils.isNotBlank(template.group)) {
this.form.addToGroup(template.group, template.name);
}
return this;
}
public <T extends Entity> FormHandle<T> build() {
return buildFor(null);
}
public <T extends Entity> FormHandle<T> buildFor(final RestCall<T> post) {
return new FormHandle<>(
this.pageService,
this.pageContext,
this.form,
post);
}
private void empty(final Composite parent, final int hspan, final int vspan) {
final Label empty = new Label(parent, SWT.LEFT);
final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, false, false, hspan, vspan);
gridData.minimumWidth = 0;
gridData.widthHint = 0;
empty.setLayoutData(gridData);
}
public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label) {
return new CheckboxFieldBuilder(name, label, null);
}
public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label, final String value) {
return new CheckboxFieldBuilder(name, label, value);
}
public static TextFieldBuilder text(final String name) {
return new TextFieldBuilder(name, null, null);
}
public static TextFieldBuilder text(final String name, final LocTextKey label) {
return new TextFieldBuilder(name, label, null);
}
public static TextFieldBuilder text(final String name, final LocTextKey label, final String value) {
return new TextFieldBuilder(name, label, value);
}
public static TextFieldBuilder text(
final String name,
final LocTextKey label,
final Supplier<String> valueSupplier) {
return new TextFieldBuilder(name, label, valueSupplier.get());
}
public static PasswordFieldBuilder password(final String name, final LocTextKey label, final CharSequence value) {
return new PasswordFieldBuilder(name, label, value);
}
public static SelectionFieldBuilder singleSelection(
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(Selection.Type.SINGLE, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder multiSelection(
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(Selection.Type.MULTI, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder multiCheckboxSelection(
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(Selection.Type.MULTI_CHECKBOX, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder multiComboSelection(
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new SelectionFieldBuilder(Selection.Type.MULTI_COMBO, name, label, value, itemsSupplier);
}
public static SelectionFieldBuilder colorSelection(
final String name,
final LocTextKey label,
final String value) {
return new SelectionFieldBuilder(Selection.Type.COLOR, name, label, value, null);
}
public static ThresholdListBuilder thresholdList(
final String name,
final LocTextKey label,
final Indicator indicator) {
return new ThresholdListBuilder(
name,
label,
indicator.thresholds);
}
public static ImageUploadFieldBuilder imageUpload(final String name, final LocTextKey label, final String value) {
return new ImageUploadFieldBuilder(name, label, value);
}
public static FileUploadFieldBuilder fileUpload(
final String name,
final LocTextKey label,
final String value,
final String... supportedFiles) {
return new FileUploadFieldBuilder(
name,
label,
value,
(supportedFiles != null) ? Arrays.asList(supportedFiles) : Collections.emptyList());
}
}

View file

@ -1,57 +1,58 @@
/*
* 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.form;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
public final class ImageUploadFieldBuilder extends FieldBuilder<String> {
private int maxWidth = 100;
private int maxHeight = 100;
ImageUploadFieldBuilder(final String name, final LocTextKey label, final String value) {
super(name, label, value);
}
public ImageUploadFieldBuilder withMaxWidth(final int width) {
this.maxWidth = width;
return this;
}
public ImageUploadFieldBuilder withMaxHeight(final int height) {
this.maxHeight = height;
return this;
}
@Override
void build(final FormBuilder builder) {
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final ImageUploadSelection imageUpload = builder.widgetFactory.imageUploadLocalized(
fieldGrid,
new LocTextKey("sebserver.overall.upload"),
builder.readonly || this.readonly,
this.maxWidth,
this.maxHeight);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
imageUpload.setLayoutData(gridData);
imageUpload.setImageBase64(this.value);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, imageUpload, errorLabel);
builder.setFieldVisible(this.visible, this.name);
}
/*
* 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.form;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
public final class ImageUploadFieldBuilder extends FieldBuilder<String> {
private int maxWidth = 100;
private int maxHeight = 100;
ImageUploadFieldBuilder(final String name, final LocTextKey label, final String value) {
super(name, label, value);
}
public ImageUploadFieldBuilder withMaxWidth(final int width) {
this.maxWidth = width;
return this;
}
public ImageUploadFieldBuilder withMaxHeight(final int height) {
this.maxHeight = height;
return this;
}
@Override
void build(final FormBuilder builder) {
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final ImageUploadSelection imageUpload = builder.widgetFactory.imageUploadLocalized(
fieldGrid,
new LocTextKey("sebserver.overall.upload"),
builder.readonly || this.readonly,
this.maxWidth,
this.maxHeight);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
imageUpload.setLayoutData(gridData);
imageUpload.setImageBase64(this.value);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, imageUpload, errorLabel);
builder.setFieldVisible(this.visible, this.name);
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020 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.form;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.PasswordInput;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
public class PasswordFieldBuilder extends FieldBuilder<CharSequence> {
PasswordFieldBuilder(final String name, final LocTextKey label, final CharSequence value) {
super(name, label, value);
}
@Override
void build(FormBuilder builder) {
final boolean readonly = builder.readonly || this.readonly;
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final PasswordInput input = new PasswordInput(fieldGrid, builder.widgetFactory);
input.setEditable(!readonly);
input.setValue((StringUtils.isNotBlank(this.value))
? builder.cryptor.decrypt(this.value)
: this.value);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, input, errorLabel);
}
}

View file

@ -1,150 +1,150 @@
/*
* 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.form;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
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.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
public final class SelectionFieldBuilder extends FieldBuilder<String> {
final Supplier<List<Tuple<String>>> itemsSupplier;
Consumer<Form> selectionListener = null;
final Selection.Type type;
SelectionFieldBuilder(
final Selection.Type type,
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
super(name, label, value);
this.type = type;
this.itemsSupplier = itemsSupplier;
}
public SelectionFieldBuilder withSelectionListener(final Consumer<Form> selectionListener) {
this.selectionListener = selectionListener;
return this;
}
@Override
void build(final FormBuilder builder) {
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
if (builder.readonly || this.readonly) {
buildReadOnly(builder, titleLabel);
} else {
buildInput(builder, titleLabel);
}
}
private void buildInput(final FormBuilder builder, final Label titleLabel) {
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final String actionKey = (this.label != null) ? this.label.name + ".action" : null;
final Selection selection = builder.widgetFactory.selectionLocalized(
this.type,
fieldGrid,
this.itemsSupplier,
null,
null,
actionKey);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
((Control) selection).setLayoutData(gridData);
selection.select(this.value);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, selection, errorLabel);
if (this.selectionListener != null) {
((Control) selection).addListener(SWT.Selection, e -> {
this.selectionListener.accept(builder.form);
});
}
builder.setFieldVisible(this.visible, this.name);
}
/* Build the read-only representation of the selection field */
private void buildReadOnly(final FormBuilder builder, final Label titleLabel) {
if (this.type == Type.MULTI || this.type == Type.MULTI_COMBO || this.type == Type.MULTI_CHECKBOX) {
final Composite composite = new Composite(builder.formParent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(1, true);
//gridLayout.verticalSpacing = 5;
gridLayout.marginBottom = 5;
gridLayout.horizontalSpacing = 0;
gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
composite.setLayout(gridLayout);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1));
if (StringUtils.isBlank(this.value)) {
final Label label = new Label(composite, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
label.setLayoutData(gridData);
label.setText(this.value);
} else {
final Collection<String> keys = Arrays.asList(StringUtils.split(this.value, Constants.LIST_SEPARATOR));
this.itemsSupplier.get()
.stream()
.filter(tuple -> keys.contains(tuple._1))
.map(tuple -> tuple._1)
.forEach(v -> buildReadonlyLabel(composite, v, 1));
}
} else {
builder.form.putReadonlyField(
this.name,
titleLabel,
buildReadonlyLabel(builder.formParent, this.value, this.spanInput));
builder.setFieldVisible(this.visible, this.name);
}
}
private Text buildReadonlyLabel(final Composite composite, final String valueKey, final int hspan) {
final Text label = new Text(composite, SWT.READ_ONLY);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true, hspan, 1);
gridData.verticalIndent = 0;
gridData.horizontalIndent = 0;
label.setLayoutData(gridData);
final Supplier<String> valueSupplier = () -> this.itemsSupplier.get().stream()
.filter(tuple -> valueKey.equals(tuple._1))
.findFirst()
.map(tuple -> tuple._2)
.orElse(Constants.EMPTY_NOTE);
final Consumer<Text> updateFunction = t -> t.setText(valueSupplier.get());
label.setText(valueSupplier.get());
label.setData(PolyglotPageService.POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
return label;
}
/*
* 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.form;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
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.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
public final class SelectionFieldBuilder extends FieldBuilder<String> {
final Supplier<List<Tuple<String>>> itemsSupplier;
Consumer<Form> selectionListener = null;
final Selection.Type type;
SelectionFieldBuilder(
final Selection.Type type,
final String name,
final LocTextKey label,
final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
super(name, label, value);
this.type = type;
this.itemsSupplier = itemsSupplier;
}
public SelectionFieldBuilder withSelectionListener(final Consumer<Form> selectionListener) {
this.selectionListener = selectionListener;
return this;
}
@Override
void build(final FormBuilder builder) {
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
if (builder.readonly || this.readonly) {
buildReadOnly(builder, titleLabel);
} else {
buildInput(builder, titleLabel);
}
}
private void buildInput(final FormBuilder builder, final Control titleLabel) {
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final String actionKey = (this.label != null) ? this.label.name + ".action" : null;
final Selection selection = builder.widgetFactory.selectionLocalized(
this.type,
fieldGrid,
this.itemsSupplier,
null,
null,
actionKey);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
((Control) selection).setLayoutData(gridData);
selection.select(this.value);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, selection, errorLabel);
if (this.selectionListener != null) {
((Control) selection).addListener(SWT.Selection, e -> {
this.selectionListener.accept(builder.form);
});
}
builder.setFieldVisible(this.visible, this.name);
}
/* Build the read-only representation of the selection field */
private void buildReadOnly(final FormBuilder builder, final Control titleLabel) {
if (this.type == Type.MULTI || this.type == Type.MULTI_COMBO || this.type == Type.MULTI_CHECKBOX) {
final Composite composite = new Composite(builder.formParent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(1, true);
//gridLayout.verticalSpacing = 5;
gridLayout.marginBottom = 5;
gridLayout.horizontalSpacing = 0;
gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
composite.setLayout(gridLayout);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1));
if (StringUtils.isBlank(this.value)) {
final Label label = new Label(composite, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
label.setLayoutData(gridData);
label.setText(this.value);
} else {
final Collection<String> keys = Arrays.asList(StringUtils.split(this.value, Constants.LIST_SEPARATOR));
this.itemsSupplier.get()
.stream()
.filter(tuple -> keys.contains(tuple._1))
.map(tuple -> tuple._1)
.forEach(v -> buildReadonlyLabel(composite, v, 1));
}
} else {
builder.form.putReadonlyField(
this.name,
titleLabel,
buildReadonlyLabel(builder.formParent, this.value, this.spanInput));
builder.setFieldVisible(this.visible, this.name);
}
}
private Text buildReadonlyLabel(final Composite composite, final String valueKey, final int hspan) {
final Text label = new Text(composite, SWT.READ_ONLY);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true, hspan, 1);
gridData.verticalIndent = 0;
gridData.horizontalIndent = 0;
label.setLayoutData(gridData);
final Supplier<String> valueSupplier = () -> this.itemsSupplier.get().stream()
.filter(tuple -> valueKey.equals(tuple._1))
.findFirst()
.map(tuple -> tuple._2)
.orElse(Constants.EMPTY_NOTE);
final Consumer<Text> updateFunction = t -> t.setText(valueSupplier.get());
label.setText(valueSupplier.get());
label.setData(PolyglotPageService.POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
return label;
}
}

View file

@ -18,6 +18,7 @@ import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
@ -88,7 +89,7 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
@Override
void build(final FormBuilder builder) {
final boolean readonly = builder.readonly || this.readonly;
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
if (readonly && this.isHTML) {
@ -117,7 +118,7 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
gridData.minimumHeight = this.areaMinHeight;
} else if (this.isColorBox) {
gridData.minimumHeight = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
textInput.setData(RWT.CUSTOM_VARIANT, "colorbox");
textInput.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.COLOR_BOX.key);
}
textInput.setLayoutData(gridData);
if (StringUtils.isNoneBlank(this.value)) {

View file

@ -1,89 +1,90 @@
/*
* 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.form;
import java.util.Collection;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.ThresholdList;
public class ThresholdListBuilder extends FieldBuilder<Collection<Threshold>> {
private final Collection<Threshold> thresholds;
protected ThresholdListBuilder(
final String name,
final LocTextKey label,
final Collection<Threshold> thresholds) {
super(name, label, thresholds);
this.thresholds = thresholds;
}
@Override
void build(final FormBuilder builder) {
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
if (builder.readonly || this.readonly) {
// No read-only view needed for this so far?
return;
} else {
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final ThresholdList thresholdList = builder.widgetFactory.thresholdList(
fieldGrid,
fieldGrid.getParent().getParent(),
this.thresholds,
() -> {
try {
final String fieldValue = builder.form.getFieldValue(Domain.INDICATOR.ATTR_TYPE);
return IndicatorType.valueOf(fieldValue);
} catch (final Exception e) {
return null;
}
});
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
thresholdList.setLayoutData(gridData);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, thresholdList, errorLabel);
builder.setFieldVisible(this.visible, this.name);
}
}
public static final String thresholdsToFormURLEncodedStringValue(final Collection<Threshold> thresholds) {
if (thresholds == null || thresholds.isEmpty()) {
return null;
}
// thresholds={value}|{color},thresholds={value}|{color}...
return StringUtils.join(thresholds.stream()
.map(t -> Domain.THRESHOLD.REFERENCE_NAME
+ Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR
+ String.valueOf(t.getValue())
+ Constants.EMBEDDED_LIST_SEPARATOR
+ t.getColor())
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR);
}
}
/*
* 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.form;
import java.util.Collection;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.ThresholdList;
public class ThresholdListBuilder extends FieldBuilder<Collection<Threshold>> {
private final Collection<Threshold> thresholds;
protected ThresholdListBuilder(
final String name,
final LocTextKey label,
final Collection<Threshold> thresholds) {
super(name, label, thresholds);
this.thresholds = thresholds;
}
@Override
void build(final FormBuilder builder) {
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
if (builder.readonly || this.readonly) {
// No read-only view needed for this so far?
return;
} else {
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final ThresholdList thresholdList = builder.widgetFactory.thresholdList(
fieldGrid,
fieldGrid.getParent().getParent(),
this.thresholds,
() -> {
try {
final String fieldValue = builder.form.getFieldValue(Domain.INDICATOR.ATTR_TYPE);
return IndicatorType.valueOf(fieldValue);
} catch (final Exception e) {
return null;
}
});
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
thresholdList.setLayoutData(gridData);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, thresholdList, errorLabel);
builder.setFieldVisible(this.visible, this.name);
}
}
public static final String thresholdsToFormURLEncodedStringValue(final Collection<Threshold> thresholds) {
if (thresholds == null || thresholds.isEmpty()) {
return null;
}
// thresholds={value}|{color},thresholds={value}|{color}...
return StringUtils.join(thresholds.stream()
.map(t -> Domain.THRESHOLD.REFERENCE_NAME
+ Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR
+ String.valueOf(t.getValue())
+ Constants.EMBEDDED_LIST_SEPARATOR
+ t.getColor())
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR);
}
}

View file

@ -20,6 +20,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Tuple3;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.apache.commons.lang3.StringUtils;
@ -107,6 +108,7 @@ public class ResourceService {
public static final String CONFIG_ATTRIBUTE_TYPE_PREFIX = "sebserver.configtemplate.attr.type.";
public static final String SEB_RESTRICTION_WHITE_LIST_PREFIX = "sebserver.exam.form.sebrestriction.whiteListPaths.";
public static final String SEB_RESTRICTION_PERMISSIONS_PREFIX = "sebserver.exam.form.sebrestriction.permissions.";
public static final String SEB_CLIENT_CONFIG_PURPOSE_PREFIX = "sebserver.clientconfig.config.purpose.";
public static final EnumSet<AttributeType> ATTRIBUTE_TYPES_NOT_DISPLAYED = EnumSet.of(
AttributeType.LABEL,
@ -656,4 +658,16 @@ public class ResourceService {
.call();
}
public List<Tuple<String>> sebClientConfigPurposeResources() {
return Arrays.stream(SebClientConfig.ConfigPurpose.values())
.map(type -> new Tuple3<>(
type.name(),
this.i18nSupport.getText(SEB_CLIENT_CONFIG_PURPOSE_PREFIX + type.name()),
Utils.formatLineBreaks(this.i18nSupport.getText(
SEB_CLIENT_CONFIG_PURPOSE_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
StringUtils.EMPTY))))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}
}

View file

@ -1,178 +1,181 @@
/*
* 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.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Hex;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Orientation;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.form.FieldBuilder;
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;
@Lazy
@Component
@GuiProfile
public class PassworFieldBuilder implements InputFieldBuilder {
private static final Logger log = LoggerFactory.getLogger(PassworFieldBuilder.class);
private static final LocTextKey VAL_CONFIRM_PWD_TEXT_KEY =
new LocTextKey("sebserver.examconfig.props.validation.password.confirm");
@Override
public boolean builderFor(
final ConfigurationAttribute attribute,
final Orientation orientation) {
if (attribute == null) {
return false;
}
return AttributeType.PASSWORD_FIELD == attribute.type;
}
@Override
public InputField createInputField(
final Composite parent,
final ConfigurationAttribute attribute,
final ViewContext viewContext) {
final Orientation orientation = viewContext
.getOrientation(attribute.id);
final Composite innerGrid = InputFieldBuilder
.createInnerGrid(parent, attribute, orientation);
final Text passwordInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD);
final GridData passwordInputLD = new GridData(SWT.FILL, SWT.FILL, true, false);
passwordInput.setLayoutData(passwordInputLD);
final Text confirmInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
gridData.verticalIndent = 14;
confirmInput.setLayoutData(gridData);
final PasswordInputField passwordInputField = new PasswordInputField(
attribute,
orientation,
passwordInput,
confirmInput,
FieldBuilder.createErrorLabel(innerGrid));
if (viewContext.readonly) {
passwordInput.setEditable(false);
passwordInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key);
passwordInputLD.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
confirmInput.setEditable(false);
confirmInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key);
gridData.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
} else {
final Listener valueChangeEventListener = event -> {
passwordInputField.clearError();
final String pwd = passwordInput.getText();
final String confirm = confirmInput.getText();
if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) {
return;
}
if (!pwd.equals(confirm)) {
passwordInputField.showError(viewContext
.getI18nSupport()
.getText(VAL_CONFIRM_PWD_TEXT_KEY));
return;
}
final String hashedPWD = passwordInputField.getValue();
if (hashedPWD != null) {
viewContext.getValueChangeListener().valueChanged(
viewContext,
attribute,
hashedPWD,
passwordInputField.listIndex);
}
};
passwordInput.addListener(SWT.FocusOut, valueChangeEventListener);
passwordInput.addListener(SWT.Traverse, valueChangeEventListener);
confirmInput.addListener(SWT.FocusOut, valueChangeEventListener);
confirmInput.addListener(SWT.Traverse, valueChangeEventListener);
}
return passwordInputField;
}
static final class PasswordInputField extends AbstractInputField<Text> {
private final Text confirm;
PasswordInputField(
final ConfigurationAttribute attribute,
final Orientation orientation,
final Text control,
final Text confirm,
final Label errorLabel) {
super(attribute, orientation, control, errorLabel);
this.confirm = confirm;
}
@Override
protected void setValueToControl(final String value) {
// TODO clarify setting some "fake" input when a password is set (like in config tool)
if (value != null) {
this.control.setText(value);
this.confirm.setText(value);
}
}
@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);
}
}
}
/*
* 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 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;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import ch.ethz.seb.sebserver.gui.form.FieldBuilder;
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.PasswordInput;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
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.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Lazy
@Component
@GuiProfile
public class PasswordFieldBuilder implements InputFieldBuilder {
private static final Logger log = LoggerFactory.getLogger(PasswordFieldBuilder.class);
private static final LocTextKey VAL_CONFIRM_PWD_TEXT_KEY =
new LocTextKey("sebserver.examconfig.props.validation.password.confirm");
private final Cryptor cryptor;
private final WidgetFactory widgetFactory;
public PasswordFieldBuilder(
final WidgetFactory widgetFactory,
final Cryptor cryptor) {
this.cryptor = cryptor;
this.widgetFactory = widgetFactory;
}
@Override
public boolean builderFor(
final ConfigurationAttribute attribute,
final Orientation orientation) {
if (attribute == null) {
return false;
}
return AttributeType.PASSWORD_FIELD == attribute.type;
}
@Override
public InputField createInputField(
final Composite parent,
final ConfigurationAttribute attribute,
final ViewContext viewContext) {
final Orientation orientation = viewContext
.getOrientation(attribute.id);
final Composite innerGrid = InputFieldBuilder
.createInnerGrid(parent, attribute, orientation);
final PasswordInput passwordInput = new PasswordInput(innerGrid, widgetFactory);
final GridData passwordInputLD = new GridData(SWT.FILL, SWT.FILL, true, true);
passwordInput.setLayoutData(passwordInputLD);
final PasswordInput confirmInput = new PasswordInput(innerGrid, widgetFactory);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.verticalIndent = 14;
confirmInput.setLayoutData(gridData);
innerGrid.setData("isPlainText", false);
final PasswordInputField passwordInputField = new PasswordInputField(
attribute,
orientation,
passwordInput,
confirmInput,
FieldBuilder.createErrorLabel(innerGrid),
cryptor);
if (viewContext.readonly) {
passwordInput.setEditable(false);
passwordInputLD.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
confirmInput.setEditable(false);
confirmInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key);
gridData.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
} else {
final Listener valueChangeEventListener = event -> {
passwordInputField.clearError();
final CharSequence pwd = passwordInput.getValue();
final CharSequence confirm = confirmInput.getValue();
if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) {
return;
}
if (!pwd.equals(confirm)) {
passwordInputField.showError(viewContext
.getI18nSupport()
.getText(VAL_CONFIRM_PWD_TEXT_KEY));
return;
}
final String hashedPWD = passwordInputField.getValue();
if (hashedPWD != null) {
viewContext.getValueChangeListener().valueChanged(
viewContext,
attribute,
hashedPWD,
passwordInputField.listIndex);
}
};
passwordInput.addListener(SWT.FocusOut, valueChangeEventListener);
passwordInput.addListener(SWT.Traverse, valueChangeEventListener);
confirmInput.addListener(SWT.FocusOut, valueChangeEventListener);
confirmInput.addListener(SWT.Traverse, valueChangeEventListener);
}
return passwordInputField;
}
static final class PasswordInputField extends AbstractInputField<PasswordInput> {
private final PasswordInput confirm;
private final Cryptor cryptor;
PasswordInputField(
final ConfigurationAttribute attribute,
final Orientation orientation,
final PasswordInput control,
final PasswordInput confirm,
final Label errorLabel,
final Cryptor cryptor) {
super(attribute, orientation, control, errorLabel);
this.confirm = confirm;
this.cryptor = cryptor;
}
@Override
protected void setValueToControl(final String value) {
if (StringUtils.isNotBlank(value)) {
CharSequence pwd = cryptor.decrypt(value);
this.control.setValue(pwd.toString());
this.confirm.setValue(pwd.toString());
} else {
this.control.setValue(StringUtils.EMPTY);
this.confirm.setValue(StringUtils.EMPTY);
}
}
@Override
public String getValue() {
final CharSequence pwd = this.control.getValue();
if (StringUtils.isNotBlank(pwd)) {
return cryptor.encrypt(pwd).toString();
}
return StringUtils.EMPTY;
}
}
}

View file

@ -20,6 +20,7 @@ import java.util.function.Supplier;
import javax.servlet.http.HttpSession;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.eclipse.rap.rwt.RWT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -78,6 +79,7 @@ public class PageServiceImpl implements PageService {
private static final String ATTR_PAGE_STATE = "PAGE_STATE";
private static final ListenerComparator LIST_COMPARATOR = new ListenerComparator();
private final Cryptor cryptor;
private final JSONMapper jsonMapper;
private final WidgetFactory widgetFactory;
private final PolyglotPageService polyglotPageService;
@ -85,12 +87,14 @@ public class PageServiceImpl implements PageService {
private final CurrentUser currentUser;
public PageServiceImpl(
final Cryptor cryptor,
final JSONMapper jsonMapper,
final WidgetFactory widgetFactory,
final PolyglotPageService polyglotPageService,
final ResourceService resourceService,
final CurrentUser currentUser) {
this.cryptor = cryptor;
this.jsonMapper = jsonMapper;
this.widgetFactory = widgetFactory;
this.polyglotPageService = polyglotPageService;
@ -337,7 +341,7 @@ public class PageServiceImpl implements PageService {
@Override
public FormBuilder formBuilder(final PageContext pageContext, final int rows) {
return new FormBuilder(this, pageContext, rows);
return new FormBuilder(this, pageContext, cryptor, rows);
}
@Override

View file

@ -0,0 +1,132 @@
/*
* Copyright (c) 2020 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.widget;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
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.Text;
public class PasswordInput extends Composite {
public static final LocTextKey PLAIN_TEXT_VIEW_TOOLTIP_KEY =
new LocTextKey("sebserver.overall.action.showPassword.tooltip");
private final WidgetFactory widgetFactory;
private final Composite inputAnchor;
private final Label visibilityButton;
private Text passwordInput = null;
private boolean isPlainText = true;
private boolean isEditable = true;
public PasswordInput(final Composite parent, final WidgetFactory widgetFactory) {
super(parent, SWT.NONE);
this.widgetFactory = widgetFactory;
GridLayout gridLayout = new GridLayout(2, false);
gridLayout.horizontalSpacing = 0;
gridLayout.verticalSpacing = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
this.setLayout(gridLayout);
this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
inputAnchor = new Composite(this, SWT.NONE);
gridLayout = new GridLayout(1, false);
gridLayout.horizontalSpacing = 0;
gridLayout.verticalSpacing = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
inputAnchor.setLayout(gridLayout);
inputAnchor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
visibilityButton = widgetFactory.imageButton(
WidgetFactory.ImageIcon.VISIBILITY,
this,
PLAIN_TEXT_VIEW_TOOLTIP_KEY,
event -> changePasswordView());
GridData ld = new GridData(SWT.RIGHT, SWT.BOTTOM, false, false);
ld.heightHint = 22;
ld.horizontalIndent = 5;
visibilityButton.setLayoutData(ld);
changePasswordView();
}
private void changePasswordView() {
final String value = (this.passwordInput != null) ? this.passwordInput.getText() : null;
final boolean buildPassword = this.isPlainText;
if (this.passwordInput != null) {
PageService.clearComposite(this.inputAnchor);
}
Text passwordInput = new Text(
inputAnchor,
SWT.LEFT | SWT.BORDER | (buildPassword ? SWT.PASSWORD : SWT.NONE));
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
passwordInput.setLayoutData(gridData);
passwordInput.setText(value != null ? value : StringUtils.EMPTY);
if (!buildPassword) {
passwordInput.setEditable(false);
} else {
passwordInput.setEditable(isEditable);
passwordInput.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.CONFIG_INPUT_READONLY.key);
if (!isEditable) {
gridData.heightHint = 21;
}
}
if (buildPassword) {
passwordInput.addListener(SWT.FocusOut, event -> super.notifyListeners(SWT.FocusOut, event));
passwordInput.addListener(SWT.Traverse, event -> super.notifyListeners(SWT.Traverse, event));
this.visibilityButton.setImage(WidgetFactory.ImageIcon.VISIBILITY.getImage(getDisplay()));
} else {
passwordInput.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.PLAIN_PWD.key);
this.visibilityButton.setImage(WidgetFactory.ImageIcon.VISIBILITY_OFF.getImage(getDisplay()));
}
this.passwordInput = passwordInput;
this.isPlainText = !this.isPlainText;
super.layout(true, true);
}
public void setValue(CharSequence value) {
if (passwordInput != null) {
passwordInput.setText(value != null ? value.toString() : StringUtils.EMPTY);
}
}
public CharSequence getValue() {
if (passwordInput != null) {
return passwordInput.getText();
}
return null;
}
public void setEditable(boolean editable) {
this.isEditable = editable;
this.isPlainText = !this.isPlainText;
this.changePasswordView();
}
}

View file

@ -1,203 +1,143 @@
/*
* 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.client;
import java.io.UnsupportedEncodingException;
import java.nio.CharBuffer;
import java.security.SecureRandom;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.keygen.KeyGenerators;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
@Lazy
@Service
@WebServiceProfile
public class ClientCredentialServiceImpl implements ClientCredentialService {
private static final Logger log = LoggerFactory.getLogger(ClientCredentialServiceImpl.class);
static final String SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY = "sebserver.webservice.internalSecret";
private final Environment environment;
protected ClientCredentialServiceImpl(final Environment environment) {
this.environment = environment;
}
@Override
public Result<ClientCredentials> generatedClientCredentials() {
return Result.tryCatch(() -> {
try {
return encryptClientCredentials(
generateClientId(),
generateClientSecret());
} catch (final UnsupportedEncodingException e) {
log.error("Error while trying to generate client credentials: ", e);
throw new RuntimeException("cause: ", e);
}
});
}
@Override
public ClientCredentials encryptClientCredentials(
final CharSequence clientIdPlaintext,
final CharSequence secretPlaintext,
final CharSequence accessTokenPlaintext) {
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return new ClientCredentials(
clientIdPlaintext,
(StringUtils.isNoneBlank(secretPlaintext))
? encrypt(secretPlaintext, secret).toString()
: null,
(StringUtils.isNoneBlank(accessTokenPlaintext))
? encrypt(accessTokenPlaintext, secret).toString()
: null);
}
@Override
public CharSequence getPlainClientSecret(final ClientCredentials credentials) {
if (credentials == null || !credentials.hasSecret()) {
return null;
}
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return this.decrypt(credentials.secret, secret);
}
@Override
public CharSequence getPlainAccessToken(final ClientCredentials credentials) {
if (credentials == null || !credentials.hasAccessToken()) {
return null;
}
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return this.decrypt(credentials.accessToken, secret);
}
@Override
public CharSequence encrypt(final CharSequence text) {
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return encrypt(text, secret);
}
@Override
public CharSequence decrypt(final CharSequence text) {
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return decrypt(text, secret);
}
CharSequence encrypt(final CharSequence text, final CharSequence secret) {
if (text == null) {
throw new IllegalArgumentException("Text has null reference");
}
if (secret == null) {
log.warn("No internal secret supplied: skip encryption");
return text;
}
try {
final CharSequence salt = KeyGenerators.string().generateKey();
final CharSequence cipher = Encryptors
.delux(secret, salt)
.encrypt(text.toString());
return new StringBuilder(cipher)
.append(salt);
} catch (final Exception e) {
log.error("Failed to encrypt text: ", e);
throw e;
}
}
CharSequence decrypt(final CharSequence cipher, final CharSequence secret) {
if (cipher == null) {
throw new IllegalArgumentException("Cipher has null reference");
}
if (secret == null) {
log.warn("No internal secret supplied: skip decryption");
return cipher;
}
try {
final int length = cipher.length();
final int cipherTextLength = length - 16;
final CharSequence salt = cipher.subSequence(cipherTextLength, length);
final CharSequence cipherText = cipher.subSequence(0, cipherTextLength);
return Encryptors
.delux(secret, salt)
.decrypt(cipherText.toString());
} catch (final Exception e) {
log.error("Failed to decrypt text: ", e);
throw e;
}
}
private final static char[] possibleCharacters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^*()-_=+[{]}?"
.toCharArray();
public final static CharSequence generateClientId() {
return RandomStringUtils.random(
16, 0, possibleCharacters.length - 1, false, false,
possibleCharacters, new SecureRandom());
}
public final static CharSequence generateClientSecret() throws UnsupportedEncodingException {
// TODO fine a better way to generate a random char array instead of using RandomStringUtils.random which uses a String
return RandomStringUtils.random(
64, 0, possibleCharacters.length - 1, false, false,
possibleCharacters, new SecureRandom());
}
public final static void clearChars(final CharSequence sequence) {
if (sequence == null) {
return;
}
if (sequence instanceof CharBuffer) {
((CharBuffer) sequence).clear();
return;
}
throw new IllegalArgumentException(
"Cannot clear chars on CharSequence of type: " + sequence.getClass().getName());
}
}
/*
* 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.client;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import ch.ethz.seb.sebserver.gbl.util.Result;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.nio.CharBuffer;
import java.security.SecureRandom;
@Lazy
@Service
@WebServiceProfile
public class ClientCredentialServiceImpl implements ClientCredentialService {
private static final Logger log = LoggerFactory.getLogger(ClientCredentialServiceImpl.class);
private final Environment environment;
private final Cryptor cryptor;
protected ClientCredentialServiceImpl(
final Environment environment,
final Cryptor cryptor) {
this.environment = environment;
this.cryptor = cryptor;
}
@Override
public Result<ClientCredentials> generatedClientCredentials() {
return Result.tryCatch(() -> {
try {
return encryptClientCredentials(
generateClientId(),
generateClientSecret());
} catch (final UnsupportedEncodingException e) {
log.error("Error while trying to generate client credentials: ", e);
throw new RuntimeException("cause: ", e);
}
});
}
@Override
public ClientCredentials encryptClientCredentials(
final CharSequence clientIdPlaintext,
final CharSequence secretPlaintext,
final CharSequence accessTokenPlaintext) {
final CharSequence secret = this.environment
.getProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return new ClientCredentials(
clientIdPlaintext,
(StringUtils.isNoneBlank(secretPlaintext))
? Cryptor.encrypt(secretPlaintext, secret).toString()
: null,
(StringUtils.isNoneBlank(accessTokenPlaintext))
? Cryptor.encrypt(accessTokenPlaintext, secret).toString()
: null);
}
@Override
public CharSequence getPlainClientSecret(final ClientCredentials credentials) {
if (credentials == null || !credentials.hasSecret()) {
return null;
}
final CharSequence secret = this.environment
.getProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return Cryptor.decrypt(credentials.secret, secret);
}
@Override
public CharSequence getPlainAccessToken(final ClientCredentials credentials) {
if (credentials == null || !credentials.hasAccessToken()) {
return null;
}
final CharSequence secret = this.environment
.getProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return Cryptor.decrypt(credentials.accessToken, secret);
}
@Override
public CharSequence encrypt(final CharSequence text) {
return cryptor.encrypt(text);
}
@Override
public CharSequence decrypt(final CharSequence text) {
return cryptor.decrypt(text);
}
private final static char[] possibleCharacters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^*()-_=+[{]}?"
.toCharArray();
public static CharSequence generateClientId() {
return RandomStringUtils.random(
16, 0, possibleCharacters.length - 1, false, false,
possibleCharacters, new SecureRandom());
}
public static CharSequence generateClientSecret() throws UnsupportedEncodingException {
// TODO find a better way to generate a random char array instead of using RandomStringUtils.random which uses a String
return RandomStringUtils.random(
64, 0, possibleCharacters.length - 1, false, false,
possibleCharacters, new SecureRandom());
}
public static void clearChars(final CharSequence sequence) {
if (sequence == null) {
return;
}
if (sequence instanceof CharBuffer) {
((CharBuffer) sequence).clear();
return;
}
throw new IllegalArgumentException(
"Cannot clear chars on CharSequence of type: " + sequence.getClass().getName());
}
}

View file

@ -1 +0,0 @@
/ExamDAO.java

View file

@ -1,70 +1,70 @@
/*
* 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.dao;
import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
public interface ExamConfigurationMapDAO extends
EntityDAO<ExamConfigurationMap, ExamConfigurationMap>,
BulkActionSupportDAO<ExamConfigurationMap> {
/** Get a specific ExamConfigurationMap by the mapping identifiers
*
* @param examId The Exam mapping identifier
* @param configurationNodeId the ConfigurationNode mapping identifier
* @return Result refer to the ExamConfigurationMap with specified mapping or to an exception if happened */
Result<ExamConfigurationMap> byMapping(Long examId, Long configurationNodeId);
/** Get the password cipher of a specific ExamConfigurationMap by the mapping identifiers
*
* @param examId The Exam mapping identifier
* @param configurationNodeId the ConfigurationNode mapping identifier
* @return Result refer to the password cipher of specified mapping or to an exception if happened */
Result<CharSequence> getConfigPasswortCipher(Long examId, Long configurationNodeId);
/** Get the ConfigurationNode identifier of the default Exam Configuration of
* the Exam with specified identifier.
*
* @param examId The Exam identifier
* @return ConfigurationNode identifier of the default Exam Configuration of
* the Exam with specified identifier */
Result<Long> getDefaultConfigurationNode(Long examId);
/** Get the ConfigurationNode identifier of the Exam Configuration of
* the Exam for a specified user identifier.
*
* @param examId The Exam identifier
* @param userId the user identifier
* @return ConfigurationNode identifier of the Exam Configuration of
* the Exam for a specified user identifier */
Result<Long> getUserConfigurationNodeId(final Long examId, final String userId);
/** Get a list of all ConfigurationNode identifiers of configurations that currently are attached to a given Exam
*
* @param examId the Exam identifier
* @return Result refers to a list of ConfigurationNode identifiers or refer to an error if happened */
Result<Collection<Long>> getConfigurationNodeIds(Long examId);
/** Get all id of Exams that has a relation to the given configuration id.
*
* @param configurationNodeId the configuration node identifier (PK)
* @return Result referencing the List of exam identifiers (PK) for a given configuration node identifier */
Result<Collection<Long>> getExamIdsForConfigNodeId(Long configurationNodeId);
/** Get all id of Exams that has a relation to the given configuration id.
*
* @param configurationId
* @return Result referencing the List of exam identifiers (PK) for a given configuration identifier */
Result<Collection<Long>> getExamIdsForConfigId(Long configurationId);
}
/*
* 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.dao;
import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
public interface ExamConfigurationMapDAO extends
EntityDAO<ExamConfigurationMap, ExamConfigurationMap>,
BulkActionSupportDAO<ExamConfigurationMap> {
/** Get a specific ExamConfigurationMap by the mapping identifiers
*
* @param examId The Exam mapping identifier
* @param configurationNodeId the ConfigurationNode mapping identifier
* @return Result refer to the ExamConfigurationMap with specified mapping or to an exception if happened */
Result<ExamConfigurationMap> byMapping(Long examId, Long configurationNodeId);
/** Get the password cipher of a specific ExamConfigurationMap by the mapping identifiers
*
* @param examId The Exam mapping identifier
* @param configurationNodeId the ConfigurationNode mapping identifier
* @return Result refer to the password cipher of specified mapping or to an exception if happened */
Result<CharSequence> getConfigPasswordCipher(Long examId, Long configurationNodeId);
/** Get the ConfigurationNode identifier of the default Exam Configuration of
* the Exam with specified identifier.
*
* @param examId The Exam identifier
* @return ConfigurationNode identifier of the default Exam Configuration of
* the Exam with specified identifier */
Result<Long> getDefaultConfigurationNode(Long examId);
/** Get the ConfigurationNode identifier of the Exam Configuration of
* the Exam for a specified user identifier.
*
* @param examId The Exam identifier
* @param userId the user identifier
* @return ConfigurationNode identifier of the Exam Configuration of
* the Exam for a specified user identifier */
Result<Long> getUserConfigurationNodeId(final Long examId, final String userId);
/** Get a list of all ConfigurationNode identifiers of configurations that currently are attached to a given Exam
*
* @param examId the Exam identifier
* @return Result refers to a list of ConfigurationNode identifiers or refer to an error if happened */
Result<Collection<Long>> getConfigurationNodeIds(Long examId);
/** Get all id of Exams that has a relation to the given configuration id.
*
* @param configurationNodeId the configuration node identifier (PK)
* @return Result referencing the List of exam identifiers (PK) for a given configuration node identifier */
Result<Collection<Long>> getExamIdsForConfigNodeId(Long configurationNodeId);
/** Get all id of Exams that has a relation to the given configuration id.
*
* @param configurationId
* @return Result referencing the List of exam identifiers (PK) for a given configuration identifier */
Result<Collection<Long>> getExamIdsForConfigId(Long configurationId);
}

View file

@ -1,63 +1,63 @@
/*
* 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.dao;
import java.util.Collection;
import java.util.Set;
import org.springframework.cache.annotation.CacheEvict;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
/** Concrete EntityDAO interface of SebClientConfig entities */
public interface SebClientConfigDAO extends
ActivatableEntityDAO<SebClientConfig, SebClientConfig>,
BulkActionSupportDAO<SebClientConfig> {
/** Get a SebClientConfig by specified client identifier
*
* @param clientName the client name
* @return Result refer to the SebClientConfig for client or refer to an error if happened */
Result<SebClientConfig> byClientName(String clientName);
/** Get the configured ClientCredentials for a given SebClientConfig.
* The ClientCredentials are still encoded as they are on DB storage
*
* @param modelId the model identifier of the SebClientConfig to get the ClientCredentials for
* @return the configured ClientCredentials for a given SebClientConfig */
Result<ClientCredentials> getSebClientCredentials(String modelId);
/** Get the stored encrypted configuration password from a specified SEB client configuration.
* The SEB client configuration password is used to encrypt a SEB Client Configuration
*
* @param modelId the model
* @return encrypted configuration password */
Result<CharSequence> getConfigPasswortCipher(String modelId);
/** Get the stored encrypted configuration password from a specified SEB client configuration.
* The SEB client configuration password is used to encrypt a SEB Client Configuration.
*
* The SEB client configuration must be active otherwise a error is returned
*
* @param clientName the client name
* @return encrypted configuration password */
Result<CharSequence> getConfigPasswortCipherByClientName(String clientName);
@Override
@CacheEvict(
cacheNames = ClientConfigService.EXAM_CLIENT_DETAILS_CACHE,
allEntries = true)
Result<Collection<EntityKey>> delete(Set<EntityKey> all);
}
/*
* 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.dao;
import java.util.Collection;
import java.util.Set;
import org.springframework.cache.annotation.CacheEvict;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
/** Concrete EntityDAO interface of SebClientConfig entities */
public interface SebClientConfigDAO extends
ActivatableEntityDAO<SebClientConfig, SebClientConfig>,
BulkActionSupportDAO<SebClientConfig> {
/** Get a SebClientConfig by specified client identifier
*
* @param clientName the client name
* @return Result refer to the SebClientConfig for client or refer to an error if happened */
Result<SebClientConfig> byClientName(String clientName);
/** Get the configured ClientCredentials for a given SebClientConfig.
* The ClientCredentials are still encoded as they are on DB storage
*
* @param modelId the model identifier of the SebClientConfig to get the ClientCredentials for
* @return the configured ClientCredentials for a given SebClientConfig */
Result<ClientCredentials> getSebClientCredentials(String modelId);
/** Get the stored encrypted configuration password from a specified SEB client configuration.
* The SEB client configuration password is used to encrypt a SEB Client Configuration
*
* @param modelId the model
* @return encrypted configuration password */
Result<CharSequence> getConfigPasswordCipher(String modelId);
/** Get the stored encrypted configuration password from a specified SEB client configuration.
* The SEB client configuration password is used to encrypt a SEB Client Configuration.
*
* The SEB client configuration must be active otherwise a error is returned
*
* @param clientName the client name
* @return encrypted configuration password */
Result<CharSequence> getConfigPasswordCipherByClientName(String clientName);
@Override
@CacheEvict(
cacheNames = ClientConfigService.EXAM_CLIENT_DETAILS_CACHE,
allEntries = true)
Result<Collection<EntityKey>> delete(Set<EntityKey> all);
}

View file

@ -1,266 +1,264 @@
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy;
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.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
import io.micrometer.core.instrument.util.StringUtils;
@Lazy
@Component
@WebServiceProfile
public class ClientConnectionDAOImpl implements ClientConnectionDAO {
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
private final ClientEventRecordMapper clientEventRecordMapper;
protected ClientConnectionDAOImpl(
final ClientConnectionRecordMapper clientConnectionRecordMapper,
final ClientEventRecordMapper clientEventRecordMapper) {
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.clientEventRecordMapper = clientEventRecordMapper;
}
@Override
public EntityType entityType() {
return EntityType.CLIENT_CONNECTION;
}
@Override
@Transactional(readOnly = true)
public Result<ClientConnection> byPK(final Long id) {
return recordById(id)
.flatMap(ClientConnectionDAOImpl::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ClientConnection>> allMatching(
final FilterMap filterMap,
final Predicate<ClientConnection> predicate) {
return Result.tryCatch(() -> this.clientConnectionRecordMapper
.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
ClientConnectionRecordDynamicSqlSupport.examId,
isEqualToWhenPresent(filterMap.getClientConnectionExamId()))
.and(
ClientConnectionRecordDynamicSqlSupport.status,
isEqualToWhenPresent(filterMap.getClientConnectionStatus()))
.build()
.execute()
.stream()
.map(ClientConnectionDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ClientConnection>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> {
return this.clientConnectionRecordMapper.selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(ClientConnectionDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<String>> getConnectionTokens(final Long examId) {
return Result.tryCatch(() -> {
return this.clientConnectionRecordMapper
.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.build()
.execute()
.stream()
.map(ClientConnectionRecord::getConnectionToken)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
});
}
@Override
@Transactional
public Result<ClientConnection> createNew(final ClientConnection data) {
return Result.tryCatch(() -> {
final ClientConnectionRecord newRecord = new ClientConnectionRecord(
null,
data.institutionId,
data.examId,
ConnectionStatus.CONNECTION_REQUESTED.name(),
data.connectionToken,
null,
data.clientAddress,
data.virtualClientAddress,
Utils.getMillisecondsNow());
this.clientConnectionRecordMapper.insert(newRecord);
return newRecord;
})
.flatMap(ClientConnectionDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ClientConnection> save(final ClientConnection data) {
return Result.tryCatch(() -> {
final ClientConnectionRecord updateRecord = new ClientConnectionRecord(
data.id,
null,
data.examId,
data.status != null ? data.status.name() : null,
null,
data.userSessionId,
data.clientAddress,
data.virtualClientAddress,
null);
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
return this.clientConnectionRecordMapper.selectByPrimaryKey(data.id);
})
.flatMap(ClientConnectionDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
// first delete all related client events
this.clientEventRecordMapper.deleteByExample()
.where(
ClientEventRecordDynamicSqlSupport.clientConnectionId,
SqlBuilder.isIn(ids))
.build()
.execute();
// then delete all requested client-connections
this.clientConnectionRecordMapper.deleteByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.id,
SqlBuilder.isIn(ids))
.build()
.execute();
return ids.stream()
.map(id -> new EntityKey(id, EntityType.CLIENT_CONNECTION))
.collect(Collectors.toList());
});
}
@Override
public Result<ClientConnection> byConnectionToken(final String connectionToken) {
return Result.tryCatch(() -> {
final List<ClientConnectionRecord> list = this.clientConnectionRecordMapper
.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.connectionToken,
SqlBuilder.isEqualTo(connectionToken))
.build()
.execute();
if (list.isEmpty()) {
throw new ResourceNotFoundException(EntityType.CLIENT_CONNECTION, "connectionToken");
}
if (list.size() > 1) {
throw new IllegalStateException("Only one ClientConnection expected but there are: " + list.size());
}
return list.get(0);
})
.flatMap(ClientConnectionDAOImpl::toDomainModel);
}
private Result<ClientConnectionRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final ClientConnectionRecord record = this.clientConnectionRecordMapper.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
entityType(),
String.valueOf(id));
}
return record;
});
}
private static Result<ClientConnection> toDomainModel(final ClientConnectionRecord record) {
return Result.tryCatch(() -> {
final String status = record.getStatus();
return new ClientConnection(
record.getId(),
record.getInstitutionId(),
record.getExamId(),
(StringUtils.isNotBlank(status))
? ConnectionStatus.valueOf(status)
: ConnectionStatus.UNDEFINED,
record.getConnectionToken(),
record.getExamUserSessionId(),
record.getClientAddress(),
record.getVirtualClientAddress(),
record.getCreationTime());
});
}
}
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy;
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.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
import io.micrometer.core.instrument.util.StringUtils;
@Lazy
@Component
@WebServiceProfile
public class ClientConnectionDAOImpl implements ClientConnectionDAO {
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
private final ClientEventRecordMapper clientEventRecordMapper;
protected ClientConnectionDAOImpl(
final ClientConnectionRecordMapper clientConnectionRecordMapper,
final ClientEventRecordMapper clientEventRecordMapper) {
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.clientEventRecordMapper = clientEventRecordMapper;
}
@Override
public EntityType entityType() {
return EntityType.CLIENT_CONNECTION;
}
@Override
@Transactional(readOnly = true)
public Result<ClientConnection> byPK(final Long id) {
return recordById(id)
.flatMap(ClientConnectionDAOImpl::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ClientConnection>> allMatching(
final FilterMap filterMap,
final Predicate<ClientConnection> predicate) {
return Result.tryCatch(() -> this.clientConnectionRecordMapper
.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
ClientConnectionRecordDynamicSqlSupport.examId,
isEqualToWhenPresent(filterMap.getClientConnectionExamId()))
.and(
ClientConnectionRecordDynamicSqlSupport.status,
isEqualToWhenPresent(filterMap.getClientConnectionStatus()))
.build()
.execute()
.stream()
.map(ClientConnectionDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ClientConnection>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> this.clientConnectionRecordMapper.selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(ClientConnectionDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<String>> getConnectionTokens(final Long examId) {
return Result.tryCatch(() -> {
return this.clientConnectionRecordMapper
.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.build()
.execute()
.stream()
.map(ClientConnectionRecord::getConnectionToken)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
});
}
@Override
@Transactional
public Result<ClientConnection> createNew(final ClientConnection data) {
return Result.tryCatch(() -> {
final ClientConnectionRecord newRecord = new ClientConnectionRecord(
null,
data.institutionId,
data.examId,
ConnectionStatus.CONNECTION_REQUESTED.name(),
data.connectionToken,
null,
data.clientAddress,
data.virtualClientAddress,
Utils.getMillisecondsNow());
this.clientConnectionRecordMapper.insert(newRecord);
return newRecord;
})
.flatMap(ClientConnectionDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ClientConnection> save(final ClientConnection data) {
return Result.tryCatch(() -> {
final ClientConnectionRecord updateRecord = new ClientConnectionRecord(
data.id,
null,
data.examId,
data.status != null ? data.status.name() : null,
null,
data.userSessionId,
data.clientAddress,
data.virtualClientAddress,
null);
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
return this.clientConnectionRecordMapper.selectByPrimaryKey(data.id);
})
.flatMap(ClientConnectionDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
// first delete all related client events
this.clientEventRecordMapper.deleteByExample()
.where(
ClientEventRecordDynamicSqlSupport.clientConnectionId,
SqlBuilder.isIn(ids))
.build()
.execute();
// then delete all requested client-connections
this.clientConnectionRecordMapper.deleteByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.id,
SqlBuilder.isIn(ids))
.build()
.execute();
return ids.stream()
.map(id -> new EntityKey(id, EntityType.CLIENT_CONNECTION))
.collect(Collectors.toList());
});
}
@Override
public Result<ClientConnection> byConnectionToken(final String connectionToken) {
return Result.tryCatch(() -> {
final List<ClientConnectionRecord> list = this.clientConnectionRecordMapper
.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.connectionToken,
SqlBuilder.isEqualTo(connectionToken))
.build()
.execute();
if (list.isEmpty()) {
throw new ResourceNotFoundException(EntityType.CLIENT_CONNECTION, "connectionToken");
}
if (list.size() > 1) {
throw new IllegalStateException("Only one ClientConnection expected but there are: " + list.size());
}
return list.get(0);
})
.flatMap(ClientConnectionDAOImpl::toDomainModel);
}
private Result<ClientConnectionRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final ClientConnectionRecord record = this.clientConnectionRecordMapper.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
entityType(),
String.valueOf(id));
}
return record;
});
}
private static Result<ClientConnection> toDomainModel(final ClientConnectionRecord record) {
return Result.tryCatch(() -> {
final String status = record.getStatus();
return new ClientConnection(
record.getId(),
record.getInstitutionId(),
record.getExamId(),
(StringUtils.isNotBlank(status))
? ConnectionStatus.valueOf(status)
: ConnectionStatus.UNDEFINED,
record.getConnectionToken(),
record.getExamUserSessionId(),
record.getClientAddress(),
record.getVirtualClientAddress(),
record.getCreationTime());
});
}
}

View file

@ -1,265 +1,257 @@
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy;
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.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper.ConnectionEventJoinRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy
@Component
@WebServiceProfile
public class ClientEventDAOImpl implements ClientEventDAO {
private final ClientEventRecordMapper clientEventRecordMapper;
private final ClientEventExtentionMapper clientEventExtentionMapper;
protected ClientEventDAOImpl(
final ClientEventRecordMapper clientEventRecordMapper,
final ClientEventExtentionMapper clientEventExtentionMapper) {
this.clientEventRecordMapper = clientEventRecordMapper;
this.clientEventExtentionMapper = clientEventExtentionMapper;
}
@Override
public EntityType entityType() {
return EntityType.CLIENT_EVENT;
}
@Override
@Transactional(readOnly = true)
public Result<ClientEvent> byPK(final Long id) {
return recordById(id)
.flatMap(ClientEventDAOImpl::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ClientEvent>> allMatching(
final FilterMap filterMap,
final Predicate<ClientEvent> predicate) {
return Result.tryCatch(() -> {
return this.clientEventRecordMapper
.selectByExample()
.where(
ClientEventRecordDynamicSqlSupport.clientConnectionId,
isEqualToWhenPresent(filterMap.getClientEventConnectionId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
isEqualToWhenPresent(filterMap.getClientEventTypeId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
SqlBuilder.isNotEqualTo(EventType.LAST_PING.id))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.text,
SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText()))
.build()
.execute()
.stream()
.map(ClientEventDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList());
});
}
@Override
public Result<Collection<ExtendedClientEvent>> allMatchingExtended(
final FilterMap filterMap,
final Predicate<ExtendedClientEvent> predicate) {
return Result.tryCatch(() -> this.clientEventExtentionMapper.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
ClientConnectionRecordDynamicSqlSupport.examId,
isEqualToWhenPresent(filterMap.getClientEventExamId()))
.and(
ClientConnectionRecordDynamicSqlSupport.examUserSessionId,
SqlBuilder.isLikeWhenPresent(filterMap.getSQLWildcard(ClientConnection.FILTER_ATTR_SESSION_ID)))
.and(
ClientEventRecordDynamicSqlSupport.clientConnectionId,
isEqualToWhenPresent(filterMap.getClientEventConnectionId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
isEqualToWhenPresent(filterMap.getClientEventTypeId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
SqlBuilder.isNotEqualTo(EventType.LAST_PING.id))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.text,
SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText()))
.build()
.execute()
.stream()
.map(ClientEventDAOImpl::toDomainModelExtended)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ClientEvent>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> {
return this.clientEventRecordMapper.selectByExample()
.where(ClientEventRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(ClientEventDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@Override
@Transactional
public Result<ClientEvent> createNew(final ClientEvent data) {
return Result.tryCatch(() -> {
final EventType eventType = data.getEventType();
final ClientEventRecord newRecord = new ClientEventRecord(
null,
data.connectionId,
(eventType != null) ? eventType.id : EventType.UNKNOWN.id,
data.clientTime,
data.serverTime,
(data.numValue != null) ? new BigDecimal(data.numValue) : null,
data.text);
this.clientEventRecordMapper.insertSelective(newRecord);
return newRecord;
})
.flatMap(ClientEventDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ClientEvent> save(final ClientEvent data) {
throw new UnsupportedOperationException("Update is not supported for client events");
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
throw new UnsupportedOperationException(
"Delete is not supported for particular client events. "
+ "Use delete of a client connection to delete also all client events of this connection.");
}
private Result<ClientEventRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final ClientEventRecord record = this.clientEventRecordMapper.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
entityType(),
String.valueOf(id));
}
return record;
});
}
private static Result<ClientEvent> toDomainModel(final ClientEventRecord record) {
return Result.tryCatch(() -> {
final Integer type = record.getType();
final BigDecimal numericValue = record.getNumericValue();
return new ClientEvent(
record.getId(),
record.getClientConnectionId(),
(type != null) ? EventType.byId(type) : EventType.UNKNOWN,
record.getClientTime(),
record.getServerTime(),
(numericValue != null) ? numericValue.doubleValue() : null,
record.getText());
});
}
private static Result<ExtendedClientEvent> toDomainModelExtended(final ConnectionEventJoinRecord record) {
return Result.tryCatch(() -> {
return new ExtendedClientEvent(
record.institution_id,
record.exam_id,
record.exam_user_session_identifer,
record.id,
record.connection_id,
(record.type != null) ? EventType.byId(record.type) : EventType.UNKNOWN,
record.client_time,
record.server_time,
(record.numeric_value != null) ? record.numeric_value.doubleValue() : null,
record.text);
});
}
}
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy;
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.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper.ConnectionEventJoinRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy
@Component
@WebServiceProfile
public class ClientEventDAOImpl implements ClientEventDAO {
private final ClientEventRecordMapper clientEventRecordMapper;
private final ClientEventExtentionMapper clientEventExtentionMapper;
protected ClientEventDAOImpl(
final ClientEventRecordMapper clientEventRecordMapper,
final ClientEventExtentionMapper clientEventExtentionMapper) {
this.clientEventRecordMapper = clientEventRecordMapper;
this.clientEventExtentionMapper = clientEventExtentionMapper;
}
@Override
public EntityType entityType() {
return EntityType.CLIENT_EVENT;
}
@Override
@Transactional(readOnly = true)
public Result<ClientEvent> byPK(final Long id) {
return recordById(id)
.flatMap(ClientEventDAOImpl::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ClientEvent>> allMatching(
final FilterMap filterMap,
final Predicate<ClientEvent> predicate) {
return Result.tryCatch(() -> this.clientEventRecordMapper
.selectByExample()
.where(
ClientEventRecordDynamicSqlSupport.clientConnectionId,
isEqualToWhenPresent(filterMap.getClientEventConnectionId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
isEqualToWhenPresent(filterMap.getClientEventTypeId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
SqlBuilder.isNotEqualTo(EventType.LAST_PING.id))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.text,
SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText()))
.build()
.execute()
.stream()
.map(ClientEventDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
public Result<Collection<ExtendedClientEvent>> allMatchingExtended(
final FilterMap filterMap,
final Predicate<ExtendedClientEvent> predicate) {
return Result.tryCatch(() -> this.clientEventExtentionMapper.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
ClientConnectionRecordDynamicSqlSupport.examId,
isEqualToWhenPresent(filterMap.getClientEventExamId()))
.and(
ClientConnectionRecordDynamicSqlSupport.examUserSessionId,
SqlBuilder.isLikeWhenPresent(filterMap.getSQLWildcard(ClientConnection.FILTER_ATTR_SESSION_ID)))
.and(
ClientEventRecordDynamicSqlSupport.clientConnectionId,
isEqualToWhenPresent(filterMap.getClientEventConnectionId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
isEqualToWhenPresent(filterMap.getClientEventTypeId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
SqlBuilder.isNotEqualTo(EventType.LAST_PING.id))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.text,
SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText()))
.build()
.execute()
.stream()
.map(ClientEventDAOImpl::toDomainModelExtended)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ClientEvent>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> this.clientEventRecordMapper.selectByExample()
.where(ClientEventRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(ClientEventDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()));
}
@Override
@Transactional
public Result<ClientEvent> createNew(final ClientEvent data) {
return Result.tryCatch(() -> {
final EventType eventType = data.getEventType();
final ClientEventRecord newRecord = new ClientEventRecord(
null,
data.connectionId,
(eventType != null) ? eventType.id : EventType.UNKNOWN.id,
data.clientTime,
data.serverTime,
(data.numValue != null) ? new BigDecimal(data.numValue) : null,
data.text);
this.clientEventRecordMapper.insertSelective(newRecord);
return newRecord;
})
.flatMap(ClientEventDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ClientEvent> save(final ClientEvent data) {
throw new UnsupportedOperationException("Update is not supported for client events");
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
throw new UnsupportedOperationException(
"Delete is not supported for particular client events. "
+ "Use delete of a client connection to delete also all client events of this connection.");
}
private Result<ClientEventRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final ClientEventRecord record = this.clientEventRecordMapper.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
entityType(),
String.valueOf(id));
}
return record;
});
}
private static Result<ClientEvent> toDomainModel(final ClientEventRecord record) {
return Result.tryCatch(() -> {
final Integer type = record.getType();
final BigDecimal numericValue = record.getNumericValue();
return new ClientEvent(
record.getId(),
record.getClientConnectionId(),
(type != null) ? EventType.byId(type) : EventType.UNKNOWN,
record.getClientTime(),
record.getServerTime(),
(numericValue != null) ? numericValue.doubleValue() : null,
record.getText());
});
}
private static Result<ExtendedClientEvent> toDomainModelExtended(final ConnectionEventJoinRecord record) {
return Result.tryCatch(() -> new ExtendedClientEvent(
record.institution_id,
record.exam_id,
record.exam_user_session_identifer,
record.id,
record.connection_id,
(record.type != null) ? EventType.byId(record.type) : EventType.UNKNOWN,
record.client_time,
record.server_time,
record.numeric_value,
record.text));
}
}

View file

@ -1,266 +1,264 @@
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy;
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.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationAttributeRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationAttributeRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationAttributeRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy
@Component
@WebServiceProfile
public class ConfigurationAttributeDAOImpl implements ConfigurationAttributeDAO {
private final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper;
private final ConfigurationValueRecordMapper configurationValueRecordMapper;
private final OrientationRecordMapper orientationRecordMapper;
protected ConfigurationAttributeDAOImpl(
final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper,
final ConfigurationValueRecordMapper configurationValueRecordMapper,
final OrientationRecordMapper orientationRecordMapper) {
this.configurationAttributeRecordMapper = configurationAttributeRecordMapper;
this.configurationValueRecordMapper = configurationValueRecordMapper;
this.orientationRecordMapper = orientationRecordMapper;
}
@Override
public EntityType entityType() {
return EntityType.CONFIGURATION_ATTRIBUTE;
}
@Override
@Transactional(readOnly = true)
public Result<ConfigurationAttribute> byPK(final Long id) {
return recordById(id)
.flatMap(ConfigurationAttributeDAOImpl::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ConfigurationAttribute>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> {
return this.configurationAttributeRecordMapper.selectByExample()
.where(ConfigurationAttributeRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(ConfigurationAttributeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ConfigurationAttribute>> allMatching(
final FilterMap filterMap,
final Predicate<ConfigurationAttribute> predicate) {
return Result.tryCatch(() -> this.configurationAttributeRecordMapper
.selectByExample()
.where(
ConfigurationAttributeRecordDynamicSqlSupport.parentId,
SqlBuilder.isEqualToWhenPresent(filterMap.getConfigAttributeParentId()))
.and(
ConfigurationAttributeRecordDynamicSqlSupport.type,
SqlBuilder.isEqualToWhenPresent(filterMap.getConfigAttributeType()))
.build()
.execute()
.stream()
.map(ConfigurationAttributeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ConfigurationAttribute>> allChildAttributes(final Long parentId) {
return Result.tryCatch(() -> this.configurationAttributeRecordMapper
.selectByExample()
.where(
ConfigurationAttributeRecordDynamicSqlSupport.parentId,
SqlBuilder.isEqualTo(parentId))
.build()
.execute()
.stream()
.map(ConfigurationAttributeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.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) {
return Result.tryCatch(() -> {
final ConfigurationAttributeRecord newRecord = new ConfigurationAttributeRecord(
null,
data.name,
data.type.name(),
data.parentId,
data.resources,
data.validator,
data.dependencies,
data.defaultValue);
this.configurationAttributeRecordMapper.insert(newRecord);
return newRecord;
})
.flatMap(ConfigurationAttributeDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ConfigurationAttribute> save(final ConfigurationAttribute data) {
return Result.tryCatch(() -> {
final ConfigurationAttributeRecord newRecord = new ConfigurationAttributeRecord(
data.id,
data.name,
data.type.name(),
data.parentId,
data.resources,
data.validator,
data.dependencies,
data.defaultValue);
this.configurationAttributeRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.configurationAttributeRecordMapper.selectByPrimaryKey(data.id);
})
.flatMap(ConfigurationAttributeDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
final List<EntityKey> result = new ArrayList<>();
// if this is a complex attribute that has children, delete the children first
final List<ConfigurationAttributeRecord> children =
this.configurationAttributeRecordMapper.selectByExample()
.where(ConfigurationAttributeRecordDynamicSqlSupport.parentId, isIn(ids))
.build()
.execute();
// recursive call for children and adding result to the overall result set
if (children != null && !children.isEmpty()) {
result.addAll(delete(children.stream()
.map(r -> new EntityKey(r.getId(), EntityType.CONFIGURATION_ATTRIBUTE))
.collect(Collectors.toSet()))
.getOrThrow());
}
// delete all ConfigurationValue's that belongs to the ConfigurationAttributes to delete
this.configurationValueRecordMapper.deleteByExample()
.where(
ConfigurationValueRecordDynamicSqlSupport.configurationAttributeId,
SqlBuilder.isIn(ids))
.build()
.execute();
// delete all Orientations that belongs to the ConfigurationAttributes to delete
this.orientationRecordMapper.deleteByExample()
.where(
OrientationRecordDynamicSqlSupport.configAttributeId,
SqlBuilder.isIn(ids))
.build()
.execute();
// then delete all requested ConfigurationAttributes
this.configurationAttributeRecordMapper.deleteByExample()
.where(ConfigurationAttributeRecordDynamicSqlSupport.id, isIn(ids))
.build()
.execute();
result.addAll(ids.stream()
.map(id -> new EntityKey(id, EntityType.CONFIGURATION_ATTRIBUTE))
.collect(Collectors.toList()));
return result;
});
}
Result<ConfigurationAttributeRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final ConfigurationAttributeRecord record = this.configurationAttributeRecordMapper
.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
EntityType.CONFIGURATION_ATTRIBUTE,
String.valueOf(id));
}
return record;
});
}
static Result<ConfigurationAttribute> toDomainModel(final ConfigurationAttributeRecord record) {
return Result.tryCatch(() -> new ConfigurationAttribute(
record.getId(),
record.getParentId(),
record.getName(),
AttributeType.valueOf(record.getType()),
record.getResources(),
record.getValidator(),
record.getDependencies(),
record.getDefaultValue()));
}
}
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy;
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.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationAttributeRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationAttributeRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationAttributeRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy
@Component
@WebServiceProfile
public class ConfigurationAttributeDAOImpl implements ConfigurationAttributeDAO {
private final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper;
private final ConfigurationValueRecordMapper configurationValueRecordMapper;
private final OrientationRecordMapper orientationRecordMapper;
protected ConfigurationAttributeDAOImpl(
final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper,
final ConfigurationValueRecordMapper configurationValueRecordMapper,
final OrientationRecordMapper orientationRecordMapper) {
this.configurationAttributeRecordMapper = configurationAttributeRecordMapper;
this.configurationValueRecordMapper = configurationValueRecordMapper;
this.orientationRecordMapper = orientationRecordMapper;
}
@Override
public EntityType entityType() {
return EntityType.CONFIGURATION_ATTRIBUTE;
}
@Override
@Transactional(readOnly = true)
public Result<ConfigurationAttribute> byPK(final Long id) {
return recordById(id)
.flatMap(ConfigurationAttributeDAOImpl::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ConfigurationAttribute>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> this.configurationAttributeRecordMapper.selectByExample()
.where(ConfigurationAttributeRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(ConfigurationAttributeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ConfigurationAttribute>> allMatching(
final FilterMap filterMap,
final Predicate<ConfigurationAttribute> predicate) {
return Result.tryCatch(() -> this.configurationAttributeRecordMapper
.selectByExample()
.where(
ConfigurationAttributeRecordDynamicSqlSupport.parentId,
SqlBuilder.isEqualToWhenPresent(filterMap.getConfigAttributeParentId()))
.and(
ConfigurationAttributeRecordDynamicSqlSupport.type,
SqlBuilder.isEqualToWhenPresent(filterMap.getConfigAttributeType()))
.build()
.execute()
.stream()
.map(ConfigurationAttributeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ConfigurationAttribute>> allChildAttributes(final Long parentId) {
return Result.tryCatch(() -> this.configurationAttributeRecordMapper
.selectByExample()
.where(
ConfigurationAttributeRecordDynamicSqlSupport.parentId,
SqlBuilder.isEqualTo(parentId))
.build()
.execute()
.stream()
.map(ConfigurationAttributeDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.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) {
return Result.tryCatch(() -> {
final ConfigurationAttributeRecord newRecord = new ConfigurationAttributeRecord(
null,
data.name,
data.type.name(),
data.parentId,
data.resources,
data.validator,
data.dependencies,
data.defaultValue);
this.configurationAttributeRecordMapper.insert(newRecord);
return newRecord;
})
.flatMap(ConfigurationAttributeDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ConfigurationAttribute> save(final ConfigurationAttribute data) {
return Result.tryCatch(() -> {
final ConfigurationAttributeRecord newRecord = new ConfigurationAttributeRecord(
data.id,
data.name,
data.type.name(),
data.parentId,
data.resources,
data.validator,
data.dependencies,
data.defaultValue);
this.configurationAttributeRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.configurationAttributeRecordMapper.selectByPrimaryKey(data.id);
})
.flatMap(ConfigurationAttributeDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
final List<EntityKey> result = new ArrayList<>();
// if this is a complex attribute that has children, delete the children first
final List<ConfigurationAttributeRecord> children =
this.configurationAttributeRecordMapper.selectByExample()
.where(ConfigurationAttributeRecordDynamicSqlSupport.parentId, isIn(ids))
.build()
.execute();
// recursive call for children and adding result to the overall result set
if (children != null && !children.isEmpty()) {
result.addAll(delete(children.stream()
.map(r -> new EntityKey(r.getId(), EntityType.CONFIGURATION_ATTRIBUTE))
.collect(Collectors.toSet()))
.getOrThrow());
}
// delete all ConfigurationValue's that belongs to the ConfigurationAttributes to delete
this.configurationValueRecordMapper.deleteByExample()
.where(
ConfigurationValueRecordDynamicSqlSupport.configurationAttributeId,
SqlBuilder.isIn(ids))
.build()
.execute();
// delete all Orientations that belongs to the ConfigurationAttributes to delete
this.orientationRecordMapper.deleteByExample()
.where(
OrientationRecordDynamicSqlSupport.configAttributeId,
SqlBuilder.isIn(ids))
.build()
.execute();
// then delete all requested ConfigurationAttributes
this.configurationAttributeRecordMapper.deleteByExample()
.where(ConfigurationAttributeRecordDynamicSqlSupport.id, isIn(ids))
.build()
.execute();
result.addAll(ids.stream()
.map(id -> new EntityKey(id, EntityType.CONFIGURATION_ATTRIBUTE))
.collect(Collectors.toList()));
return result;
});
}
Result<ConfigurationAttributeRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final ConfigurationAttributeRecord record = this.configurationAttributeRecordMapper
.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
EntityType.CONFIGURATION_ATTRIBUTE,
String.valueOf(id));
}
return record;
});
}
static Result<ConfigurationAttribute> toDomainModel(final ConfigurationAttributeRecord record) {
return Result.tryCatch(() -> new ConfigurationAttribute(
record.getId(),
record.getParentId(),
record.getName(),
AttributeType.valueOf(record.getType()),
record.getResources(),
record.getValidator(),
record.getDependencies(),
record.getDefaultValue()));
}
}

View file

@ -1,494 +1,480 @@
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationNodeRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamConfigurationMapRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy
@Component
@WebServiceProfile
public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
private final ExamRecordMapper examRecordMapper;
private final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper;
private final ConfigurationNodeRecordMapper configurationNodeRecordMapper;
private final ClientCredentialService clientCredentialService;
private final ExamDAO examDAO;
protected ExamConfigurationMapDAOImpl(
final ExamRecordMapper examRecordMapper,
final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper,
final ConfigurationNodeRecordMapper configurationNodeRecordMapper,
final ClientCredentialService clientCredentialService,
final ExamDAO examDAO) {
this.examRecordMapper = examRecordMapper;
this.examConfigurationMapRecordMapper = examConfigurationMapRecordMapper;
this.configurationNodeRecordMapper = configurationNodeRecordMapper;
this.clientCredentialService = clientCredentialService;
this.examDAO = examDAO;
}
@Override
public EntityType entityType() {
return EntityType.EXAM_CONFIGURATION_MAP;
}
@Override
@Transactional(readOnly = true)
public Result<ExamConfigurationMap> byPK(final Long id) {
return recordById(id)
.flatMap(this::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ExamConfigurationMap>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> {
return this.examConfigurationMapRecordMapper.selectByExample()
.where(ExamConfigurationMapRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ExamConfigurationMap>> allMatching(
final FilterMap filterMap,
final Predicate<ExamConfigurationMap> predicate) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.institutionId,
SqlBuilder.isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualToWhenPresent(filterMap.getExamConfigExamId()))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
SqlBuilder.isEqualToWhenPresent(filterMap.getExamConfigConfigId()))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<ExamConfigurationMap> byMapping(final Long examId, final Long configurationNodeId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
SqlBuilder.isEqualTo(configurationNodeId))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Utils.toSingleton()));
}
@Override
@Transactional(readOnly = true)
public Result<CharSequence> getConfigPasswortCipher(final Long examId, final Long configurationNodeId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
SqlBuilder.isEqualTo(configurationNodeId))
.build()
.execute()
.stream()
.collect(Utils.toSingleton()))
.map(ExamConfigurationMapRecord::getEncryptSecret);
}
@Override
@Transactional(readOnly = true)
public Result<Long> getDefaultConfigurationNode(final Long examId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.userNames,
SqlBuilder.isNull())
.build()
.execute()
.stream()
.map(mapping -> mapping.getConfigurationNodeId())
.collect(Utils.toSingleton()));
}
@Override
@Transactional(readOnly = true)
public Result<Long> getUserConfigurationNodeId(final Long examId, final String userId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.userNames,
SqlBuilder.isLike(Utils.toSQLWildcard(userId)))
.build()
.execute()
.stream()
.map(mapping -> mapping.getConfigurationNodeId())
.collect(Utils.toSingleton()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Long>> getConfigurationNodeIds(final Long examId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.build()
.execute()
.stream()
.map(mapping -> mapping.getConfigurationNodeId())
.collect(Collectors.toList()));
}
@Override
@Transactional
public Result<ExamConfigurationMap> createNew(final ExamConfigurationMap data) {
return checkMappingIntegrity(data)
.map(config -> {
final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord(
null,
data.institutionId,
data.examId,
data.configurationNodeId,
data.userNames,
getEncryptionPassword(data));
this.examConfigurationMapRecordMapper.insert(newRecord);
return newRecord;
})
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ExamConfigurationMap> save(final ExamConfigurationMap data) {
return Result.tryCatch(() -> {
final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord(
data.id,
null,
null,
null,
data.userNames,
getEncryptionPassword(data));
this.examConfigurationMapRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.examConfigurationMapRecordMapper.selectByPrimaryKey(data.id);
})
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
this.examConfigurationMapRecordMapper.deleteByExample()
.where(ExamConfigurationMapRecordDynamicSqlSupport.id, isIn(ids))
.build()
.execute();
return ids.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) {
return Collections.emptySet();
}
// define the select function in case of source type
Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction;
switch (bulkAction.sourceType) {
case INSTITUTION:
selectionFunction = this::allIdsOfInstitution;
break;
case LMS_SETUP:
selectionFunction = this::allIdsOfLmsSetup;
break;
case EXAM:
selectionFunction = this::allIdsOfExam;
break;
case CONFIGURATION_NODE:
selectionFunction = this::allIdsOfConfig;
break;
default:
selectionFunction = key -> Result.of(Collections.emptyList()); //empty select function
break;
}
return getDependencies(bulkAction, selectionFunction);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Long>> getExamIdsForConfigNodeId(final Long configurationNodeId) {
return Result.tryCatch(() -> {
return this.examConfigurationMapRecordMapper.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
isEqualTo(configurationNodeId))
.build()
.execute()
.stream()
.map(record -> record.getExamId())
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Long>> getExamIdsForConfigId(final Long configurationId) {
return Result.tryCatch(() -> {
return this.configurationNodeRecordMapper.selectIdsByExample()
.leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord)
.on(
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
equalTo(ConfigurationNodeRecordDynamicSqlSupport.id))
.where(
ConfigurationRecordDynamicSqlSupport.id,
isEqualTo(configurationId))
.build()
.execute()
.stream()
.collect(Utils.toSingleton());
})
.flatMap(this::getExamIdsForConfigNodeId);
}
private Result<ExamConfigurationMapRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final ExamConfigurationMapRecord record = this.examConfigurationMapRecordMapper
.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
EntityType.EXAM_CONFIGURATION_MAP,
String.valueOf(id));
}
return record;
});
}
private Result<ExamConfigurationMap> toDomainModel(final ExamConfigurationMapRecord record) {
return Result.tryCatch(() -> {
final ConfigurationNodeRecord config = this.configurationNodeRecordMapper
.selectByPrimaryKey(record.getConfigurationNodeId());
final String status = config.getStatus();
final Exam exam = this.examDAO.byPK(record.getExamId())
.getOr(null);
return new ExamConfigurationMap(
record.getId(),
record.getInstitutionId(),
record.getExamId(),
(exam != null) ? exam.name : null,
(exam != null) ? exam.description : null,
(exam != null) ? exam.startTime : null,
(exam != null) ? exam.type : ExamType.UNDEFINED,
record.getConfigurationNodeId(),
record.getUserNames(),
null,
null,
config.getName(),
config.getDescription(),
(StringUtils.isNotBlank(status)) ? ConfigurationStatus.valueOf(status) : null);
});
}
private Result<ExamConfigurationMap> checkMappingIntegrity(final ExamConfigurationMap data) {
return Result.tryCatch(() -> {
final ConfigurationNodeRecord config =
this.configurationNodeRecordMapper.selectByPrimaryKey(data.configurationNodeId);
if (config == null) {
throw new ResourceNotFoundException(
EntityType.CONFIGURATION_NODE,
String.valueOf(data.configurationNodeId));
}
if (config.getInstitutionId().longValue() != data.institutionId.longValue()) {
throw new IllegalArgumentException("Institutional integrity constraint violation");
}
final ExamRecord exam = this.examRecordMapper.selectByPrimaryKey(data.examId);
if (exam == null) {
throw new ResourceNotFoundException(
EntityType.EXAM,
String.valueOf(data.configurationNodeId));
}
if (exam.getInstitutionId().longValue() != data.institutionId.longValue()) {
throw new IllegalArgumentException("Institutional integrity constraint violation");
}
return data;
});
}
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
return Result.tryCatch(() -> {
return this.examConfigurationMapRecordMapper.selectIdsByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.institutionId,
isEqualTo(Long.valueOf(institutionKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList());
});
}
private Result<Collection<EntityKey>> allIdsOfLmsSetup(final EntityKey lmsSetupKey) {
return Result.tryCatch(() -> {
return this.examConfigurationMapRecordMapper.selectIdsByExample()
.leftJoin(ExamRecordDynamicSqlSupport.examRecord)
.on(
ExamRecordDynamicSqlSupport.id,
equalTo(ExamConfigurationMapRecordDynamicSqlSupport.examId))
.where(
ExamRecordDynamicSqlSupport.lmsSetupId,
isEqualTo(Long.valueOf(lmsSetupKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList());
});
}
private Result<Collection<EntityKey>> allIdsOfExam(final EntityKey examKey) {
return Result.tryCatch(() -> {
return this.examConfigurationMapRecordMapper.selectIdsByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
isEqualTo(Long.valueOf(examKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList());
});
}
private Result<Collection<EntityKey>> allIdsOfConfig(final EntityKey configKey) {
return Result.tryCatch(() -> {
return this.examConfigurationMapRecordMapper.selectIdsByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
isEqualTo(Long.valueOf(configKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList());
});
}
private String getEncryptionPassword(final ExamConfigurationMap examConfigurationMap) {
if (examConfigurationMap.hasEncryptionSecret() &&
!examConfigurationMap.encryptSecret.equals(examConfigurationMap.confirmEncryptSecret)) {
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
}
final CharSequence encrypted_encrypt_secret = examConfigurationMap.hasEncryptionSecret()
? this.clientCredentialService.encrypt(examConfigurationMap.encryptSecret)
: null;
return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null;
}
}
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationNodeRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamConfigurationMapRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy
@Component
@WebServiceProfile
public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
private final ExamRecordMapper examRecordMapper;
private final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper;
private final ConfigurationNodeRecordMapper configurationNodeRecordMapper;
private final ClientCredentialService clientCredentialService;
private final ExamDAO examDAO;
protected ExamConfigurationMapDAOImpl(
final ExamRecordMapper examRecordMapper,
final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper,
final ConfigurationNodeRecordMapper configurationNodeRecordMapper,
final ClientCredentialService clientCredentialService,
final ExamDAO examDAO) {
this.examRecordMapper = examRecordMapper;
this.examConfigurationMapRecordMapper = examConfigurationMapRecordMapper;
this.configurationNodeRecordMapper = configurationNodeRecordMapper;
this.clientCredentialService = clientCredentialService;
this.examDAO = examDAO;
}
@Override
public EntityType entityType() {
return EntityType.EXAM_CONFIGURATION_MAP;
}
@Override
@Transactional(readOnly = true)
public Result<ExamConfigurationMap> byPK(final Long id) {
return recordById(id)
.flatMap(this::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ExamConfigurationMap>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectByExample()
.where(ExamConfigurationMapRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ExamConfigurationMap>> allMatching(
final FilterMap filterMap,
final Predicate<ExamConfigurationMap> predicate) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.institutionId,
SqlBuilder.isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualToWhenPresent(filterMap.getExamConfigExamId()))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
SqlBuilder.isEqualToWhenPresent(filterMap.getExamConfigConfigId()))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<ExamConfigurationMap> byMapping(final Long examId, final Long configurationNodeId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
SqlBuilder.isEqualTo(configurationNodeId))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Utils.toSingleton()));
}
@Override
@Transactional(readOnly = true)
public Result<CharSequence> getConfigPasswordCipher(final Long examId, final Long configurationNodeId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
SqlBuilder.isEqualTo(configurationNodeId))
.build()
.execute()
.stream()
.collect(Utils.toSingleton()))
.map(ExamConfigurationMapRecord::getEncryptSecret);
}
@Override
@Transactional(readOnly = true)
public Result<Long> getDefaultConfigurationNode(final Long examId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.userNames,
SqlBuilder.isNull())
.build()
.execute()
.stream()
.map(ExamConfigurationMapRecord::getConfigurationNodeId)
.collect(Utils.toSingleton()));
}
@Override
@Transactional(readOnly = true)
public Result<Long> getUserConfigurationNodeId(final Long examId, final String userId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.and(
ExamConfigurationMapRecordDynamicSqlSupport.userNames,
SqlBuilder.isLike(Utils.toSQLWildcard(userId)))
.build()
.execute()
.stream()
.map(ExamConfigurationMapRecord::getConfigurationNodeId)
.collect(Utils.toSingleton()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Long>> getConfigurationNodeIds(final Long examId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.build()
.execute()
.stream()
.map(ExamConfigurationMapRecord::getConfigurationNodeId)
.collect(Collectors.toList()));
}
@Override
@Transactional
public Result<ExamConfigurationMap> createNew(final ExamConfigurationMap data) {
return checkMappingIntegrity(data)
.map(config -> {
final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord(
null,
data.institutionId,
data.examId,
data.configurationNodeId,
data.userNames,
getEncryptionPassword(data));
this.examConfigurationMapRecordMapper.insert(newRecord);
return newRecord;
})
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ExamConfigurationMap> save(final ExamConfigurationMap data) {
return Result.tryCatch(() -> {
final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord(
data.id,
null,
null,
null,
data.userNames,
getEncryptionPassword(data));
this.examConfigurationMapRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.examConfigurationMapRecordMapper.selectByPrimaryKey(data.id);
})
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
this.examConfigurationMapRecordMapper.deleteByExample()
.where(ExamConfigurationMapRecordDynamicSqlSupport.id, isIn(ids))
.build()
.execute();
return ids.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) {
return Collections.emptySet();
}
// define the select function in case of source type
Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction;
switch (bulkAction.sourceType) {
case INSTITUTION:
selectionFunction = this::allIdsOfInstitution;
break;
case LMS_SETUP:
selectionFunction = this::allIdsOfLmsSetup;
break;
case EXAM:
selectionFunction = this::allIdsOfExam;
break;
case CONFIGURATION_NODE:
selectionFunction = this::allIdsOfConfig;
break;
default:
selectionFunction = key -> Result.of(Collections.emptyList()); //empty select function
break;
}
return getDependencies(bulkAction, selectionFunction);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Long>> getExamIdsForConfigNodeId(final Long configurationNodeId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
isEqualTo(configurationNodeId))
.build()
.execute()
.stream()
.map(ExamConfigurationMapRecord::getExamId)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Long>> getExamIdsForConfigId(final Long configurationId) {
return Result.tryCatch(() -> this.configurationNodeRecordMapper.selectIdsByExample()
.leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord)
.on(
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
equalTo(ConfigurationNodeRecordDynamicSqlSupport.id))
.where(
ConfigurationRecordDynamicSqlSupport.id,
isEqualTo(configurationId))
.build()
.execute()
.stream()
.collect(Utils.toSingleton()))
.flatMap(this::getExamIdsForConfigNodeId);
}
private Result<ExamConfigurationMapRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final ExamConfigurationMapRecord record = this.examConfigurationMapRecordMapper
.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
EntityType.EXAM_CONFIGURATION_MAP,
String.valueOf(id));
}
return record;
});
}
private Result<ExamConfigurationMap> toDomainModel(final ExamConfigurationMapRecord record) {
return Result.tryCatch(() -> {
final ConfigurationNodeRecord config = this.configurationNodeRecordMapper
.selectByPrimaryKey(record.getConfigurationNodeId());
final String status = config.getStatus();
final Exam exam = this.examDAO.byPK(record.getExamId())
.getOr(null);
return new ExamConfigurationMap(
record.getId(),
record.getInstitutionId(),
record.getExamId(),
(exam != null) ? exam.name : null,
(exam != null) ? exam.description : null,
(exam != null) ? exam.startTime : null,
(exam != null) ? exam.type : ExamType.UNDEFINED,
record.getConfigurationNodeId(),
record.getUserNames(),
record.getEncryptSecret(),
null,
config.getName(),
config.getDescription(),
(StringUtils.isNotBlank(status)) ? ConfigurationStatus.valueOf(status) : null);
});
}
private Result<ExamConfigurationMap> checkMappingIntegrity(final ExamConfigurationMap data) {
return Result.tryCatch(() -> {
final ConfigurationNodeRecord config =
this.configurationNodeRecordMapper.selectByPrimaryKey(data.configurationNodeId);
if (config == null) {
throw new ResourceNotFoundException(
EntityType.CONFIGURATION_NODE,
String.valueOf(data.configurationNodeId));
}
if (config.getInstitutionId().longValue() != data.institutionId.longValue()) {
throw new IllegalArgumentException("Institutional integrity constraint violation");
}
final ExamRecord exam = this.examRecordMapper.selectByPrimaryKey(data.examId);
if (exam == null) {
throw new ResourceNotFoundException(
EntityType.EXAM,
String.valueOf(data.configurationNodeId));
}
if (exam.getInstitutionId().longValue() != data.institutionId.longValue()) {
throw new IllegalArgumentException("Institutional integrity constraint violation");
}
return data;
});
}
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.institutionId,
isEqualTo(Long.valueOf(institutionKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList()));
}
private Result<Collection<EntityKey>> allIdsOfLmsSetup(final EntityKey lmsSetupKey) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
.leftJoin(ExamRecordDynamicSqlSupport.examRecord)
.on(
ExamRecordDynamicSqlSupport.id,
equalTo(ExamConfigurationMapRecordDynamicSqlSupport.examId))
.where(
ExamRecordDynamicSqlSupport.lmsSetupId,
isEqualTo(Long.valueOf(lmsSetupKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList()));
}
private Result<Collection<EntityKey>> allIdsOfExam(final EntityKey examKey) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
isEqualTo(Long.valueOf(examKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList()));
}
private Result<Collection<EntityKey>> allIdsOfConfig(final EntityKey configKey) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
isEqualTo(Long.valueOf(configKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList()));
}
private String getEncryptionPassword(final ExamConfigurationMap examConfigurationMap) {
if (examConfigurationMap.hasEncryptionSecret() &&
!examConfigurationMap.encryptSecret.equals(examConfigurationMap.confirmEncryptSecret)) {
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
}
final CharSequence encrypted_encrypt_secret = examConfigurationMap.hasEncryptionSecret()
? this.clientCredentialService.encrypt(examConfigurationMap.encryptSecret)
: null;
return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null;
}
}

View file

@ -1,430 +1,526 @@
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.SebClientConfigRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy
@Component
@WebServiceProfile
public class SebClientConfigDAOImpl implements SebClientConfigDAO {
private final SebClientConfigRecordMapper sebClientConfigRecordMapper;
private final ClientCredentialService clientCredentialService;
private final AdditionalAttributesDAOImpl additionalAttributesDAO;
protected SebClientConfigDAOImpl(
final SebClientConfigRecordMapper sebClientConfigRecordMapper,
final ClientCredentialService clientCredentialService,
final AdditionalAttributesDAOImpl additionalAttributesDAO) {
this.sebClientConfigRecordMapper = sebClientConfigRecordMapper;
this.clientCredentialService = clientCredentialService;
this.additionalAttributesDAO = additionalAttributesDAO;
}
@Override
public EntityType entityType() {
return EntityType.SEB_CLIENT_CONFIGURATION;
}
@Override
@Transactional(readOnly = true)
public Result<SebClientConfig> byPK(final Long id) {
return recordById(id)
.flatMap(this::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<SebClientConfig>> all(final Long institutionId, final Boolean active) {
return Result.tryCatch(() -> {
final List<SebClientConfigRecord> records = (active != null)
? this.sebClientConfigRecordMapper.selectByExample()
.where(
SebClientConfigRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(institutionId))
.and(
SebClientConfigRecordDynamicSqlSupport.active,
isEqualToWhenPresent(BooleanUtils.toIntegerObject(active)))
.build()
.execute()
: this.sebClientConfigRecordMapper.selectByExample().build().execute();
return records.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<SebClientConfig>> allMatching(
final FilterMap filterMap,
final Predicate<SebClientConfig> predicate) {
return Result.tryCatch(() -> {
return this.sebClientConfigRecordMapper
.selectByExample()
.where(
SebClientConfigRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
SebClientConfigRecordDynamicSqlSupport.name,
isLikeWhenPresent(filterMap.getName()))
.and(
SebClientConfigRecordDynamicSqlSupport.date,
isGreaterThanOrEqualToWhenPresent(filterMap.getSebClientConfigFromTime()))
.and(
SebClientConfigRecordDynamicSqlSupport.active,
isEqualToWhenPresent(filterMap.getActiveAsInt()))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList());
});
}
@Override
public Result<SebClientConfig> byClientName(final String clientName) {
return Result.tryCatch(() -> {
return this.sebClientConfigRecordMapper
.selectByExample()
.where(
SebClientConfigRecordDynamicSqlSupport.clientName,
isEqualTo(clientName))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Utils.toSingleton());
});
}
@Override
@Transactional(readOnly = true)
public Result<CharSequence> getConfigPasswortCipherByClientName(final String clientName) {
return Result.tryCatch(() -> {
final SebClientConfigRecord record = this.sebClientConfigRecordMapper
.selectByExample()
.where(
SebClientConfigRecordDynamicSqlSupport.clientName,
isEqualTo(clientName))
.and(
SebClientConfigRecordDynamicSqlSupport.active,
isNotEqualTo(0))
.build()
.execute()
.stream()
.collect(Utils.toSingleton());
return record.getClientSecret();
});
}
@Override
@Transactional(readOnly = true)
public boolean isActive(final String modelId) {
if (StringUtils.isBlank(modelId)) {
return false;
}
return this.sebClientConfigRecordMapper.countByExample()
.where(SebClientConfigRecordDynamicSqlSupport.id, isEqualTo(Long.valueOf(modelId)))
.and(SebClientConfigRecordDynamicSqlSupport.active, isEqualTo(BooleanUtils.toInteger(true)))
.build()
.execute()
.longValue() > 0;
}
@Override
@Transactional
public Result<Collection<EntityKey>> setActive(final Set<EntityKey> all, final boolean active) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
final SebClientConfigRecord record = new SebClientConfigRecord(
null, null, null, null, null, null, null,
BooleanUtils.toIntegerObject(active));
this.sebClientConfigRecordMapper.updateByExampleSelective(record)
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids))
.build()
.execute();
return ids.stream()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.collect(Collectors.toList());
});
}
@Override
@Transactional
public Result<SebClientConfig> createNew(final SebClientConfig sebClientConfig) {
return this.clientCredentialService
.generatedClientCredentials()
.map(cc -> {
checkUniqueName(sebClientConfig);
final SebClientConfigRecord newRecord = new SebClientConfigRecord(
null,
sebClientConfig.institutionId,
sebClientConfig.name,
DateTime.now(DateTimeZone.UTC),
cc.clientIdAsString(),
cc.secretAsString(),
getEncryptionPassword(sebClientConfig),
BooleanUtils.toInteger(BooleanUtils.isTrue(sebClientConfig.active)));
this.sebClientConfigRecordMapper
.insert(newRecord);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
newRecord.getId(),
SebClientConfig.ATTR_FALLBACK_START_URL,
sebClientConfig.fallbackStartURL);
return newRecord;
})
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<SebClientConfig> save(final SebClientConfig sebClientConfig) {
return Result.tryCatch(() -> {
checkUniqueName(sebClientConfig);
final SebClientConfigRecord newRecord = new SebClientConfigRecord(
sebClientConfig.id,
null,
sebClientConfig.name,
null,
null,
null,
getEncryptionPassword(sebClientConfig),
null);
this.sebClientConfigRecordMapper
.updateByPrimaryKeySelective(newRecord);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
newRecord.getId(),
SebClientConfig.ATTR_FALLBACK_START_URL,
sebClientConfig.fallbackStartURL);
return this.sebClientConfigRecordMapper
.selectByPrimaryKey(sebClientConfig.id);
})
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
this.sebClientConfigRecordMapper.deleteByExample()
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids))
.build()
.execute();
return ids.stream()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<SebClientConfig>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> {
return this.sebClientConfigRecordMapper.selectByExample()
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
// all of institution
if (bulkAction.sourceType == EntityType.INSTITUTION) {
return getDependencies(bulkAction, this::allIdsOfInstitution);
}
return Collections.emptySet();
}
@Override
@Transactional(readOnly = true)
public Result<ClientCredentials> getSebClientCredentials(final String modelId) {
return recordByModelId(modelId)
.map(rec -> new ClientCredentials(
rec.getClientName(),
rec.getClientSecret(),
null));
}
@Override
@Transactional(readOnly = true)
public Result<CharSequence> getConfigPasswortCipher(final String modelId) {
return recordByModelId(modelId)
.map(rec -> rec.getEncryptSecret());
}
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
return Result.tryCatch(() -> {
return this.sebClientConfigRecordMapper.selectIdsByExample()
.where(SebClientConfigRecordDynamicSqlSupport.institutionId,
isEqualTo(Long.valueOf(institutionKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.collect(Collectors.toList());
});
}
private Result<SebClientConfigRecord> recordByModelId(final String modelId) {
return Result.tryCatch(() -> {
return recordById(Long.parseLong(modelId)).getOrThrow();
});
}
private Result<SebClientConfigRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final SebClientConfigRecord record = this.sebClientConfigRecordMapper
.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
EntityType.SEB_CLIENT_CONFIGURATION,
String.valueOf(id));
}
return record;
});
}
private Result<SebClientConfig> toDomainModel(final SebClientConfigRecord record) {
final String fallbackURL = this.additionalAttributesDAO.getAdditionalAttributes(
EntityType.SEB_CLIENT_CONFIGURATION,
record.getId())
.getOrThrow()
.stream()
.filter(rec -> SebClientConfig.ATTR_FALLBACK_START_URL.equals(rec.getName()))
.findFirst()
.map(AdditionalAttributeRecord::getValue)
.orElse(null);
return Result.tryCatch(() -> new SebClientConfig(
record.getId(),
record.getInstitutionId(),
record.getName(),
fallbackURL,
record.getDate(),
null,
null,
BooleanUtils.toBooleanObject(record.getActive())));
}
private String getEncryptionPassword(final SebClientConfig sebClientConfig) {
if (sebClientConfig.hasEncryptionSecret() &&
!sebClientConfig.encryptSecret.equals(sebClientConfig.confirmEncryptSecret)) {
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
}
final CharSequence encrypted_encrypt_secret = sebClientConfig.hasEncryptionSecret()
? this.clientCredentialService.encrypt(sebClientConfig.encryptSecret)
: null;
return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null;
}
// check if same name already exists for the same institution
// if true an APIMessageException with a field validation error is thrown
private void checkUniqueName(final SebClientConfig sebClientConfig) {
final Long otherWithSameName = this.sebClientConfigRecordMapper
.countByExample()
.where(SebClientConfigRecordDynamicSqlSupport.name, isEqualTo(sebClientConfig.name))
.and(SebClientConfigRecordDynamicSqlSupport.institutionId, isEqualTo(sebClientConfig.institutionId))
.and(SebClientConfigRecordDynamicSqlSupport.id, isNotEqualToWhenPresent(sebClientConfig.id))
.build()
.execute();
if (otherWithSameName != null && otherWithSameName.longValue() > 0) {
throw new APIMessageException(APIMessage.fieldValidationError(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME,
"clientconfig:name:name.notunique"));
}
}
}
/*
* 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.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig.ConfigPurpose;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.SebClientConfigRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy
@Component
@WebServiceProfile
public class SebClientConfigDAOImpl implements SebClientConfigDAO {
private final SebClientConfigRecordMapper sebClientConfigRecordMapper;
private final ClientCredentialService clientCredentialService;
private final AdditionalAttributesDAOImpl additionalAttributesDAO;
protected SebClientConfigDAOImpl(
final SebClientConfigRecordMapper sebClientConfigRecordMapper,
final ClientCredentialService clientCredentialService,
final AdditionalAttributesDAOImpl additionalAttributesDAO) {
this.sebClientConfigRecordMapper = sebClientConfigRecordMapper;
this.clientCredentialService = clientCredentialService;
this.additionalAttributesDAO = additionalAttributesDAO;
}
@Override
public EntityType entityType() {
return EntityType.SEB_CLIENT_CONFIGURATION;
}
@Override
@Transactional(readOnly = true)
public Result<SebClientConfig> byPK(final Long id) {
return recordById(id)
.flatMap(this::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<SebClientConfig>> all(final Long institutionId, final Boolean active) {
return Result.tryCatch(() -> {
final List<SebClientConfigRecord> records = (active != null)
? this.sebClientConfigRecordMapper.selectByExample()
.where(
SebClientConfigRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(institutionId))
.and(
SebClientConfigRecordDynamicSqlSupport.active,
isEqualToWhenPresent(BooleanUtils.toIntegerObject(active)))
.build()
.execute()
: this.sebClientConfigRecordMapper.selectByExample().build().execute();
return records.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<SebClientConfig>> allMatching(
final FilterMap filterMap,
final Predicate<SebClientConfig> predicate) {
return Result.tryCatch(() -> this.sebClientConfigRecordMapper
.selectByExample()
.where(
SebClientConfigRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
SebClientConfigRecordDynamicSqlSupport.name,
isLikeWhenPresent(filterMap.getName()))
.and(
SebClientConfigRecordDynamicSqlSupport.date,
isGreaterThanOrEqualToWhenPresent(filterMap.getSebClientConfigFromTime()))
.and(
SebClientConfigRecordDynamicSqlSupport.active,
isEqualToWhenPresent(filterMap.getActiveAsInt()))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
}
@Override
public Result<SebClientConfig> byClientName(final String clientName) {
return Result.tryCatch(() -> this.sebClientConfigRecordMapper
.selectByExample()
.where(
SebClientConfigRecordDynamicSqlSupport.clientName,
isEqualTo(clientName))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Utils.toSingleton()));
}
@Override
@Transactional(readOnly = true)
public Result<CharSequence> getConfigPasswordCipherByClientName(final String clientName) {
return Result.tryCatch(() -> {
final SebClientConfigRecord record = this.sebClientConfigRecordMapper
.selectByExample()
.where(
SebClientConfigRecordDynamicSqlSupport.clientName,
isEqualTo(clientName))
.and(
SebClientConfigRecordDynamicSqlSupport.active,
isNotEqualTo(0))
.build()
.execute()
.stream()
.collect(Utils.toSingleton());
return record.getClientSecret();
});
}
@Override
@Transactional(readOnly = true)
public boolean isActive(final String modelId) {
if (StringUtils.isBlank(modelId)) {
return false;
}
return this.sebClientConfigRecordMapper.countByExample()
.where(SebClientConfigRecordDynamicSqlSupport.id, isEqualTo(Long.valueOf(modelId)))
.and(SebClientConfigRecordDynamicSqlSupport.active, isEqualTo(BooleanUtils.toInteger(true)))
.build()
.execute() > 0;
}
@Override
@Transactional
public Result<Collection<EntityKey>> setActive(final Set<EntityKey> all, final boolean active) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
final SebClientConfigRecord record = new SebClientConfigRecord(
null, null, null, null, null, null, null,
BooleanUtils.toIntegerObject(active));
this.sebClientConfigRecordMapper.updateByExampleSelective(record)
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids))
.build()
.execute();
return ids.stream()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.collect(Collectors.toList());
});
}
@Override
@Transactional
public Result<SebClientConfig> createNew(final SebClientConfig sebClientConfig) {
return this.clientCredentialService
.generatedClientCredentials()
.map(cc -> {
checkUniqueName(sebClientConfig);
final SebClientConfigRecord newRecord = new SebClientConfigRecord(
null,
sebClientConfig.institutionId,
sebClientConfig.name,
DateTime.now(DateTimeZone.UTC),
cc.clientIdAsString(),
cc.secretAsString(),
getEncryptionPassword(sebClientConfig),
BooleanUtils.toInteger(BooleanUtils.isTrue(sebClientConfig.active)));
this.sebClientConfigRecordMapper
.insert(newRecord);
saveAdditionalAttributes(sebClientConfig, newRecord.getId());
return newRecord;
})
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<SebClientConfig> save(final SebClientConfig sebClientConfig) {
return Result.tryCatch(() -> {
checkUniqueName(sebClientConfig);
final SebClientConfigRecord newRecord = new SebClientConfigRecord(
sebClientConfig.id,
null,
sebClientConfig.name,
null,
null,
null,
getEncryptionPassword(sebClientConfig),
null);
this.sebClientConfigRecordMapper
.updateByPrimaryKeySelective(newRecord);
saveAdditionalAttributes(sebClientConfig, newRecord.getId());
return this.sebClientConfigRecordMapper
.selectByPrimaryKey(sebClientConfig.id);
})
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
this.sebClientConfigRecordMapper.deleteByExample()
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids))
.build()
.execute();
return ids.stream()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<SebClientConfig>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> this.sebClientConfigRecordMapper.selectByExample()
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
// all of institution
if (bulkAction.sourceType == EntityType.INSTITUTION) {
return getDependencies(bulkAction, this::allIdsOfInstitution);
}
return Collections.emptySet();
}
@Override
@Transactional(readOnly = true)
public Result<ClientCredentials> getSebClientCredentials(final String modelId) {
return recordByModelId(modelId)
.map(rec -> new ClientCredentials(
rec.getClientName(),
rec.getClientSecret(),
null));
}
@Override
@Transactional(readOnly = true)
public Result<CharSequence> getConfigPasswordCipher(final String modelId) {
return recordByModelId(modelId)
.map(SebClientConfigRecord::getEncryptSecret);
}
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
return Result.tryCatch(() -> this.sebClientConfigRecordMapper.selectIdsByExample()
.where(SebClientConfigRecordDynamicSqlSupport.institutionId,
isEqualTo(Long.valueOf(institutionKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.collect(Collectors.toList()));
}
private Result<SebClientConfigRecord> recordByModelId(final String modelId) {
return Result.tryCatch(() -> recordById(Long.parseLong(modelId)).getOrThrow());
}
private Result<SebClientConfigRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
final SebClientConfigRecord record = this.sebClientConfigRecordMapper
.selectByPrimaryKey(id);
if (record == null) {
throw new ResourceNotFoundException(
EntityType.SEB_CLIENT_CONFIGURATION,
String.valueOf(id));
}
return record;
});
}
private Result<SebClientConfig> toDomainModel(final SebClientConfigRecord record) {
Map<String, AdditionalAttributeRecord> additionalAttributes = this.additionalAttributesDAO
.getAdditionalAttributes(
EntityType.SEB_CLIENT_CONFIGURATION,
record.getId())
.getOrThrow()
.stream()
.collect(Collectors.toMap(
AdditionalAttributeRecord::getName,
Function.identity()));
additionalAttributes.get(SebClientConfig.ATTR_CONFIG_PURPOSE);
return Result.tryCatch(() -> new SebClientConfig(
record.getId(),
record.getInstitutionId(),
record.getName(),
additionalAttributes.containsKey(SebClientConfig.ATTR_CONFIG_PURPOSE)
? ConfigPurpose.valueOf(additionalAttributes.get(SebClientConfig.ATTR_CONFIG_PURPOSE).getValue())
: ConfigPurpose.START_EXAM,
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK) &&
BooleanUtils.toBoolean(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK).getValue()),
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_START_URL)
? additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_START_URL).getValue()
: null,
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_TIMEOUT)
? Long.parseLong(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_TIMEOUT).getValue())
: null,
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_ATTEMPTS)
? Short.parseShort(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_ATTEMPTS).getValue())
: null,
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL)
? Short.parseShort(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL).getValue())
: null,
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_PASSWORD)
? additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_PASSWORD).getValue()
: null,
null,
additionalAttributes.containsKey(SebClientConfig.ATTR_QUIT_PASSWORD)
? additionalAttributes.get(SebClientConfig.ATTR_QUIT_PASSWORD).getValue()
: null,
null,
record.getDate(),
record.getEncryptSecret(),
null,
BooleanUtils.toBooleanObject(record.getActive())));
}
private String getEncryptionPassword(final SebClientConfig sebClientConfig) {
if (sebClientConfig.hasEncryptionSecret() &&
!sebClientConfig.encryptSecret.equals(sebClientConfig.encryptSecretConfirm)) {
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
}
final CharSequence encrypted_encrypt_secret = sebClientConfig.hasEncryptionSecret()
? this.clientCredentialService.encrypt(sebClientConfig.encryptSecret)
: null;
return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null;
}
// check if same name already exists for the same institution
// if true an APIMessageException with a field validation error is thrown
private void checkUniqueName(final SebClientConfig sebClientConfig) {
final Long otherWithSameName = this.sebClientConfigRecordMapper
.countByExample()
.where(SebClientConfigRecordDynamicSqlSupport.name, isEqualTo(sebClientConfig.name))
.and(SebClientConfigRecordDynamicSqlSupport.institutionId, isEqualTo(sebClientConfig.institutionId))
.and(SebClientConfigRecordDynamicSqlSupport.id, isNotEqualToWhenPresent(sebClientConfig.id))
.build()
.execute();
if (otherWithSameName != null && otherWithSameName > 0) {
throw new APIMessageException(APIMessage.fieldValidationError(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME,
"clientconfig:name:name.notunique"));
}
}
private void saveAdditionalAttributes(SebClientConfig sebClientConfig, Long configId) {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SebClientConfig.ATTR_CONFIG_PURPOSE,
(sebClientConfig.configPurpose != null)
? sebClientConfig.configPurpose.name()
: ConfigPurpose.START_EXAM.name());
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SebClientConfig.ATTR_FALLBACK,
String.valueOf(BooleanUtils.isTrue(sebClientConfig.fallback)));
if (BooleanUtils.isTrue(sebClientConfig.fallback)) {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SebClientConfig.ATTR_FALLBACK_START_URL,
sebClientConfig.fallbackStartURL);
} else {
this.additionalAttributesDAO.delete(
configId,
SebClientConfig.ATTR_FALLBACK_START_URL);
}
if (BooleanUtils.isTrue(sebClientConfig.fallback)) {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
sebClientConfig.fallbackTimeout.toString());
} else {
this.additionalAttributesDAO.delete(
configId,
SebClientConfig.ATTR_FALLBACK_TIMEOUT);
}
if (BooleanUtils.isTrue(sebClientConfig.fallback)) {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
sebClientConfig.fallbackAttempts.toString());
} else {
this.additionalAttributesDAO.delete(
configId,
SebClientConfig.ATTR_FALLBACK_ATTEMPTS);
}
if (BooleanUtils.isTrue(sebClientConfig.fallback)) {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
sebClientConfig.fallbackAttemptInterval.toString());
} else {
this.additionalAttributesDAO.delete(
configId,
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL);
}
if (BooleanUtils.isTrue(sebClientConfig.fallback) && StringUtils.isNotBlank(sebClientConfig.fallbackPassword)) {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SebClientConfig.ATTR_FALLBACK_PASSWORD,
this.clientCredentialService.encrypt(sebClientConfig.fallbackPassword).toString());
} else {
this.additionalAttributesDAO.delete(
configId,
SebClientConfig.ATTR_FALLBACK_PASSWORD);
}
if (BooleanUtils.isTrue(sebClientConfig.fallback) && StringUtils.isNotBlank(sebClientConfig.quitPassword)) {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SebClientConfig.ATTR_QUIT_PASSWORD,
this.clientCredentialService.encrypt(sebClientConfig.quitPassword).toString());
} else {
this.additionalAttributesDAO.delete(
configId,
SebClientConfig.ATTR_QUIT_PASSWORD);
}
}
}

View file

@ -1,93 +1,69 @@
/*
* 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 java.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.event.EventListener;
import org.springframework.security.oauth2.provider.ClientDetails;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent;
public interface ClientConfigService {
Logger log = LoggerFactory.getLogger(ClientConfigService.class);
public static final String EXAM_CLIENT_DETAILS_CACHE = "EXAM_CLIENT_DETAILS_CACHE";
static String SEB_CLIENT_CONFIG_EXAMPLE_XML =
" <dict>\r\n" +
" <key>sebMode</key>\r\n" +
" <integer>1</integer>\r\n" +
" <key>sebConfigPurpose</key>\r\n" +
" <integer>1</integer>\r\n" +
" <key>sebServerFallback</key>\r\n" +
" <%s />\r\n" +
" %s" +
" <key>sebServerURL</key>\r\n" +
" <string>%s</string>\r\n" +
" <key>sebServerConfiguration</key>\r\n" +
" <dict>\r\n" +
" <key>institution</key>\r\n" +
" <string>%s</string>\r\n" +
" <key>clientName</key>\r\n" +
" <string>%s</string>\r\n" +
" <key>clientSecret</key>\r\n" +
" <string>%s</string>\r\n" +
" <key>apiDiscovery</key>\r\n" +
" <string>%s</string>\r\n" +
" </dict>\r\n" +
" </dict>\r\n";
/** Indicates if there is any SebClientConfiguration for a specified institution.
*
* @param institutionId the institution identifier
* @return true if there is any SebClientConfiguration for a specified institution. False otherwise */
boolean hasSebClientConfigurationForInstitution(Long institutionId);
/** Use this to auto-generate a SebClientConfiguration for a specified institution.
* clientName and clientSecret are randomly generated.
*
* @param institutionId the institution identifier
* @return the created SebClientConfig */
Result<SebClientConfig> autoCreateSebClientConfigurationForInstitution(Long institutionId);
/** Use this to export a specified SebClientConfiguration within a given OutputStream.
* The SEB Client Configuration is exported in the defined SEB Configuration format
* as described here: https://www.safeexambrowser.org/developer/seb-file-format.html
*
* @param out OutputStream to write the export to
* @param modelId the model identifier of the SebClientConfiguration to export */
void exportSebClientConfiguration(
OutputStream out,
final String modelId);
/** Get the ClientDetails for given client name that identifies a SebClientConfig entry.
*
* @param clientName the client name of a SebClientConfig entry
* @return Result refer to the ClientDetails for the specified clientName or to an error if happened */
@Cacheable(
cacheNames = EXAM_CLIENT_DETAILS_CACHE,
key = "#clientName",
unless = "#result.hasError()")
Result<ClientDetails> getClientConfigDetails(String clientName);
@CacheEvict(
cacheNames = EXAM_CLIENT_DETAILS_CACHE,
allEntries = true)
@EventListener(BulkActionEvent.class)
void flushClientConfigData(BulkActionEvent event);
}
/*
* 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 java.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.event.EventListener;
import org.springframework.security.oauth2.provider.ClientDetails;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent;
public interface ClientConfigService {
Logger log = LoggerFactory.getLogger(ClientConfigService.class);
String EXAM_CLIENT_DETAILS_CACHE = "EXAM_CLIENT_DETAILS_CACHE";
/** Indicates if there is any SebClientConfiguration for a specified institution.
*
* @param institutionId the institution identifier
* @return true if there is any SebClientConfiguration for a specified institution. False otherwise */
boolean hasSebClientConfigurationForInstitution(Long institutionId);
/** Use this to auto-generate a SebClientConfiguration for a specified institution.
* clientName and clientSecret are randomly generated.
*
* @param institutionId the institution identifier
* @return the created SebClientConfig */
Result<SebClientConfig> autoCreateSebClientConfigurationForInstitution(Long institutionId);
/** Use this to export a specified SebClientConfiguration within a given OutputStream.
* The SEB Client Configuration is exported in the defined SEB Configuration format
* as described here: https://www.safeexambrowser.org/developer/seb-file-format.html
*
* @param out OutputStream to write the export to
* @param modelId the model identifier of the SebClientConfiguration to export */
void exportSebClientConfiguration(
OutputStream out,
final String modelId);
/** Get the ClientDetails for given client name that identifies a SebClientConfig entry.
*
* @param clientName the client name of a SebClientConfig entry
* @return Result refer to the ClientDetails for the specified clientName or to an error if happened */
@Cacheable(
cacheNames = EXAM_CLIENT_DETAILS_CACHE,
key = "#clientName",
unless = "#result.hasError()")
Result<ClientDetails> getClientConfigDetails(String clientName);
@CacheEvict(
cacheNames = EXAM_CLIENT_DETAILS_CACHE,
allEntries = true)
@EventListener(BulkActionEvent.class)
void flushClientConfigData(BulkActionEvent event);
}

View file

@ -1,314 +1,377 @@
/*
* 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;
import java.io.IOException;
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.Collections;
import java.util.UUID;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.WebSecurityConfig;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebConfigEncryptionServiceImpl.EncryptionContext;
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration;
@Lazy
@Service
@WebServiceProfile
public class ClientConfigServiceImpl implements ClientConfigService {
private static final Logger log = LoggerFactory.getLogger(ClientConfigServiceImpl.class);
private final InstitutionDAO institutionDAO;
private final SebClientConfigDAO sebClientConfigDAO;
private final ClientCredentialService clientCredentialService;
private final SebConfigEncryptionService sebConfigEncryptionService;
private final PasswordEncoder clientPasswordEncoder;
private final ZipService zipService;
private final TokenStore tokenStore;
private final WebserviceInfo webserviceInfo;
protected ClientConfigServiceImpl(
final InstitutionDAO institutionDAO,
final SebClientConfigDAO sebClientConfigDAO,
final ClientCredentialService clientCredentialService,
final SebConfigEncryptionService sebConfigEncryptionService,
final ZipService zipService,
final TokenStore tokenStore,
@Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder clientPasswordEncoder,
final WebserviceInfo webserviceInfo) {
this.institutionDAO = institutionDAO;
this.sebClientConfigDAO = sebClientConfigDAO;
this.clientCredentialService = clientCredentialService;
this.sebConfigEncryptionService = sebConfigEncryptionService;
this.zipService = zipService;
this.clientPasswordEncoder = clientPasswordEncoder;
this.tokenStore = tokenStore;
this.webserviceInfo = webserviceInfo;
}
@Override
public boolean hasSebClientConfigurationForInstitution(final Long institutionId) {
final Result<Collection<SebClientConfig>> all = this.sebClientConfigDAO.all(institutionId, true);
return all != null && !all.hasError() && !all.getOrThrow().isEmpty();
}
@Override
public Result<SebClientConfig> autoCreateSebClientConfigurationForInstitution(final Long institutionId) {
return Result.tryCatch(() -> {
final Institution institution = this.institutionDAO
.byPK(institutionId)
.getOrThrow();
return new SebClientConfig(
null,
institutionId,
institution.name + "_" + UUID.randomUUID(),
null,
null,
null,
null,
true);
})
.flatMap(this.sebClientConfigDAO::createNew);
}
@Override
public Result<ClientDetails> getClientConfigDetails(final String clientName) {
return this.getEncodedClientConfigSecret(clientName)
.map(pwd -> {
final BaseClientDetails baseClientDetails = new BaseClientDetails(
Utils.toString(clientName),
WebserviceResourceConfiguration.EXAM_API_RESOURCE_ID,
null,
Constants.OAUTH2_GRANT_TYPE_CLIENT_CREDENTIALS,
StringUtils.EMPTY);
baseClientDetails.setScope(Collections.emptySet());
baseClientDetails.setClientSecret(Utils.toString(pwd));
baseClientDetails.setAccessTokenValiditySeconds(-1); // not expiring
if (log.isDebugEnabled()) {
log.debug("Created new BaseClientDetails for id: {}", clientName);
}
return baseClientDetails;
});
}
@Override
public void exportSebClientConfiguration(
final OutputStream output,
final String modelId) {
final SebClientConfig config = this.sebClientConfigDAO
.byModelId(modelId).getOrThrow();
final CharSequence encryptionPassword = this.sebClientConfigDAO
.getConfigPasswortCipher(config.getModelId())
.getOr(null);
final String plainTextConfig = getPlainXMLConfig(config);
PipedOutputStream pOut = null;
PipedInputStream pIn = null;
try {
// zip the plain text
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);
this.zipService.write(pOut, plainIn);
if (encryptionPassword != null) {
passwordEncryption(output, encryptionPassword, pIn);
} else {
this.sebConfigEncryptionService.streamEncrypted(
output,
pIn,
EncryptionContext.contextOfPlainText());
}
if (log.isDebugEnabled()) {
log.debug("*** Finished Seb client configuration download streaming composition");
}
} catch (final Exception e) {
log.error("Error while zip and encrypt seb client config stream: ", e);
try {
if (pIn != null) {
pIn.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1);
}
try {
if (pOut != null) {
pOut.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
}
}
}
public String getPlainXMLConfig(final SebClientConfig config) {
final ClientCredentials sebClientCredentials = this.sebClientConfigDAO
.getSebClientCredentials(config.getModelId())
.getOrThrow();
final CharSequence plainClientId = sebClientCredentials.clientId;
final CharSequence plainClientSecret = this.clientCredentialService
.getPlainClientSecret(sebClientCredentials);
final String plainTextConfig = extractXML(
config,
plainClientId,
plainClientSecret);
return plainTextConfig;
}
private String extractXML(
final SebClientConfig config,
final CharSequence plainClientId,
final CharSequence plainClientSecret) {
final String plainTextConfig = String.format(
SEB_CLIENT_CONFIG_EXAMPLE_XML,
(StringUtils.isNotBlank(config.fallbackStartURL))
? "true"
: "false",
(StringUtils.isNotBlank(config.fallbackStartURL))
? "<key>startURL</key>\r\n <string>" + config.fallbackStartURL + "</string>\r\n"
: "",
this.webserviceInfo.getExternalServerURL(),
String.valueOf(config.institutionId),
plainClientId,
plainClientSecret,
this.webserviceInfo.getDiscoveryEndpoint());
if (log.isDebugEnabled()) {
log.debug("SEB client configuration export:\n {}", plainTextConfig);
}
return plainTextConfig;
}
@Override
public void flushClientConfigData(final BulkActionEvent event) {
try {
final BulkAction bulkAction = event.getBulkAction();
if (bulkAction.type == BulkActionType.DEACTIVATE ||
bulkAction.type == BulkActionType.HARD_DELETE) {
bulkAction.extractKeys(EntityType.SEB_CLIENT_CONFIGURATION)
.stream()
.forEach(this::flushClientConfigData);
}
} catch (final Exception e) {
log.error("Unexpected error while trying to flush ClientConfig data ", e);
}
}
private void flushClientConfigData(final EntityKey key) {
try {
final String clientName = this.sebClientConfigDAO.getSebClientCredentials(key.modelId)
.getOrThrow()
.clientIdAsString();
final Collection<OAuth2AccessToken> tokensByClientId = this.tokenStore.findTokensByClientId(clientName);
tokensByClientId.stream()
.forEach(token -> this.tokenStore.removeAccessToken(token));
} catch (final Exception e) {
log.error("Unexpected error while trying to flush ClientConfig data for {}", key, e);
}
}
private void passwordEncryption(
final OutputStream output,
final CharSequence encryptionPassword,
final InputStream input) {
if (log.isDebugEnabled()) {
log.debug("*** Seb client configuration with password based encryption");
}
final CharSequence encryptionPasswordPlaintext = this.clientCredentialService
.decrypt(encryptionPassword);
this.sebConfigEncryptionService.streamEncrypted(
output,
input,
EncryptionContext.contextOf(
Strategy.PASSWORD_PSWD,
encryptionPasswordPlaintext));
}
/** Get a encoded clientSecret for the SebClientConfiguration with specified clientId/clientName.
*
* @param clientId the clientId/clientName
* @return encoded clientSecret for that SebClientConfiguration with clientId or null of not existing */
private Result<CharSequence> getEncodedClientConfigSecret(final String clientId) {
return this.sebClientConfigDAO.getConfigPasswortCipherByClientName(clientId)
.map(cipher -> this.clientPasswordEncoder.encode(this.clientCredentialService.decrypt(cipher)));
}
}
/*
* 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;
import ch.ethz.seb.sebserver.WebSecurityConfig;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebConfigEncryptionServiceImpl.EncryptionContext;
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import java.io.IOException;
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.Collections;
import java.util.UUID;
@Lazy
@Service
@WebServiceProfile
public class ClientConfigServiceImpl implements ClientConfigService {
private static final Logger log = LoggerFactory.getLogger(ClientConfigServiceImpl.class);
private static final String SEB_CLIENT_CONFIG_TEMPLATE_XML =
" <dict>\r\n" +
" <key>sebMode</key>\r\n" +
" <integer>1</integer>\r\n" +
" <key>sebConfigPurpose</key>\r\n" +
" <integer>%s</integer>\r\n" +
" <key>sebServerFallback</key>\r\n" +
" <%s />\r\n" +
"%s" +
" <key>sebServerURL</key>\r\n" +
" <string>%s</string>\r\n" +
" <key>sebServerConfiguration</key>\r\n" +
" <dict>\r\n" +
" <key>institution</key>\r\n" +
" <string>%s</string>\r\n" +
" <key>clientName</key>\r\n" +
" <string>%s</string>\r\n" +
" <key>clientSecret</key>\r\n" +
" <string>%s</string>\r\n" +
" <key>apiDiscovery</key>\r\n" +
" <string>%s</string>\r\n" +
" </dict>\r\n" +
" </dict>\r\n";
private final static String SEB_CLIENT_CONFIG_INTEGER_TEMPLATE =
" <key>%s</key>\r\n" +
" <integer>%s</integer>\r\n";
private final static String SEB_CLIENT_CONFIG_STRING_TEMPLATE =
" <key>%s</key>\r\n" +
" <string>%s</string>\r\n";
private final InstitutionDAO institutionDAO;
private final SebClientConfigDAO sebClientConfigDAO;
private final ClientCredentialService clientCredentialService;
private final SebConfigEncryptionService sebConfigEncryptionService;
private final PasswordEncoder clientPasswordEncoder;
private final ZipService zipService;
private final TokenStore tokenStore;
private final WebserviceInfo webserviceInfo;
protected ClientConfigServiceImpl(
final InstitutionDAO institutionDAO,
final SebClientConfigDAO sebClientConfigDAO,
final ClientCredentialService clientCredentialService,
final SebConfigEncryptionService sebConfigEncryptionService,
final ZipService zipService,
final TokenStore tokenStore,
@Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder clientPasswordEncoder,
final WebserviceInfo webserviceInfo) {
this.institutionDAO = institutionDAO;
this.sebClientConfigDAO = sebClientConfigDAO;
this.clientCredentialService = clientCredentialService;
this.sebConfigEncryptionService = sebConfigEncryptionService;
this.zipService = zipService;
this.clientPasswordEncoder = clientPasswordEncoder;
this.tokenStore = tokenStore;
this.webserviceInfo = webserviceInfo;
}
@Override
public boolean hasSebClientConfigurationForInstitution(final Long institutionId) {
final Result<Collection<SebClientConfig>> all = this.sebClientConfigDAO.all(institutionId, true);
return all != null && !all.hasError() && !all.getOrThrow().isEmpty();
}
@Override
public Result<SebClientConfig> autoCreateSebClientConfigurationForInstitution(final Long institutionId) {
return Result.tryCatch(() -> {
final Institution institution = this.institutionDAO
.byPK(institutionId)
.getOrThrow();
return new SebClientConfig(
null,
institutionId,
institution.name + "_" + UUID.randomUUID(),
null,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true);
})
.flatMap(this.sebClientConfigDAO::createNew);
}
@Override
public Result<ClientDetails> getClientConfigDetails(final String clientName) {
return this.getEncodedClientConfigSecret(clientName)
.map(pwd -> {
final BaseClientDetails baseClientDetails = new BaseClientDetails(
Utils.toString(clientName),
WebserviceResourceConfiguration.EXAM_API_RESOURCE_ID,
null,
Constants.OAUTH2_GRANT_TYPE_CLIENT_CREDENTIALS,
StringUtils.EMPTY);
baseClientDetails.setScope(Collections.emptySet());
baseClientDetails.setClientSecret(Utils.toString(pwd));
baseClientDetails.setAccessTokenValiditySeconds(-1); // not expiring
if (log.isDebugEnabled()) {
log.debug("Created new BaseClientDetails for id: {}", clientName);
}
return baseClientDetails;
});
}
@Override
public void exportSebClientConfiguration(
final OutputStream output,
final String modelId) {
final SebClientConfig config = this.sebClientConfigDAO
.byModelId(modelId).getOrThrow();
final CharSequence encryptionPassword = this.sebClientConfigDAO
.getConfigPasswordCipher(config.getModelId())
.getOr(null);
final String plainTextXMLContent = extractXMLContent(config);
PipedOutputStream pOut = null;
PipedInputStream pIn = null;
try {
// zip the plain text
final InputStream plainIn = IOUtils.toInputStream(
Constants.XML_VERSION_HEADER +
Constants.XML_DOCTYPE_HEADER +
Constants.XML_PLIST_START_V1 +
plainTextXMLContent +
Constants.XML_PLIST_END,
StandardCharsets.UTF_8.name());
pOut = new PipedOutputStream();
pIn = new PipedInputStream(pOut);
this.zipService.write(pOut, plainIn);
if (encryptionPassword != null) {
passwordEncryption(output, encryptionPassword, pIn);
} else {
this.sebConfigEncryptionService.streamEncrypted(
output,
pIn,
EncryptionContext.contextOfPlainText());
}
if (log.isDebugEnabled()) {
log.debug("*** Finished Seb client configuration download streaming composition");
}
} catch (final Exception e) {
log.error("Error while zip and encrypt seb client config stream: ", e);
try {
if (pIn != null) {
pIn.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1);
}
try {
if (pOut != null) {
pOut.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
}
}
}
private String extractXMLContent(final SebClientConfig config) {
String fallbackAddition = "";
if (BooleanUtils.isTrue(config.fallback)) {
fallbackAddition += String.format(
SEB_CLIENT_CONFIG_STRING_TEMPLATE,
SebClientConfig.ATTR_FALLBACK_START_URL,
config.fallbackStartURL);
fallbackAddition += String.format(
SEB_CLIENT_CONFIG_INTEGER_TEMPLATE,
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
config.fallbackTimeout);
fallbackAddition += String.format(
SEB_CLIENT_CONFIG_INTEGER_TEMPLATE,
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
config.fallbackAttempts);
fallbackAddition += String.format(
SEB_CLIENT_CONFIG_INTEGER_TEMPLATE,
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
config.fallbackAttemptInterval);
if (StringUtils.isNotBlank(config.fallbackPassword)) {
CharSequence decrypt = clientCredentialService.decrypt(config.fallbackPassword);
fallbackAddition += String.format(
SEB_CLIENT_CONFIG_STRING_TEMPLATE,
SebClientConfig.ATTR_FALLBACK_PASSWORD,
Utils.hash_SHA_256_Base_16(decrypt));
}
if (StringUtils.isNotBlank(config.quitPassword)) {
CharSequence decrypt = clientCredentialService.decrypt(config.quitPassword);
fallbackAddition += String.format(
SEB_CLIENT_CONFIG_STRING_TEMPLATE,
SebClientConfig.ATTR_QUIT_PASSWORD,
Utils.hash_SHA_256_Base_16(decrypt));
}
}
final ClientCredentials sebClientCredentials = this.sebClientConfigDAO
.getSebClientCredentials(config.getModelId())
.getOrThrow();
final CharSequence plainClientId = sebClientCredentials.clientId;
final CharSequence plainClientSecret = this.clientCredentialService
.getPlainClientSecret(sebClientCredentials);
final String plainTextConfig = String.format(
SEB_CLIENT_CONFIG_TEMPLATE_XML,
config.configPurpose.ordinal(),
(StringUtils.isNotBlank(config.fallbackStartURL))
? "true"
: "false",
fallbackAddition,
this.webserviceInfo.getExternalServerURL(),
config.institutionId,
plainClientId,
plainClientSecret,
this.webserviceInfo.getDiscoveryEndpoint());
if (log.isDebugEnabled()) {
log.debug("SEB client configuration export:\n {}", plainTextConfig);
}
return plainTextConfig;
}
@Override
public void flushClientConfigData(final BulkActionEvent event) {
try {
final BulkAction bulkAction = event.getBulkAction();
if (bulkAction.type == BulkActionType.DEACTIVATE ||
bulkAction.type == BulkActionType.HARD_DELETE) {
bulkAction.extractKeys(EntityType.SEB_CLIENT_CONFIGURATION)
.forEach(this::flushClientConfigData);
}
} catch (final Exception e) {
log.error("Unexpected error while trying to flush ClientConfig data ", e);
}
}
private void flushClientConfigData(final EntityKey key) {
try {
final String clientName = this.sebClientConfigDAO.getSebClientCredentials(key.modelId)
.getOrThrow()
.clientIdAsString();
final Collection<OAuth2AccessToken> tokensByClientId = this.tokenStore.findTokensByClientId(clientName);
tokensByClientId.forEach(this.tokenStore::removeAccessToken);
} catch (final Exception e) {
log.error("Unexpected error while trying to flush ClientConfig data for {}", key, e);
}
}
private void passwordEncryption(
final OutputStream output,
final CharSequence encryptionPassword,
final InputStream input) {
if (log.isDebugEnabled()) {
log.debug("*** Seb client configuration with password based encryption");
}
final CharSequence encryptionPasswordPlaintext = this.clientCredentialService
.decrypt(encryptionPassword);
this.sebConfigEncryptionService.streamEncrypted(
output,
input,
EncryptionContext.contextOf(
Strategy.PASSWORD_PSWD,
encryptionPasswordPlaintext));
}
/** Get a encoded clientSecret for the SebClientConfiguration with specified clientId/clientName.
*
* @param clientId the clientId/clientName
* @return encoded clientSecret for that SebClientConfiguration with clientId or null of not existing */
private Result<CharSequence> getEncodedClientConfigSecret(final String clientId) {
return this.sebClientConfigDAO.getConfigPasswordCipherByClientName(clientId)
.map(cipher -> this.clientPasswordEncoder.encode(this.clientCredentialService.decrypt(cipher)));
}
}

View file

@ -1,313 +1,318 @@
/*
* 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;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.SequenceInputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
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.ConfigurationDAO;
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.AttributeValueConverter;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverterService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
@Lazy
@Component
@WebServiceProfile
public class ExamConfigIO {
private static final Logger log = LoggerFactory.getLogger(ExamConfigIO.class);
private static final byte[] XML_VERSION_HEADER_UTF_8 = Utils.toByteArray(Constants.XML_VERSION_HEADER);
private static final byte[] XML_DOCTYPE_HEADER_UTF_8 = Utils.toByteArray(Constants.XML_DOCTYPE_HEADER);
private static final byte[] XML_PLIST_START_V1_UTF_8 = Utils.toByteArray(Constants.XML_PLIST_START_V1);
private static final byte[] XML_PLIST_END_UTF_8 = Utils.toByteArray(Constants.XML_PLIST_END);
private static final byte[] XML_DICT_START_UTF_8 = Utils.toByteArray(Constants.XML_DICT_START);
private static final byte[] XML_DICT_END_UTF_8 = Utils.toByteArray(Constants.XML_DICT_END);
private static final byte[] JSON_START = Utils.toByteArray("{");
private static final byte[] JSON_END = Utils.toByteArray("}");
private static final byte[] JSON_SEPARATOR = Utils.toByteArray(Constants.LIST_SEPARATOR);
private final ConfigurationAttributeDAO configurationAttributeDAO;
private final ConfigurationValueDAO configurationValueDAO;
private final ConfigurationDAO configurationDAO;
private final AttributeValueConverterService attributeValueConverterService;
private final ZipService zipService;
protected ExamConfigIO(
final ConfigurationAttributeDAO configurationAttributeDAO,
final ConfigurationValueDAO configurationValueDAO,
final ConfigurationDAO configurationDAO,
final AttributeValueConverterService attributeValueConverterService,
final ZipService zipService) {
this.configurationAttributeDAO = configurationAttributeDAO;
this.configurationValueDAO = configurationValueDAO;
this.configurationDAO = configurationDAO;
this.attributeValueConverterService = attributeValueConverterService;
this.zipService = zipService;
}
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
void exportPlain(
final ConfigurationFormat exportFormat,
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) throws Exception {
if (log.isDebugEnabled()) {
log.debug("Start export SEB plain XML configuration asynconously");
}
try {
// get all defined root configuration attributes prepared and sorted
final List<ConfigurationAttribute> sortedAttributes = this.configurationAttributeDAO.getAllRootAttributes()
.getOrThrow()
.stream()
.flatMap(this::convertAttribute)
.filter(exportFormatBasedAttributeFilter(exportFormat))
.sorted()
.collect(Collectors.toList());
// get follow-up configurationId for given configurationNodeId
final Long configurationId = this.configurationDAO
.getConfigurationLastStableVersion(configurationNodeId)
.getOrThrow().id;
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier =
getConfigurationValueSupplier(institutionId, configurationId);
writeHeader(exportFormat, out);
// write attributes
final Iterator<ConfigurationAttribute> iterator = sortedAttributes.iterator();
while (iterator.hasNext()) {
final ConfigurationAttribute attribute = iterator.next();
final AttributeValueConverter attributeValueConverter =
this.attributeValueConverterService.getAttributeValueConverter(attribute);
switch (exportFormat) {
case XML: {
attributeValueConverter.convertToXML(
out,
attribute,
configurationValueSupplier);
break;
}
case JSON: {
attributeValueConverter.convertToJSON(
out,
attribute,
configurationValueSupplier);
if (iterator.hasNext()) {
out.write(JSON_SEPARATOR);
}
break;
}
}
}
writeFooter(exportFormat, out);
if (log.isDebugEnabled()) {
log.debug("Finished export SEB plain XML configuration asynconously");
}
} catch (final Exception e) {
log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e);
throw e;
} finally {
try {
out.flush();
} catch (final IOException e1) {
log.error("Unable to flush output stream after error");
}
IOUtils.closeQuietly(out);
}
}
/** This parses the XML from given InputStream with a SAX parser to avoid keeping the
* whole XML file in memory and keep up with the streaming approach of SEB Exam Configuration
* to avoid trouble with big SEB Exam Configuration in the future.
*
* @param in The InputString to constantly read the XML from
* @param institutionId the institionId of the import
* @param configurationId the identifier of the internal configuration to apply the imported values to */
void importPlainXML(final InputStream in, final Long institutionId, final Long configurationId) {
try {
// get all attributes and map the names to ids
final Map<String, ConfigurationAttribute> attributeMap = this.configurationAttributeDAO
.allMatching(new FilterMap())
.getOrThrow()
.stream()
.collect(Collectors.toMap(
attr -> attr.name,
Function.identity()));
// the SAX handler with a ConfigValue sink that saves the values to DB
// and a attribute-name/id mapping function with pre-created mapping
final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser(
institutionId,
configurationId,
value -> this.configurationValueDAO
.save(value)
.getOrThrow(),
attributeMap::get);
// SAX parsing
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
final SAXParser parser = saxParserFactory.newSAXParser();
parser.parse(in, examConfigImportHandler);
} catch (final ParserConfigurationException e) {
log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
throw new RuntimeException(e);
} catch (final SAXException e) {
log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
throw new RuntimeException(e);
} catch (final IOException e) {
log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(in);
}
}
InputStream unzip(final InputStream input) throws Exception {
final byte[] zipHeader = new byte[Constants.GZIP_HEADER_LENGTH];
final int read = input.read(zipHeader);
if (read < Constants.GZIP_HEADER_LENGTH) {
throw new IllegalArgumentException("Failed to verify Zip type from input stream. Header size mismatch.");
}
final boolean isZipped = Byte.toUnsignedInt(zipHeader[0]) == Constants.GZIP_ID1
&& Byte.toUnsignedInt(zipHeader[1]) == Constants.GZIP_ID2
&& Byte.toUnsignedInt(zipHeader[2]) == Constants.GZIP_CM;
final InputStream sequencedInput = new SequenceInputStream(
new ByteArrayInputStream(zipHeader, 0, Constants.GZIP_HEADER_LENGTH),
input);
if (isZipped) {
final PipedInputStream pipedIn = new PipedInputStream();
final PipedOutputStream pipedOut = new PipedOutputStream(pipedIn);
this.zipService.read(pipedOut, sequencedInput);
return pipedIn;
} else {
return sequencedInput;
}
}
private Predicate<ConfigurationAttribute> exportFormatBasedAttributeFilter(final ConfigurationFormat format) {
// Filter originatorVersion according to: https://www.safeexambrowser.org/developer/seb-config-key.html
return attr -> !("originatorVersion".equals(attr.getName()) && format == ConfigurationFormat.JSON);
}
private void writeFooter(
final ConfigurationFormat exportFormat,
final OutputStream out) throws IOException {
if (exportFormat == ConfigurationFormat.XML) {
// plist close
out.write(XML_DICT_END_UTF_8);
out.write(XML_PLIST_END_UTF_8);
} else {
out.write(JSON_END);
}
}
private void writeHeader(
final ConfigurationFormat exportFormat,
final OutputStream out) throws IOException {
if (exportFormat == ConfigurationFormat.XML) {
writeXMLHeaderInformation(out);
} else {
writeJSONHeaderInformation(out);
}
}
private void writeJSONHeaderInformation(final OutputStream out) throws IOException {
out.write(JSON_START);
}
private void writeXMLHeaderInformation(final OutputStream out) throws IOException {
// write headers
out.write(XML_VERSION_HEADER_UTF_8);
out.write(XML_DOCTYPE_HEADER_UTF_8);
// plist open
out.write(XML_PLIST_START_V1_UTF_8);
out.write(XML_DICT_START_UTF_8);
}
private Stream<ConfigurationAttribute> convertAttribute(final ConfigurationAttribute attr) {
final AttributeValueConverter attributeValueConverter =
this.attributeValueConverterService.getAttributeValueConverter(attr);
if (attributeValueConverter != null) {
return attributeValueConverter.convertAttribute(attr);
} else {
return Stream.of(attr);
}
}
private Function<ConfigurationAttribute, ConfigurationValue> getConfigurationValueSupplier(
final Long institutionId,
final Long configurationId) {
final Map<Long, ConfigurationValue> mapping = this.configurationValueDAO
.allRootAttributeValues(institutionId, configurationId)
.getOrThrow()
.stream()
.collect(Collectors.toMap(
ConfigurationValue::getAttributeId,
Function.identity()));
return attr -> mapping.get(attr.id);
}
}
/*
* 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;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.SequenceInputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
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.ConfigurationDAO;
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.AttributeValueConverter;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverterService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
@Lazy
@Component
@WebServiceProfile
public class ExamConfigIO {
private static final Logger log = LoggerFactory.getLogger(ExamConfigIO.class);
private static final byte[] XML_VERSION_HEADER_UTF_8 = Utils.toByteArray(Constants.XML_VERSION_HEADER);
private static final byte[] XML_DOCTYPE_HEADER_UTF_8 = Utils.toByteArray(Constants.XML_DOCTYPE_HEADER);
private static final byte[] XML_PLIST_START_V1_UTF_8 = Utils.toByteArray(Constants.XML_PLIST_START_V1);
private static final byte[] XML_PLIST_END_UTF_8 = Utils.toByteArray(Constants.XML_PLIST_END);
private static final byte[] XML_DICT_START_UTF_8 = Utils.toByteArray(Constants.XML_DICT_START);
private static final byte[] XML_DICT_END_UTF_8 = Utils.toByteArray(Constants.XML_DICT_END);
private static final byte[] JSON_START = Utils.toByteArray("{");
private static final byte[] JSON_END = Utils.toByteArray("}");
private static final byte[] JSON_SEPARATOR = Utils.toByteArray(Constants.LIST_SEPARATOR);
private final ConfigurationAttributeDAO configurationAttributeDAO;
private final ConfigurationValueDAO configurationValueDAO;
private final ConfigurationDAO configurationDAO;
private final AttributeValueConverterService attributeValueConverterService;
private final ZipService zipService;
private final Cryptor cryptor;
protected ExamConfigIO(
final ConfigurationAttributeDAO configurationAttributeDAO,
final ConfigurationValueDAO configurationValueDAO,
final ConfigurationDAO configurationDAO,
final AttributeValueConverterService attributeValueConverterService,
final ZipService zipService,
final Cryptor cryptor) {
this.configurationAttributeDAO = configurationAttributeDAO;
this.configurationValueDAO = configurationValueDAO;
this.configurationDAO = configurationDAO;
this.attributeValueConverterService = attributeValueConverterService;
this.zipService = zipService;
this.cryptor = cryptor;
}
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
void exportPlain(
final ConfigurationFormat exportFormat,
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) throws Exception {
if (log.isDebugEnabled()) {
log.debug("Start export SEB plain XML configuration asynconously");
}
try {
// get all defined root configuration attributes prepared and sorted
final List<ConfigurationAttribute> sortedAttributes = this.configurationAttributeDAO.getAllRootAttributes()
.getOrThrow()
.stream()
.flatMap(this::convertAttribute)
.filter(exportFormatBasedAttributeFilter(exportFormat))
.sorted()
.collect(Collectors.toList());
// get follow-up configurationId for given configurationNodeId
final Long configurationId = this.configurationDAO
.getConfigurationLastStableVersion(configurationNodeId)
.getOrThrow().id;
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier =
getConfigurationValueSupplier(institutionId, configurationId);
writeHeader(exportFormat, out);
// write attributes
final Iterator<ConfigurationAttribute> iterator = sortedAttributes.iterator();
while (iterator.hasNext()) {
final ConfigurationAttribute attribute = iterator.next();
final AttributeValueConverter attributeValueConverter =
this.attributeValueConverterService.getAttributeValueConverter(attribute);
switch (exportFormat) {
case XML: {
attributeValueConverter.convertToXML(
out,
attribute,
configurationValueSupplier);
break;
}
case JSON: {
attributeValueConverter.convertToJSON(
out,
attribute,
configurationValueSupplier);
if (iterator.hasNext()) {
out.write(JSON_SEPARATOR);
}
break;
}
}
}
writeFooter(exportFormat, out);
if (log.isDebugEnabled()) {
log.debug("Finished export SEB plain XML configuration asynconously");
}
} catch (final Exception e) {
log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e);
throw e;
} finally {
try {
out.flush();
} catch (final IOException e1) {
log.error("Unable to flush output stream after error");
}
IOUtils.closeQuietly(out);
}
}
/** This parses the XML from given InputStream with a SAX parser to avoid keeping the
* whole XML file in memory and keep up with the streaming approach of SEB Exam Configuration
* to avoid trouble with big SEB Exam Configuration in the future.
*
* @param in The InputString to constantly read the XML from
* @param institutionId the institionId of the import
* @param configurationId the identifier of the internal configuration to apply the imported values to */
void importPlainXML(final InputStream in, final Long institutionId, final Long configurationId) {
try {
// get all attributes and map the names to ids
final Map<String, ConfigurationAttribute> attributeMap = this.configurationAttributeDAO
.allMatching(new FilterMap())
.getOrThrow()
.stream()
.collect(Collectors.toMap(
attr -> attr.name,
Function.identity()));
// the SAX handler with a ConfigValue sink that saves the values to DB
// and a attribute-name/id mapping function with pre-created mapping
final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser(
cryptor,
institutionId,
configurationId,
value -> this.configurationValueDAO
.save(value)
.getOrThrow(),
attributeMap::get);
// SAX parsing
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
final SAXParser parser = saxParserFactory.newSAXParser();
parser.parse(in, examConfigImportHandler);
} catch (final ParserConfigurationException e) {
log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
throw new RuntimeException(e);
} catch (final SAXException e) {
log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
throw new RuntimeException(e);
} catch (final IOException e) {
log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(in);
}
}
InputStream unzip(final InputStream input) throws Exception {
final byte[] zipHeader = new byte[Constants.GZIP_HEADER_LENGTH];
final int read = input.read(zipHeader);
if (read < Constants.GZIP_HEADER_LENGTH) {
throw new IllegalArgumentException("Failed to verify Zip type from input stream. Header size mismatch.");
}
final boolean isZipped = Byte.toUnsignedInt(zipHeader[0]) == Constants.GZIP_ID1
&& Byte.toUnsignedInt(zipHeader[1]) == Constants.GZIP_ID2
&& Byte.toUnsignedInt(zipHeader[2]) == Constants.GZIP_CM;
final InputStream sequencedInput = new SequenceInputStream(
new ByteArrayInputStream(zipHeader, 0, Constants.GZIP_HEADER_LENGTH),
input);
if (isZipped) {
final PipedInputStream pipedIn = new PipedInputStream();
final PipedOutputStream pipedOut = new PipedOutputStream(pipedIn);
this.zipService.read(pipedOut, sequencedInput);
return pipedIn;
} else {
return sequencedInput;
}
}
private Predicate<ConfigurationAttribute> exportFormatBasedAttributeFilter(final ConfigurationFormat format) {
// Filter originatorVersion according to: https://www.safeexambrowser.org/developer/seb-config-key.html
return attr -> !("originatorVersion".equals(attr.getName()) && format == ConfigurationFormat.JSON);
}
private void writeFooter(
final ConfigurationFormat exportFormat,
final OutputStream out) throws IOException {
if (exportFormat == ConfigurationFormat.XML) {
// plist close
out.write(XML_DICT_END_UTF_8);
out.write(XML_PLIST_END_UTF_8);
} else {
out.write(JSON_END);
}
}
private void writeHeader(
final ConfigurationFormat exportFormat,
final OutputStream out) throws IOException {
if (exportFormat == ConfigurationFormat.XML) {
writeXMLHeaderInformation(out);
} else {
writeJSONHeaderInformation(out);
}
}
private void writeJSONHeaderInformation(final OutputStream out) throws IOException {
out.write(JSON_START);
}
private void writeXMLHeaderInformation(final OutputStream out) throws IOException {
// write headers
out.write(XML_VERSION_HEADER_UTF_8);
out.write(XML_DOCTYPE_HEADER_UTF_8);
// plist open
out.write(XML_PLIST_START_V1_UTF_8);
out.write(XML_DICT_START_UTF_8);
}
private Stream<ConfigurationAttribute> convertAttribute(final ConfigurationAttribute attr) {
final AttributeValueConverter attributeValueConverter =
this.attributeValueConverterService.getAttributeValueConverter(attr);
if (attributeValueConverter != null) {
return attributeValueConverter.convertAttribute(attr);
} else {
return Stream.of(attr);
}
}
private Function<ConfigurationAttribute, ConfigurationValue> getConfigurationValueSupplier(
final Long institutionId,
final Long configurationId) {
final Map<Long, ConfigurationValue> mapping = this.configurationValueDAO
.allRootAttributeValues(institutionId, configurationId)
.getOrThrow()
.stream()
.collect(Collectors.toMap(
ConfigurationValue::getAttributeId,
Function.identity()));
return attr -> mapping.get(attr.id);
}
}

View file

@ -1,432 +1,432 @@
/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
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.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
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.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;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebConfigEncryptionServiceImpl.EncryptionContext;
@Lazy
@Service
@WebServiceProfile
public class ExamConfigServiceImpl implements ExamConfigService {
private static final Logger log = LoggerFactory.getLogger(ExamConfigServiceImpl.class);
private final ExamConfigIO examConfigIO;
private final ConfigurationAttributeDAO configurationAttributeDAO;
private final ExamConfigurationMapDAO examConfigurationMapDAO;
private final Collection<ConfigurationValueValidator> validators;
private final ClientCredentialService clientCredentialService;
private final ZipService zipService;
private final SebConfigEncryptionService sebConfigEncryptionService;
protected ExamConfigServiceImpl(
final ExamConfigIO examConfigIO,
final ConfigurationAttributeDAO configurationAttributeDAO,
final ExamConfigurationMapDAO examConfigurationMapDAO,
final Collection<ConfigurationValueValidator> validators,
final ClientCredentialService clientCredentialService,
final ZipService zipService,
final SebConfigEncryptionService sebConfigEncryptionService) {
this.examConfigIO = examConfigIO;
this.configurationAttributeDAO = configurationAttributeDAO;
this.examConfigurationMapDAO = examConfigurationMapDAO;
this.validators = validators;
this.clientCredentialService = clientCredentialService;
this.zipService = zipService;
this.sebConfigEncryptionService = sebConfigEncryptionService;
}
@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;
}
final ConfigurationAttribute attribute = this.configurationAttributeDAO
.byPK(value.attributeId)
.getOrThrow();
this.validators
.stream()
.filter(validator -> !validator.validate(value, attribute))
.findFirst()
.ifPresent(validator -> validator.throwValidationError(value, attribute));
}
@Override
public void validate(final ConfigurationTableValues tableValue) throws FieldValidationException {
final List<APIMessage> errors = tableValue.values.stream()
.map(tv -> new ConfigurationValue(
null,
tableValue.institutionId,
tableValue.configurationId,
tv.attributeId,
tv.listIndex,
tv.value))
.flatMap(cv -> {
try {
validate(cv);
return Stream.empty();
} catch (final FieldValidationException fve) {
return Stream.of(fve);
}
})
.map(fve -> fve.apiMessage)
.collect(Collectors.toList());
if (!errors.isEmpty()) {
throw new APIMessageException(errors);
}
}
@Override
public void exportPlainXML(
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) {
this.exportPlainOnly(ConfigurationFormat.XML, out, institutionId, configurationNodeId);
}
@Override
public void exportPlainJSON(
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) {
this.exportPlainOnly(ConfigurationFormat.JSON, out, institutionId, configurationNodeId);
}
public Result<Long> getDefaultConfigurationIdForExam(final Long examId) {
return this.examConfigurationMapDAO.getDefaultConfigurationNode(examId);
}
public Result<Long> getUserConfigurationIdForExam(final Long examId, final String userId) {
return this.examConfigurationMapDAO.getUserConfigurationNodeId(examId, userId);
}
@Override
public Long exportForExam(
final OutputStream out,
final Long institutionId,
final Long examId,
final String userId) {
final Long configurationNodeId = (StringUtils.isBlank(userId))
? getDefaultConfigurationIdForExam(examId)
.getOrThrow()
: getUserConfigurationIdForExam(examId, userId)
.getOrThrow();
return exportForExam(out, institutionId, examId, configurationNodeId);
}
@Override
public Long exportForExam(
final OutputStream out,
final Long institutionId,
final Long examId,
final Long configurationNodeId) {
final CharSequence passwordCipher = this.examConfigurationMapDAO
.getConfigPasswortCipher(examId, configurationNodeId)
.getOr(null);
if (StringUtils.isNotBlank(passwordCipher)) {
if (log.isDebugEnabled()) {
log.debug("*** Seb exam configuration with password based encryption");
}
final CharSequence encryptionPasswordPlaintext = this.clientCredentialService
.decrypt(passwordCipher);
PipedOutputStream plainOut = null;
PipedInputStream zipIn = null;
PipedOutputStream zipOut = null;
PipedInputStream cryptIn = null;
PipedOutputStream cryptOut = null;
PipedInputStream in = null;
try {
plainOut = new PipedOutputStream();
zipIn = new PipedInputStream(plainOut);
zipOut = new PipedOutputStream();
cryptIn = new PipedInputStream(zipOut);
cryptOut = new PipedOutputStream();
in = new PipedInputStream(cryptOut);
// streaming...
// export plain text
this.examConfigIO.exportPlain(
ConfigurationFormat.XML,
plainOut,
institutionId,
configurationNodeId);
// zip the plain text
this.zipService.write(zipOut, zipIn);
// encrypt the zipped plain text
this.sebConfigEncryptionService.streamEncrypted(
cryptOut,
cryptIn,
EncryptionContext.contextOf(
Strategy.PASSWORD_PSWD,
encryptionPasswordPlaintext));
// copy to output
IOUtils.copyLarge(in, out);
} catch (final Exception e) {
log.error("Error while zip and encrypt seb exam config stream: ", e);
} finally {
IOUtils.closeQuietly(zipIn);
IOUtils.closeQuietly(plainOut);
IOUtils.closeQuietly(cryptIn);
IOUtils.closeQuietly(zipOut);
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(cryptOut);
}
} else {
// just export in plain text XML format
this.exportPlainXML(out, institutionId, configurationNodeId);
}
return configurationNodeId;
}
@Override
public Result<String> generateConfigKey(
final Long institutionId,
final Long configurationNodeId) {
if (log.isDebugEnabled()) {
log.debug("Start to stream plain JSON SEB Configuration data for Config-Key generation");
}
if (log.isTraceEnabled()) {
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain(
ConfigurationFormat.JSON,
pout,
institutionId,
configurationNodeId);
final String json = IOUtils.toString(pin, "UTF-8");
log.trace("SEB Configuration JSON to create Config-Key: {}", json);
} catch (final Exception e) {
log.error("Failed to trace SEB Configuration JSON: ", e);
}
}
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain(
ConfigurationFormat.JSON,
pout,
institutionId,
configurationNodeId);
final String configKey = DigestUtils.sha256Hex(pin);
return Result.of(configKey);
} catch (final Exception e) {
log.error("Error while stream plain JSON SEB Configuration data for Config-Key generation: ", e);
return Result.ofError(e);
} finally {
try {
if (pin != null) {
pin.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1);
}
try {
if (pout != null) {
pout.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
}
if (log.isDebugEnabled()) {
log.debug("Finished to stream plain JSON SEB Configuration data for Config-Key generation");
}
}
}
@Override
public Result<Collection<String>> generateConfigKeys(final Long institutionId, final Long examId) {
return this.examConfigurationMapDAO.getConfigurationNodeIds(examId)
.map(ids -> ids
.stream()
.map(id -> generateConfigKey(institutionId, id)
.getOrThrow())
.collect(Collectors.toList()));
}
@Override
public Result<Configuration> importFromSEBFile(
final Configuration config,
final InputStream input,
final CharSequence password) {
return Result.tryCatch(() -> {
Future<Exception> streamDecrypted = null;
InputStream cryptIn = null;
PipedInputStream plainIn = null;
PipedOutputStream cryptOut = null;
InputStream unzippedIn = null;
try {
cryptIn = this.examConfigIO.unzip(input);
plainIn = new PipedInputStream();
cryptOut = new PipedOutputStream(plainIn);
// decrypt
streamDecrypted = this.sebConfigEncryptionService.streamDecrypted(
cryptOut,
cryptIn,
EncryptionContext.contextOf(password));
// if zipped, unzip attach unzip stream first
unzippedIn = this.examConfigIO.unzip(plainIn);
// parse XML and import
this.examConfigIO.importPlainXML(
unzippedIn,
config.institutionId,
config.id);
return config;
} catch (final Exception e) {
log.error("Unexpected error while trying to import SEB Exam Configuration: ", e);
if (streamDecrypted != null) {
final Exception exception = streamDecrypted.get();
if (exception != null && exception instanceof APIMessageException) {
throw exception;
}
}
throw new RuntimeException("Failed to import SEB configuration. Cause is: " + e.getMessage());
} finally {
IOUtils.closeQuietly(cryptIn);
IOUtils.closeQuietly(plainIn);
IOUtils.closeQuietly(cryptOut);
IOUtils.closeQuietly(unzippedIn);
}
});
}
private void exportPlainOnly(
final ConfigurationFormat exportFormat,
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) {
if (log.isDebugEnabled()) {
log.debug("Start to stream plain text SEB Configuration data");
}
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain(
exportFormat,
pout,
institutionId,
configurationNodeId);
IOUtils.copyLarge(pin, out);
} catch (final Exception e) {
log.error("Error while stream plain text SEB Configuration export data: ", e);
} finally {
try {
if (pin != null) {
pin.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1);
}
try {
if (pout != null) {
pout.flush();
pout.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
}
if (log.isDebugEnabled()) {
log.debug("Finished to stream plain text SEB Configuration export data");
}
}
}
}
/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
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.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
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.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;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebConfigEncryptionServiceImpl.EncryptionContext;
@Lazy
@Service
@WebServiceProfile
public class ExamConfigServiceImpl implements ExamConfigService {
private static final Logger log = LoggerFactory.getLogger(ExamConfigServiceImpl.class);
private final ExamConfigIO examConfigIO;
private final ConfigurationAttributeDAO configurationAttributeDAO;
private final ExamConfigurationMapDAO examConfigurationMapDAO;
private final Collection<ConfigurationValueValidator> validators;
private final ClientCredentialService clientCredentialService;
private final ZipService zipService;
private final SebConfigEncryptionService sebConfigEncryptionService;
protected ExamConfigServiceImpl(
final ExamConfigIO examConfigIO,
final ConfigurationAttributeDAO configurationAttributeDAO,
final ExamConfigurationMapDAO examConfigurationMapDAO,
final Collection<ConfigurationValueValidator> validators,
final ClientCredentialService clientCredentialService,
final ZipService zipService,
final SebConfigEncryptionService sebConfigEncryptionService) {
this.examConfigIO = examConfigIO;
this.configurationAttributeDAO = configurationAttributeDAO;
this.examConfigurationMapDAO = examConfigurationMapDAO;
this.validators = validators;
this.clientCredentialService = clientCredentialService;
this.zipService = zipService;
this.sebConfigEncryptionService = sebConfigEncryptionService;
}
@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;
}
final ConfigurationAttribute attribute = this.configurationAttributeDAO
.byPK(value.attributeId)
.getOrThrow();
this.validators
.stream()
.filter(validator -> !validator.validate(value, attribute))
.findFirst()
.ifPresent(validator -> validator.throwValidationError(value, attribute));
}
@Override
public void validate(final ConfigurationTableValues tableValue) throws FieldValidationException {
final List<APIMessage> errors = tableValue.values.stream()
.map(tv -> new ConfigurationValue(
null,
tableValue.institutionId,
tableValue.configurationId,
tv.attributeId,
tv.listIndex,
tv.value))
.flatMap(cv -> {
try {
validate(cv);
return Stream.empty();
} catch (final FieldValidationException fve) {
return Stream.of(fve);
}
})
.map(fve -> fve.apiMessage)
.collect(Collectors.toList());
if (!errors.isEmpty()) {
throw new APIMessageException(errors);
}
}
@Override
public void exportPlainXML(
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) {
this.exportPlainOnly(ConfigurationFormat.XML, out, institutionId, configurationNodeId);
}
@Override
public void exportPlainJSON(
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) {
this.exportPlainOnly(ConfigurationFormat.JSON, out, institutionId, configurationNodeId);
}
public Result<Long> getDefaultConfigurationIdForExam(final Long examId) {
return this.examConfigurationMapDAO.getDefaultConfigurationNode(examId);
}
public Result<Long> getUserConfigurationIdForExam(final Long examId, final String userId) {
return this.examConfigurationMapDAO.getUserConfigurationNodeId(examId, userId);
}
@Override
public Long exportForExam(
final OutputStream out,
final Long institutionId,
final Long examId,
final String userId) {
final Long configurationNodeId = (StringUtils.isBlank(userId))
? getDefaultConfigurationIdForExam(examId)
.getOrThrow()
: getUserConfigurationIdForExam(examId, userId)
.getOrThrow();
return exportForExam(out, institutionId, examId, configurationNodeId);
}
@Override
public Long exportForExam(
final OutputStream out,
final Long institutionId,
final Long examId,
final Long configurationNodeId) {
final CharSequence passwordCipher = this.examConfigurationMapDAO
.getConfigPasswordCipher(examId, configurationNodeId)
.getOr(null);
if (StringUtils.isNotBlank(passwordCipher)) {
if (log.isDebugEnabled()) {
log.debug("*** Seb exam configuration with password based encryption");
}
final CharSequence encryptionPasswordPlaintext = this.clientCredentialService
.decrypt(passwordCipher);
PipedOutputStream plainOut = null;
PipedInputStream zipIn = null;
PipedOutputStream zipOut = null;
PipedInputStream cryptIn = null;
PipedOutputStream cryptOut = null;
PipedInputStream in = null;
try {
plainOut = new PipedOutputStream();
zipIn = new PipedInputStream(plainOut);
zipOut = new PipedOutputStream();
cryptIn = new PipedInputStream(zipOut);
cryptOut = new PipedOutputStream();
in = new PipedInputStream(cryptOut);
// streaming...
// export plain text
this.examConfigIO.exportPlain(
ConfigurationFormat.XML,
plainOut,
institutionId,
configurationNodeId);
// zip the plain text
this.zipService.write(zipOut, zipIn);
// encrypt the zipped plain text
this.sebConfigEncryptionService.streamEncrypted(
cryptOut,
cryptIn,
EncryptionContext.contextOf(
Strategy.PASSWORD_PSWD,
encryptionPasswordPlaintext));
// copy to output
IOUtils.copyLarge(in, out);
} catch (final Exception e) {
log.error("Error while zip and encrypt seb exam config stream: ", e);
} finally {
IOUtils.closeQuietly(zipIn);
IOUtils.closeQuietly(plainOut);
IOUtils.closeQuietly(cryptIn);
IOUtils.closeQuietly(zipOut);
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(cryptOut);
}
} else {
// just export in plain text XML format
this.exportPlainXML(out, institutionId, configurationNodeId);
}
return configurationNodeId;
}
@Override
public Result<String> generateConfigKey(
final Long institutionId,
final Long configurationNodeId) {
if (log.isDebugEnabled()) {
log.debug("Start to stream plain JSON SEB Configuration data for Config-Key generation");
}
if (log.isTraceEnabled()) {
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain(
ConfigurationFormat.JSON,
pout,
institutionId,
configurationNodeId);
final String json = IOUtils.toString(pin, "UTF-8");
log.trace("SEB Configuration JSON to create Config-Key: {}", json);
} catch (final Exception e) {
log.error("Failed to trace SEB Configuration JSON: ", e);
}
}
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain(
ConfigurationFormat.JSON,
pout,
institutionId,
configurationNodeId);
final String configKey = DigestUtils.sha256Hex(pin);
return Result.of(configKey);
} catch (final Exception e) {
log.error("Error while stream plain JSON SEB Configuration data for Config-Key generation: ", e);
return Result.ofError(e);
} finally {
try {
if (pin != null) {
pin.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1);
}
try {
if (pout != null) {
pout.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
}
if (log.isDebugEnabled()) {
log.debug("Finished to stream plain JSON SEB Configuration data for Config-Key generation");
}
}
}
@Override
public Result<Collection<String>> generateConfigKeys(final Long institutionId, final Long examId) {
return this.examConfigurationMapDAO.getConfigurationNodeIds(examId)
.map(ids -> ids
.stream()
.map(id -> generateConfigKey(institutionId, id)
.getOrThrow())
.collect(Collectors.toList()));
}
@Override
public Result<Configuration> importFromSEBFile(
final Configuration config,
final InputStream input,
final CharSequence password) {
return Result.tryCatch(() -> {
Future<Exception> streamDecrypted = null;
InputStream cryptIn = null;
PipedInputStream plainIn = null;
PipedOutputStream cryptOut = null;
InputStream unzippedIn = null;
try {
cryptIn = this.examConfigIO.unzip(input);
plainIn = new PipedInputStream();
cryptOut = new PipedOutputStream(plainIn);
// decrypt
streamDecrypted = this.sebConfigEncryptionService.streamDecrypted(
cryptOut,
cryptIn,
EncryptionContext.contextOf(password));
// if zipped, unzip attach unzip stream first
unzippedIn = this.examConfigIO.unzip(plainIn);
// parse XML and import
this.examConfigIO.importPlainXML(
unzippedIn,
config.institutionId,
config.id);
return config;
} catch (final Exception e) {
log.error("Unexpected error while trying to import SEB Exam Configuration: ", e);
if (streamDecrypted != null) {
final Exception exception = streamDecrypted.get();
if (exception != null && exception instanceof APIMessageException) {
throw exception;
}
}
throw new RuntimeException("Failed to import SEB configuration. Cause is: " + e.getMessage());
} finally {
IOUtils.closeQuietly(cryptIn);
IOUtils.closeQuietly(plainIn);
IOUtils.closeQuietly(cryptOut);
IOUtils.closeQuietly(unzippedIn);
}
});
}
private void exportPlainOnly(
final ConfigurationFormat exportFormat,
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) {
if (log.isDebugEnabled()) {
log.debug("Start to stream plain text SEB Configuration data");
}
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain(
exportFormat,
pout,
institutionId,
configurationNodeId);
IOUtils.copyLarge(pin, out);
} catch (final Exception e) {
log.error("Error while stream plain text SEB Configuration export data: ", e);
} finally {
try {
if (pin != null) {
pin.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1);
}
try {
if (pout != null) {
pout.flush();
pout.close();
}
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
}
if (log.isDebugEnabled()) {
log.debug("Finished to stream plain text SEB Configuration export data");
}
}
}
}

View file

@ -1,100 +1,133 @@
/*
* 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 java.util.function.Function;
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.AttributeValueConverter;
@Lazy
@Component
@WebServiceProfile
public class StringConverter implements AttributeValueConverter {
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 XML_TEMPLATE = "<key>%s</key><string>%s</string>";
private static final String XML_TEMPLATE_EMPTY = "<key>%s</key><string />";
private static final String JSON_TEMPLATE = "\"%s\":\"%s\"";
private static final String JSON_TEMPLATE_EMPTY = "\"%s\":\"\"";
@Override
public Set<AttributeType> types() {
return SUPPORTED_TYPES;
}
@Override
public void convertToXML(
final OutputStream out,
final ConfigurationAttribute attribute,
final Function<ConfigurationAttribute, ConfigurationValue> valueSupplier) throws IOException {
convert(
out,
attribute,
valueSupplier.apply(attribute),
XML_TEMPLATE, XML_TEMPLATE_EMPTY);
}
@Override
public void convertToJSON(
final OutputStream out,
final ConfigurationAttribute attribute,
final Function<ConfigurationAttribute, ConfigurationValue> valueSupplier) throws IOException {
convert(
out,
attribute,
valueSupplier.apply(attribute),
JSON_TEMPLATE, JSON_TEMPLATE_EMPTY);
}
private void convert(
final OutputStream out,
final ConfigurationAttribute attribute,
final ConfigurationValue value,
final String template,
final String emptyTemplate) throws IOException {
final String val = (value != null && value.value != null) ? value.value : attribute.getDefaultValue();
if (StringUtils.isNotBlank(val)) {
out.write(Utils.toByteArray(String.format(
template,
AttributeValueConverter.extractName(attribute),
val)));
} else {
out.write(Utils.toByteArray(String.format(
emptyTemplate,
AttributeValueConverter.extractName(attribute))));
}
}
}
/*
* 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 java.util.function.Function;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.ExamConfigXMLParser;
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.AttributeValueConverter;
@Lazy
@Component
@WebServiceProfile
public class StringConverter implements AttributeValueConverter {
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 XML_TEMPLATE = "<key>%s</key><string>%s</string>";
private static final String XML_TEMPLATE_EMPTY = "<key>%s</key><string />";
private static final String JSON_TEMPLATE = "\"%s\":\"%s\"";
private static final String JSON_TEMPLATE_EMPTY = "\"%s\":\"\"";
private final ClientCredentialService clientCredentialService;
public StringConverter(final ClientCredentialService clientCredentialService) {
this.clientCredentialService = clientCredentialService;
}
@Override
public Set<AttributeType> types() {
return SUPPORTED_TYPES;
}
@Override
public void convertToXML(
final OutputStream out,
final ConfigurationAttribute attribute,
final Function<ConfigurationAttribute, ConfigurationValue> valueSupplier) throws IOException {
convert(
out,
attribute,
valueSupplier.apply(attribute),
XML_TEMPLATE, XML_TEMPLATE_EMPTY);
}
@Override
public void convertToJSON(
final OutputStream out,
final ConfigurationAttribute attribute,
final Function<ConfigurationAttribute, ConfigurationValue> valueSupplier) throws IOException {
convert(
out,
attribute,
valueSupplier.apply(attribute),
JSON_TEMPLATE, JSON_TEMPLATE_EMPTY);
}
private void convert(
final OutputStream out,
final ConfigurationAttribute attribute,
final ConfigurationValue value,
final String template,
final String emptyTemplate) throws IOException {
final String val = (value != null && value.value != null) ? value.value : attribute.getDefaultValue();
String realName = AttributeValueConverter.extractName(attribute);
if (StringUtils.isNotBlank(val)) {
out.write(Utils.toByteArray(String.format(
template,
realName,
convertPassword(realName, val))));
} else {
out.write(Utils.toByteArray(String.format(
emptyTemplate,
realName)));
}
}
private CharSequence convertPassword(
final String attributeName,
final String value) {
if (StringUtils.isBlank(value)) {
return value;
}
if (!ExamConfigXMLParser.PASSWORD_ATTRIBUTES.contains(attributeName)) {
return value;
}
// decrypt internally encrypted password and hash it for export
// NOTE: see special case description in ExamConfigXMLParser.createConfigurationValue
String plainText = this.clientCredentialService.decrypt(value).toString();
if (plainText.endsWith(ExamConfigXMLParser.IMPORTED_PASSWORD_MARKER)) {
return plainText.replace(ExamConfigXMLParser.IMPORTED_PASSWORD_MARKER, StringUtils.EMPTY);
} else {
return Utils.hash_SHA_256_Base_16(plainText);
}
}
}

View file

@ -1,79 +1,87 @@
/*
* 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.init;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
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.impl.ExamConfigXMLParser;
@Lazy
@Component
@WebServiceProfile
public class XMLAttributeLoader {
private static final Logger log = LoggerFactory.getLogger(XMLAttributeLoader.class);
public Collection<ConfigurationValue> loadFromXML(
final Long institutionId,
final Long configurationId,
final Function<String, ConfigurationAttribute> attributeResolver,
final String xmlFileName) {
InputStream inputStream;
try {
final ClassPathResource configFileResource = new ClassPathResource(xmlFileName);
inputStream = configFileResource.getInputStream();
} catch (final Exception e) {
log.error("Failed to get config resources from: {}", xmlFileName, e);
return Collections.emptyList();
}
try {
final Collection<ConfigurationValue> values = new ArrayList<>();
final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser(
institutionId,
configurationId,
values::add,
attributeResolver);
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
final SAXParser parser = saxParserFactory.newSAXParser();
parser.parse(inputStream, examConfigImportHandler);
return Utils.immutableCollectionOf(values);
} catch (final Exception e) {
log.error("Unexpected error while trying to get initial permitted processes", e);
return Collections.emptyList();
} finally {
IOUtils.closeQuietly(inputStream);
}
}
}
/*
* 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.init;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
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.impl.ExamConfigXMLParser;
@Lazy
@Component
@WebServiceProfile
public class XMLAttributeLoader {
private static final Logger log = LoggerFactory.getLogger(XMLAttributeLoader.class);
private final Cryptor cryptor;
public XMLAttributeLoader(Cryptor cryptor) {
this.cryptor = cryptor;
}
public Collection<ConfigurationValue> loadFromXML(
final Long institutionId,
final Long configurationId,
final Function<String, ConfigurationAttribute> attributeResolver,
final String xmlFileName) {
InputStream inputStream;
try {
final ClassPathResource configFileResource = new ClassPathResource(xmlFileName);
inputStream = configFileResource.getInputStream();
} catch (final Exception e) {
log.error("Failed to get config resources from: {}", xmlFileName, e);
return Collections.emptyList();
}
try {
final Collection<ConfigurationValue> values = new ArrayList<>();
final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser(
cryptor,
institutionId,
configurationId,
values::add,
attributeResolver);
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
final SAXParser parser = saxParserFactory.newSAXParser();
parser.parse(inputStream, examConfigImportHandler);
return Utils.immutableCollectionOf(values);
} catch (final Exception e) {
log.error("Unexpected error while trying to get initial permitted processes", e);
return Collections.emptyList();
} finally {
IOUtils.closeQuietly(inputStream);
}
}
}

View file

@ -1,166 +1,215 @@
/*
* 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.weblayer.api;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.validation.FieldError;
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.RestController;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@WebServiceProfile
@RestController
@EnableAsync
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_CONFIG_ENDPOINT)
public class SebClientConfigController extends ActivatableEntityController<SebClientConfig, SebClientConfig> {
private final ClientConfigService sebClientConfigService;
public SebClientConfigController(
final SebClientConfigDAO sebClientConfigDAO,
final AuthorizationService authorization,
final UserActivityLogDAO userActivityLogDAO,
final BulkActionService bulkActionService,
final PaginationService paginationService,
final BeanValidationService beanValidationService,
final ClientConfigService sebClientConfigService) {
super(authorization,
bulkActionService,
sebClientConfigDAO,
userActivityLogDAO,
paginationService,
beanValidationService);
this.sebClientConfigService = sebClientConfigService;
}
@RequestMapping(
path = API.SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadSEBConfig(
@PathVariable final String modelId,
final HttpServletResponse response) throws IOException {
this.entityDAO.byModelId(modelId)
.flatMap(this.authorization::checkWrite)
.map(this.userActivityLogDAO::logExport);
final ServletOutputStream outputStream = response.getOutputStream();
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.sebClientConfigService.exportSebClientConfiguration(
pout,
modelId);
IOUtils.copyLarge(pin, outputStream);
response.setStatus(HttpStatus.OK.value());
outputStream.flush();
} finally {
outputStream.flush();
outputStream.close();
}
// final StreamingResponseBody stream = out -> {
// this.sebClientConfigService.exportSebClientConfiguration(
// out,
// modelId);
// };
//
// return new ResponseEntity<>(stream, HttpStatus.OK);
}
@Override
protected SebClientConfig createNew(final POSTMapper postParams) {
final Long institutionId = postParams.getLong(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID);
if (institutionId == null) {
throw new APIConstraintViolationException("Institution identifier is missing");
}
postParams.putIfAbsent(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE,
DateTime.now(DateTimeZone.UTC).toString(Constants.DEFAULT_DATE_TIME_FORMAT));
return new SebClientConfig(institutionId, postParams);
}
@Override
protected SqlTable getSQLTableOfEntity() {
return SebClientConfigRecordDynamicSqlSupport.sebClientConfigRecord;
}
@Override
protected Result<SebClientConfig> validForCreate(final SebClientConfig entity) {
return super.validForCreate(entity)
.map(this::checkPasswordMatch);
}
@Override
protected Result<SebClientConfig> validForSave(final SebClientConfig entity) {
return super.validForSave(entity)
.map(this::checkPasswordMatch);
}
private SebClientConfig checkPasswordMatch(final SebClientConfig entity) {
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
PasswordChange.ATTR_NAME_PASSWORD,
"clientConfig:confirm_encrypt_secret:password.mismatch")));
}
return entity;
}
}
/*
* 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.weblayer.api;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.validation.FieldError;
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.RestController;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@WebServiceProfile
@RestController
@EnableAsync
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_CONFIG_ENDPOINT)
public class SebClientConfigController extends ActivatableEntityController<SebClientConfig, SebClientConfig> {
private final ClientConfigService sebClientConfigService;
public SebClientConfigController(
final SebClientConfigDAO sebClientConfigDAO,
final AuthorizationService authorization,
final UserActivityLogDAO userActivityLogDAO,
final BulkActionService bulkActionService,
final PaginationService paginationService,
final BeanValidationService beanValidationService,
final ClientConfigService sebClientConfigService) {
super(authorization,
bulkActionService,
sebClientConfigDAO,
userActivityLogDAO,
paginationService,
beanValidationService);
this.sebClientConfigService = sebClientConfigService;
}
@RequestMapping(
path = API.SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadSEBConfig(
@PathVariable final String modelId,
final HttpServletResponse response) throws IOException {
this.entityDAO.byModelId(modelId)
.flatMap(this.authorization::checkWrite)
.map(this.userActivityLogDAO::logExport);
final ServletOutputStream outputStream = response.getOutputStream();
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.sebClientConfigService.exportSebClientConfiguration(
pout,
modelId);
IOUtils.copyLarge(pin, outputStream);
response.setStatus(HttpStatus.OK.value());
outputStream.flush();
} finally {
outputStream.flush();
outputStream.close();
}
}
@Override
protected SebClientConfig createNew(final POSTMapper postParams) {
final Long institutionId = postParams.getLong(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID);
if (institutionId == null) {
throw new APIConstraintViolationException("Institution identifier is missing");
}
postParams.putIfAbsent(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE,
DateTime.now(DateTimeZone.UTC).toString(Constants.DEFAULT_DATE_TIME_FORMAT));
return new SebClientConfig(institutionId, postParams);
}
@Override
protected SqlTable getSQLTableOfEntity() {
return SebClientConfigRecordDynamicSqlSupport.sebClientConfigRecord;
}
@Override
protected Result<SebClientConfig> validForCreate(final SebClientConfig entity) {
return super.validForCreate(entity)
.map(this::checkPasswordMatch);
}
@Override
protected Result<SebClientConfig> validForSave(final SebClientConfig entity) {
return super.validForSave(entity)
.map(this::checkPasswordMatch);
}
private SebClientConfig checkPasswordMatch(final SebClientConfig entity) {
Collection<APIMessage> errors = new ArrayList<>();
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.encryptSecretConfirm)) {
errors.add(APIMessage.fieldValidationError(
new FieldError(
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
PasswordChange.ATTR_NAME_PASSWORD,
"clientConfig:confirm_encrypt_secret:password.mismatch")));
}
if (entity.hasFallbackPassword() && !entity.fallbackPassword.equals(entity.fallbackPasswordConfirm)) {
errors.add(APIMessage.fieldValidationError(
new FieldError(
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
SebClientConfig.ATTR_FALLBACK_PASSWORD_CONFIRM,
"clientConfig:sebServerFallbackPasswordHashConfirm:password.mismatch")));
}
if (entity.hasQuitPassword() && !entity.quitPassword.equals(entity.quitPasswordConfirm)) {
errors.add(APIMessage.fieldValidationError(
new FieldError(
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
SebClientConfig.ATTR_QUIT_PASSWORD_CONFIRM,
"clientConfig:hashedQuitPasswordConfirm:password.mismatch")));
}
if (BooleanUtils.isTrue(entity.fallback) && StringUtils.isBlank(entity.fallbackStartURL)) {
errors.add(APIMessage.fieldValidationError(
new FieldError(
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
SebClientConfig.ATTR_FALLBACK_START_URL,
"clientConfig:startURL:notNull")));
}
if (BooleanUtils.isTrue(entity.fallback) && entity.fallbackTimeout == null) {
errors.add(APIMessage.fieldValidationError(
new FieldError(
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
"clientConfig:sebServerFallbackTimeout:notNull")));
}
if (BooleanUtils.isTrue(entity.fallback) && entity.fallbackAttempts == null) {
errors.add(APIMessage.fieldValidationError(
new FieldError(
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
"clientConfig:sebServerFallbackAttempts:notNull")));
}
if (BooleanUtils.isTrue(entity.fallback) && entity.fallbackAttemptInterval == null) {
errors.add(APIMessage.fieldValidationError(
new FieldError(
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
"clientConfig:sebServerFallbackAttemptInterval:notNull")));
}
if (!errors.isEmpty()) {
throw new APIMessage.APIMessageException(errors);
}
return entity;
}
}

View file

@ -23,6 +23,8 @@ sebserver.overall.action.goAwayFromEditPageConfirm=Are you sure you want to leav
sebserver.overall.action.category.varia=
sebserver.overall.action.category.filter=
sebserver.overall.action.showPassword.tooltip=Show / hide password in plain text.
sebserver.overall.status.active=Active
sebserver.overall.status.inactive=Inactive
sebserver.overall.status.all=All
@ -567,7 +569,7 @@ sebserver.exam.indicator.thresholds.list.add=Add a new threshold
sebserver.exam.indicator.thresholds.list.remove=Delete this threshold
################################
# SEB Client Configuration
# SEB client configuration
################################
sebserver.sebconfig.activity.name=SEB Configuration
@ -576,7 +578,7 @@ sebserver.clientconfig.action.list=Client Configuration
sebserver.clientconfig.action.export=Export
sebserver.clientconfig.list.empty=There is currently no SEB-Client configuration available. Please create a new one
sebserver.clientconfig.list.title=SEB Client Configurations
sebserver.clientconfig.list.title=SEB client configurations
sebserver.clientconfig.list.actions=
sebserver.clientconfig.list.column.institution=Institution
sebserver.clientconfig.list.column.institution.tooltip=The institution of the SEB client configuration.<br/><br/>Use the filter above to specify the institution.<br/>{0}
@ -587,24 +589,31 @@ sebserver.clientconfig.list.column.date.tooltip=The date when the SEB client con
sebserver.clientconfig.list.column.active=Active
sebserver.clientconfig.list.column.active.tooltip=The activity of SEB client configuration.<br/><br/>Use the filter above to specify the activity.<br/>{0}
sebserver.clientconfig.info.pleaseSelect=Please select first a Client Configuration from the list
sebserver.clientconfig.list.action.no.modify.privilege=No Access: A SEB Client Configuration from other institution cannot be modified.
sebserver.clientconfig.list.action.no.modify.privilege=No Access: A SEB client configuration from other institution cannot be modified.
sebserver.clientconfig.form.title.new=Add Client Configuration
sebserver.clientconfig.form.title=SEB Client Configuration
sebserver.clientconfig.form.title=SEB client configuration
sebserver.clientconfig.form.name=Name
sebserver.clientconfig.form.name.tooltip=The name of the SEB Client Configuration.<br/>Can be any name that not already exists for another SEB Client Configuration
sebserver.clientconfig.form.name.tooltip=The name of the SEB client configuration.<br/>Can be any name that not already exists for another SEB client configuration
sebserver.clientconfig.form.fallback=With Fallback
sebserver.clientconfig.form.fallback.tooltip=Indicates whether this SEB Client Configuration has a fallback definition or not
sebserver.clientconfig.form.fallback.tooltip=Indicates whether this SEB client configuration has a fallback definition or not
sebserver.clientconfig.form.fallback-url=Fallback Start URL
sebserver.clientconfig.form.fallback-url.tooltip=A fallback URL that tells the SEB where to go when the SEB Server service is unavailable.
sebserver.clientconfig.form.sebServerFallbackTimeout=Fallback Timeout
sebserver.clientconfig.form.sebServerFallbackTimeout.tooltip=Defines the fallback timeout for the SEB Client in milli-seconds.
sebserver.clientconfig.form.sebServerFallbackAttempts=Fallback Attempts
sebserver.clientconfig.form.sebServerFallbackAttempts.tooltip=The number of connection attempts a SEB Client is trying before switching to fallback case.
sebserver.clientconfig.form.sebServerFallbackAttemptInterval=Attempt Interval
sebserver.clientconfig.form.sebServerFallbackAttemptInterval.tooltip=The interval (in milli-seconds) between connection attempts a SEB Client shall use.
sebserver.clientconfig.form.sebServerFallbackTimeout.tooltip=Defines the fallback timeout for the SEB client in milli-seconds.
sebserver.clientconfig.form.sebServerFallbackAttempts=Connection Attempts
sebserver.clientconfig.form.sebServerFallbackAttempts.tooltip=The number of connection attempts a SEB client is trying before switching to fallback case.
sebserver.clientconfig.form.sebServerFallbackAttemptInterval=Interval
sebserver.clientconfig.form.sebServerFallbackAttemptInterval.tooltip=The interval (in milli-seconds) between connection attempts a SEB client shall use.
sebserver.clientconfig.form.sebServerFallbackPasswordHash=Fallback Password
sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip=A password if set, a SEB Client user must give before the SEB Client starts the fallback procedure.
sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip=A password if set a SEB Client user must provide before the SEB client starts the fallback procedure.
sebserver.clientconfig.form.sebServerFallbackPasswordHash.confirm=Confirm Fallback Password
sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip.confirm=Please confirm the fallback password
sebserver.clientconfig.form.hashedQuitPassword=Quit Password
sebserver.clientconfig.form.hashedQuitPassword.tooltip=A password if set a SEB client user must provide to be able to quit the SEB client.
sebserver.clientconfig.form.hashedQuitPassword.confirm=Confirm Quit Password
sebserver.clientconfig.form.hashedQuitPassword.tooltip.confirm=Please confirm the quit password
sebserver.clientconfig.form.date=Creation Date
sebserver.clientconfig.form.date.tooltip=The date when the SEB client configuration was first created.
sebserver.clientconfig.form.encryptSecret=Configuration Password
@ -614,6 +623,11 @@ sebserver.clientconfig.form.encryptSecret.confirm.tooltip=Please retype the give
sebserver.clientconfig.form.sebConfigPurpose=Configuration Purpose
sebserver.clientconfig.form.sebConfigPurpose.tooltip=This indicates whether this client configuration shall be used to configure the SEB Client or to start an exam
sebserver.clientconfig.config.purpose.START_EXAM=Starting an Exam
sebserver.clientconfig.config.purpose.START_EXAM.tooltip=If the SEB client configuration is loaded via a SEB-Link, the local configuration will not be overwritten.
sebserver.clientconfig.config.purpose.CONFIGURE_CLIENT=Configure a Client
sebserver.clientconfig.config.purpose.CONFIGURE_CLIENT.tooltip=If the SEB client configuration is loaded via a SEB-Link, the local configuration will be overwritten by this configuration.
sebserver.clientconfig.action.list.new=Add Configuration
sebserver.clientconfig.action.list.view=View Configuration
sebserver.clientconfig.action.list.modify=Edit Configuration

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

View file

@ -1,150 +1,171 @@
/*
* 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.integration;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.test.context.jdbc.Sql;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ActivateClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.DeactivateClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.GetClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.NewClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.SaveClientConfig;
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" })
public class ClientConfigTest extends GuiIntegrationTest {
@Test
public void testNewClientConfigWithQueryParam() {
final RestServiceImpl restService = createRestServiceForUser("admin", "admin", new NewClientConfig());
final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
.withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.call();
assertNotNull(call);
assertFalse(call.hasError());
final SebClientConfig createdConfig = call.get();
assertEquals(Long.valueOf(1), createdConfig.id);
assertEquals("new client config", createdConfig.name);
assertFalse(createdConfig.active);
}
@Test
public void testNewClientConfigWithURLEncodedForm() {
final RestServiceImpl restService = createRestServiceForUser("admin", "admin", new NewClientConfig());
final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.call();
assertNotNull(call);
assertFalse(call.hasError());
final SebClientConfig createdConfig = call.get();
assertEquals(Long.valueOf(1), createdConfig.id);
assertEquals("new client config", createdConfig.name);
assertFalse(createdConfig.active);
}
@Test
public void testCreate_Get_Activate_Save_Deactivate_ClientConfig() {
final RestServiceImpl restService = createRestServiceForUser("admin", "admin",
new NewClientConfig(),
new GetClientConfig(),
new ActivateClientConfig(),
new SaveClientConfig(),
new DeactivateClientConfig());
// create one
final SebClientConfig config = restService.getBuilder(NewClientConfig.class)
.withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.call()
.getOrThrow();
// get
final Result<SebClientConfig> call = restService.getBuilder(GetClientConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
.call();
assertNotNull(call);
assertFalse(call.hasError());
final SebClientConfig createdConfig = call.get();
assertEquals(config.id, createdConfig.id);
assertEquals("new client config", createdConfig.name);
assertFalse(createdConfig.active);
// activate
final EntityProcessingReport activationReport = restService.getBuilder(ActivateClientConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
.call()
.getOrThrow();
assertTrue(activationReport.errors.isEmpty());
assertEquals(
"EntityKey [modelId=1, entityType=SEB_CLIENT_CONFIGURATION]",
activationReport.getSingleSource().toString());
// save with password (no confirm) expecting validation error
final Result<?> valError = restService.getBuilder(SaveClientConfig.class)
.withBody(new SebClientConfig(
config.id,
config.institutionId,
"new client config",
null,
null,
"password",
null,
null))
.call();
assertTrue(valError.hasError());
final Throwable error = valError.getError();
assertTrue(error.getMessage().contains("confirm_encrypt_secret"));
assertTrue(error.getMessage().contains("password.mismatch"));
// save with new password
final SebClientConfig newConfig = restService.getBuilder(SaveClientConfig.class)
.withBody(new SebClientConfig(
config.id,
config.institutionId,
"new client config",
null,
null,
"password",
"password",
null))
.call()
.getOrThrow();
assertEquals(config.id, newConfig.id);
assertEquals("new client config", newConfig.name);
assertTrue(newConfig.active);
assertNull(newConfig.getEncryptSecret());
// deactivate
final EntityProcessingReport deactivationReport = restService.getBuilder(DeactivateClientConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
.call()
.getOrThrow();
assertTrue(deactivationReport.errors.isEmpty());
assertEquals(
"EntityKey [modelId=1, entityType=SEB_CLIENT_CONFIGURATION]",
deactivationReport.getSingleSource().toString());
}
}
/*
* 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.integration;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.test.context.jdbc.Sql;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ActivateClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.DeactivateClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.GetClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.NewClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.SaveClientConfig;
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" })
public class ClientConfigTest extends GuiIntegrationTest {
@Test
public void testNewClientConfigWithQueryParam() {
final RestServiceImpl restService = createRestServiceForUser("admin", "admin", new NewClientConfig());
final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
.withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
.call();
assertNotNull(call);
assertFalse(call.hasError());
final SebClientConfig createdConfig = call.get();
assertEquals(Long.valueOf(1), createdConfig.id);
assertEquals("new client config", createdConfig.name);
assertFalse(createdConfig.active);
}
@Test
public void testNewClientConfigWithURLEncodedForm() {
final RestServiceImpl restService = createRestServiceForUser("admin", "admin", new NewClientConfig());
final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
.call();
assertNotNull(call);
assertFalse(call.hasError());
final SebClientConfig createdConfig = call.get();
assertEquals(Long.valueOf(1), createdConfig.id);
assertEquals("new client config", createdConfig.name);
assertFalse(createdConfig.active);
}
@Test
public void testCreate_Get_Activate_Save_Deactivate_ClientConfig() {
final RestServiceImpl restService = createRestServiceForUser("admin", "admin",
new NewClientConfig(),
new GetClientConfig(),
new ActivateClientConfig(),
new SaveClientConfig(),
new DeactivateClientConfig());
// create one
final SebClientConfig config = restService.getBuilder(NewClientConfig.class)
.withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
.call()
.getOrThrow();
// get
final Result<SebClientConfig> call = restService.getBuilder(GetClientConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
.call();
assertNotNull(call);
assertFalse(call.hasError());
final SebClientConfig createdConfig = call.get();
assertEquals(config.id, createdConfig.id);
assertEquals("new client config", createdConfig.name);
assertFalse(createdConfig.active);
// activate
final EntityProcessingReport activationReport = restService.getBuilder(ActivateClientConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
.call()
.getOrThrow();
assertTrue(activationReport.errors.isEmpty());
assertEquals(
"EntityKey [modelId=1, entityType=SEB_CLIENT_CONFIGURATION]",
activationReport.getSingleSource().toString());
// save with password (no confirm) expecting validation error
final Result<?> valError = restService.getBuilder(SaveClientConfig.class)
.withBody(new SebClientConfig(
config.id,
config.institutionId,
"new client config",
SebClientConfig.ConfigPurpose.START_EXAM,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
"password",
null,
null))
.call();
assertTrue(valError.hasError());
final Throwable error = valError.getError();
assertTrue(error.getMessage().contains("confirm_encrypt_secret"));
assertTrue(error.getMessage().contains("password.mismatch"));
// save with new password
final SebClientConfig newConfig = restService.getBuilder(SaveClientConfig.class)
.withBody(new SebClientConfig(
config.id,
config.institutionId,
"new client config",
SebClientConfig.ConfigPurpose.START_EXAM,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
"password",
"password",
null))
.call()
.getOrThrow();
assertEquals(config.id, newConfig.id);
assertEquals("new client config", newConfig.name);
assertTrue(newConfig.active);
assertNotNull(newConfig.getEncryptSecret());
// deactivate
final EntityProcessingReport deactivationReport = restService.getBuilder(DeactivateClientConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
.call()
.getOrThrow();
assertTrue(deactivationReport.errors.isEmpty());
assertEquals(
"EntityKey [modelId=1, entityType=SEB_CLIENT_CONFIGURATION]",
deactivationReport.getSingleSource().toString());
}
}

View file

@ -12,6 +12,7 @@ import static org.junit.Assert.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@ -853,7 +854,12 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
final Result<SebClientConfig> newConfigResponse = restService
.getBuilder(NewClientConfig.class)
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "No Password Protection")
.withFormParam(SebClientConfig.ATTR_FALLBACK, Constants.TRUE_STRING)
.withFormParam(SebClientConfig.ATTR_FALLBACK_START_URL, "http://fallback.com/fallback")
.withFormParam(SebClientConfig.ATTR_FALLBACK_TIMEOUT, "100")
.withFormParam(SebClientConfig.ATTR_FALLBACK_ATTEMPTS, "5")
.withFormParam(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL, "5")
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
.call();
assertNotNull(newConfigResponse);
@ -886,9 +892,14 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
final Result<SebClientConfig> configWithPasswordResponse = restService
.getBuilder(NewClientConfig.class)
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "With Password Protection")
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
.withFormParam(SebClientConfig.ATTR_FALLBACK, Constants.TRUE_STRING)
.withFormParam(SebClientConfig.ATTR_FALLBACK_START_URL, "http://fallback.com/fallback")
.withFormParam(SebClientConfig.ATTR_FALLBACK_TIMEOUT, "100")
.withFormParam(SebClientConfig.ATTR_FALLBACK_ATTEMPTS, "5")
.withFormParam(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL, "5")
.withFormParam(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET, "123")
.withFormParam(SebClientConfig.ATTR_CONFIRM_ENCRYPT_SECRET, "123")
.withFormParam(SebClientConfig.ATTR_ENCRYPT_SECRET_CONFIRM, "123")
.call();
assertNotNull(configWithPasswordResponse);
@ -1091,7 +1102,10 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
restService);
// update a value -- grab first
final ConfigurationValue value = values.get(0);
ConfigurationValue value = values.get(0);
if (value.attributeId == 1) {
value = values.get(1);
}
ConfigurationValue newValue = new ConfigurationValue(
null, value.institutionId, value.configurationId,
value.attributeId, value.listIndex, "2");
@ -1188,8 +1202,9 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
assertNotNull(valuesResponse);
assertFalse(valuesResponse.hasError());
values = valuesResponse.get();
final ConfigurationValue _value = value;
final ConfigurationValue currentValue =
values.stream().filter(v -> v.attributeId == value.attributeId).findFirst().orElse(null);
values.stream().filter(v -> v.attributeId == _value.attributeId).findFirst().orElse(null);
assertNotNull(currentValue);
assertEquals("2", currentValue.value);
}
@ -1336,12 +1351,10 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
new GetFollowupConfiguration());
// get all configuration attributes
final Collection<ConfigurationAttribute> attributes = restService
final Collection<ConfigurationAttribute> attributes = new ArrayList<>(restService
.getBuilder(GetConfigAttributes.class)
.call()
.getOrThrow()
.stream()
.collect(Collectors.toList());
.getOrThrow());
// get configuration page
final Result<Page<ConfigurationNode>> pageResponse = restService

View file

@ -1,132 +1,134 @@
/*
* Copyright (c) 2020 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.integration;
import static org.junit.Assert.*;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.tomcat.util.buf.StringUtils;
import ch.ethz.seb.sebserver.gbl.Constants;
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;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigAttributes;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurationTableValues;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.init.XMLAttributeLoader;
public abstract class UsecaseTestUtils {
static ConfigurationTableValues testProhibitedProcessesInit(
final String configId,
final RestServiceImpl restService) {
final ConfigurationTableValues tableValues = getTableValues("93", configId, restService);
assertNotNull(tableValues);
assertFalse(tableValues.values.isEmpty());
final String names = StringUtils.join(
tableValues.values
.stream()
.filter(attr -> attr.attributeId == 98)
.map(attr -> attr.value)
.sorted()
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR_CHAR);
// get all configuration attributes
final Map<String, ConfigurationAttribute> attributes = restService
.getBuilder(GetConfigAttributes.class)
.call()
.getOrThrow()
.stream()
.collect(Collectors.toMap(attr -> attr.name, Function.identity()));
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader();
final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
1L,
Long.parseLong(configId),
attrName -> attributes.get(attrName),
"config/initialProhibitedProcesses.xml")
.stream()
.filter(attr -> attr.attributeId == 98)
.map(attr -> attr.value)
.sorted()
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR_CHAR);
assertEquals(configuraedNames, names);
return tableValues;
}
static ConfigurationTableValues getTableValues(
final String attributeId,
final String configId,
final RestServiceImpl restService) {
final ConfigurationTableValues tableValues = restService.getBuilder(GetConfigurationTableValues.class)
.withQueryParam(
Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID,
attributeId)
.withQueryParam(
Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ID,
configId)
.call()
.getOrThrow();
return tableValues;
}
static ConfigurationTableValues testPermittedProcessesInit(
final String configId,
final RestServiceImpl restService) {
final ConfigurationTableValues tableValues = getTableValues("73", configId, restService);
assertNotNull(tableValues);
assertFalse(tableValues.values.isEmpty());
final String names = StringUtils.join(
tableValues.values
.stream()
.filter(attr -> attr.attributeId == 76)
.map(attr -> attr.value)
.sorted()
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR_CHAR);
// get all configuration attributes
final Map<String, ConfigurationAttribute> attributes = restService
.getBuilder(GetConfigAttributes.class)
.call()
.getOrThrow()
.stream()
.collect(Collectors.toMap(attr -> attr.name, Function.identity()));
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader();
final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
1L,
Long.parseLong(configId),
attrName -> attributes.get(attrName),
"config/initialPermittedProcesses.xml")
.stream()
.filter(attr -> attr.attributeId == 76)
.map(attr -> attr.value)
.sorted()
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR_CHAR);
assertEquals(configuraedNames, names);
return tableValues;
}
}
/*
* Copyright (c) 2020 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.integration;
import static org.junit.Assert.*;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.apache.tomcat.util.buf.StringUtils;
import ch.ethz.seb.sebserver.gbl.Constants;
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;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigAttributes;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurationTableValues;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.init.XMLAttributeLoader;
import org.mockito.Mockito;
public abstract class UsecaseTestUtils {
static ConfigurationTableValues testProhibitedProcessesInit(
final String configId,
final RestServiceImpl restService) {
final ConfigurationTableValues tableValues = getTableValues("93", configId, restService);
assertNotNull(tableValues);
assertFalse(tableValues.values.isEmpty());
final String names = StringUtils.join(
tableValues.values
.stream()
.filter(attr -> attr.attributeId == 98)
.map(attr -> attr.value)
.sorted()
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR_CHAR);
// get all configuration attributes
final Map<String, ConfigurationAttribute> attributes = restService
.getBuilder(GetConfigAttributes.class)
.call()
.getOrThrow()
.stream()
.collect(Collectors.toMap(attr -> attr.name, Function.identity()));
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader(Mockito.mock(Cryptor.class));
final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
1L,
Long.parseLong(configId),
attrName -> attributes.get(attrName),
"config/initialProhibitedProcesses.xml")
.stream()
.filter(attr -> attr.attributeId == 98)
.map(attr -> attr.value)
.sorted()
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR_CHAR);
assertEquals(configuraedNames, names);
return tableValues;
}
static ConfigurationTableValues getTableValues(
final String attributeId,
final String configId,
final RestServiceImpl restService) {
final ConfigurationTableValues tableValues = restService.getBuilder(GetConfigurationTableValues.class)
.withQueryParam(
Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID,
attributeId)
.withQueryParam(
Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ID,
configId)
.call()
.getOrThrow();
return tableValues;
}
static ConfigurationTableValues testPermittedProcessesInit(
final String configId,
final RestServiceImpl restService) {
final ConfigurationTableValues tableValues = getTableValues("73", configId, restService);
assertNotNull(tableValues);
assertFalse(tableValues.values.isEmpty());
final String names = StringUtils.join(
tableValues.values
.stream()
.filter(attr -> attr.attributeId == 76)
.map(attr -> attr.value)
.sorted()
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR_CHAR);
// get all configuration attributes
final Map<String, ConfigurationAttribute> attributes = restService
.getBuilder(GetConfigAttributes.class)
.call()
.getOrThrow()
.stream()
.collect(Collectors.toMap(attr -> attr.name, Function.identity()));
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader(Mockito.mock(Cryptor.class));
final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
1L,
Long.parseLong(configId),
attrName -> attributes.get(attrName),
"config/initialPermittedProcesses.xml")
.stream()
.filter(attr -> attr.attributeId == 76)
.map(attr -> attr.value)
.sorted()
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR_CHAR);
assertEquals(configuraedNames, names);
return tableValues;
}
}

View file

@ -1,55 +1,58 @@
/*
* 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.client;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.springframework.core.env.Environment;
public class ClientCredentialServiceTest {
// @Test
// public void testEncryptSimpleSecret() {
// final Environment envMock = mock(Environment.class);
// when(envMock.getProperty(ClientCredentialServiceImpl.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY))
// .thenReturn("somePW");
//
// final ClientCredentialService service = new ClientCredentialServiceImpl(envMock);
// final CharSequence encrypt = service.encrypt("test");
// assertEquals("", encrypt.toString());
// }
@Test
public void testEncryptDecryptClientCredentials() {
final Environment envMock = mock(Environment.class);
when(envMock.getRequiredProperty(ClientCredentialServiceImpl.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY))
.thenReturn("secret1");
final String clientName = "simpleClientName";
final ClientCredentialServiceImpl service = new ClientCredentialServiceImpl(envMock);
String encrypted =
service.encrypt(clientName, "secret1").toString();
String decrypted = service.decrypt(encrypted, "secret1").toString();
assertEquals(clientName, decrypted);
final String clientSecret = "fbjreij39ru29305ruࣣàèLöäöäü65%(/%(ç87";
encrypted =
service.encrypt(clientSecret, "secret1").toString();
decrypted = service.decrypt(encrypted, "secret1").toString();
assertEquals(clientSecret, decrypted);
}
}
/*
* 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.client;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.junit.Test;
import org.springframework.core.env.Environment;
public class ClientCredentialServiceTest {
// @Test
// public void testEncryptSimpleSecret() {
// final Environment envMock = mock(Environment.class);
// when(envMock.getProperty(ClientCredentialServiceImpl.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY))
// .thenReturn("somePW");
//
// final ClientCredentialService service = new ClientCredentialServiceImpl(envMock);
// final CharSequence encrypt = service.encrypt("test");
// assertEquals("", encrypt.toString());
// }
@Test
public void testEncryptDecryptClientCredentials() {
final Environment envMock = mock(Environment.class);
when(envMock.getRequiredProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY))
.thenReturn("secret1");
Cryptor cryptor = new Cryptor(envMock);
final String clientName = "simpleClientName";
final ClientCredentialServiceImpl service = new ClientCredentialServiceImpl(envMock, cryptor);
String encrypted =
cryptor.encrypt(clientName, "secret1").toString();
String decrypted = cryptor.decrypt(encrypted, "secret1").toString();
assertEquals(clientName, decrypted);
final String clientSecret = "fbjreij39ru29305ruࣣàèLöäöäü65%(/%(ç87";
encrypted =
cryptor.encrypt(clientSecret, "secret1").toString();
decrypted = cryptor.decrypt(encrypted, "secret1").toString();
assertEquals(clientSecret, decrypted);
}
}

View file

@ -1,339 +1,347 @@
/*
* 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;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.Test;
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;
public class ExamConfigImportHandlerTest {
private static final Function<String, ConfigurationAttribute> attributeResolver =
name -> new ConfigurationAttribute(
getId(name),
null, name, (name.contains("array")) ? AttributeType.MULTI_SELECTION : null, null, null, null,
null);
private static final Long getId(final String name) {
try {
return Long.parseLong(String.valueOf(name.charAt(name.length() - 1)));
} catch (final Exception e) {
return -1L;
}
}
@Test
public void simpleStringValueTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
1L,
1L,
valueCollector,
attributeResolver);
final String attribute = "param1";
final String value = "value1";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null);
candidate.characters(value.toCharArray(), 0, value.length());
candidate.endElement(null, null, "string");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
final ConfigurationValue configurationValue = valueCollector.values.get(0);
assertNotNull(configurationValue);
assertTrue(1L == configurationValue.attributeId);
assertEquals("value1", configurationValue.value);
}
@Test
public void simpleIntegerValueTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
1L,
1L,
valueCollector,
attributeResolver);
final String attribute = "param2";
final String value = "22";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "integer", null);
candidate.characters(value.toCharArray(), 0, value.length());
candidate.endElement(null, null, "integer");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
final ConfigurationValue configurationValue = valueCollector.values.get(0);
assertNotNull(configurationValue);
assertTrue(2L == configurationValue.attributeId);
assertEquals("22", configurationValue.value);
}
@Test
public void simpleBooleanValueTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
1L,
1L,
valueCollector,
attributeResolver);
final String attribute = "param3";
final String value = "true";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, value, null);
candidate.endElement(null, null, value);
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
final ConfigurationValue configurationValue = valueCollector.values.get(0);
assertNotNull(configurationValue);
assertTrue(3L == configurationValue.attributeId);
assertEquals("true", configurationValue.value);
}
@Test
public void arrayOfStringValueTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
1L,
1L,
valueCollector,
attributeResolver);
final String attribute = "array1";
final String value1 = "val1";
final String value2 = "val2";
final String value3 = "val3";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "array", null);
candidate.startElement(null, null, "string", null);
candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "string", null);
candidate.characters(value2.toCharArray(), 0, value2.length());
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "string", null);
candidate.characters(value3.toCharArray(), 0, value3.length());
candidate.endElement(null, null, "string");
candidate.endElement(null, null, "array");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
assertTrue(valueCollector.values.size() == 1);
final ConfigurationValue configurationValue1 = valueCollector.values.get(0);
assertEquals("val1,val2,val3", configurationValue1.value);
assertTrue(configurationValue1.listIndex == 0);
}
@Test
public void dictOfValuesTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final List<String> attrNamesCollector = new ArrayList<>();
final Function<String, ConfigurationAttribute> attrConverter = attrName -> {
attrNamesCollector.add(attrName);
return attributeResolver.apply(attrName);
};
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
1L,
1L,
valueCollector,
attrConverter);
final String attribute = "dict1";
final String attr1 = "attr1";
final String attr2 = "attr2";
final String attr3 = "attr3";
final String value1 = "val1";
final String value2 = "2";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attr1.toCharArray(), 0, attr1.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null);
candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "key", null);
candidate.characters(attr2.toCharArray(), 0, attr2.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "integer", null);
candidate.characters(value2.toCharArray(), 0, value2.length());
candidate.endElement(null, null, "integer");
candidate.startElement(null, null, "key", null);
candidate.characters(attr3.toCharArray(), 0, attr3.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "true", null);
candidate.endElement(null, null, "true");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
assertTrue(valueCollector.values.size() == 3);
assertEquals(
"[ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=0, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=0, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=0, value=true]]",
valueCollector.values.toString());
assertEquals(
"[attr1, attr2, attr3]",
attrNamesCollector.toString());
}
@Test
public void arrayOfDictOfValuesTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final List<String> attrNamesCollector = new ArrayList<>();
final Function<String, ConfigurationAttribute> attrConverter = attrName -> {
attrNamesCollector.add(attrName);
return attributeResolver.apply(attrName);
};
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
1L,
1L,
valueCollector,
attrConverter);
final String attribute = "attribute";
final String attr1 = "attr1";
final String attr2 = "attr2";
final String attr3 = "attr3";
final String value1 = "val1";
final String value2 = "2";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "array", null);
for (int i = 0; i < 3; i++) {
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attr1.toCharArray(), 0, attr1.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null);
candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "key", null);
candidate.characters(attr2.toCharArray(), 0, attr2.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "integer", null);
candidate.characters(value2.toCharArray(), 0, value2.length());
candidate.endElement(null, null, "integer");
candidate.startElement(null, null, "key", null);
candidate.characters(attr3.toCharArray(), 0, attr3.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "true", null);
candidate.endElement(null, null, "true");
candidate.endElement(null, null, "dict");
}
candidate.endElement(null, null, "array");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
assertTrue(valueCollector.values.size() == 9);
assertEquals(
"[ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=0, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=0, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=0, value=true], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=1, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=1, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=1, value=true], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=2, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=2, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=2, value=true]]",
valueCollector.values.toString());
assertEquals(
"[attribute.attr1, attribute.attr2, attribute.attr3, attribute.attr1, attribute.attr2, attribute.attr3, attribute.attr1, attribute.attr2, attribute.attr3, attribute]",
attrNamesCollector.toString());
}
private static final class ValueCollector implements Consumer<ConfigurationValue> {
List<ConfigurationValue> values = new ArrayList<>();
@Override
public void accept(final ConfigurationValue value) {
this.values.add(value);
}
}
}
/*
* 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;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.junit.Test;
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 org.mockito.Mockito;
public class ExamConfigImportHandlerTest {
private static final Function<String, ConfigurationAttribute> attributeResolver =
name -> new ConfigurationAttribute(
getId(name),
null, name, (name.contains("array")) ? AttributeType.MULTI_SELECTION : null, null, null, null,
null);
private static final Long getId(final String name) {
try {
return Long.parseLong(String.valueOf(name.charAt(name.length() - 1)));
} catch (final Exception e) {
return -1L;
}
}
@Test
public void simpleStringValueTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
Mockito.mock(Cryptor.class),
1L,
1L,
valueCollector,
attributeResolver);
final String attribute = "param1";
final String value = "value1";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null);
candidate.characters(value.toCharArray(), 0, value.length());
candidate.endElement(null, null, "string");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
final ConfigurationValue configurationValue = valueCollector.values.get(0);
assertNotNull(configurationValue);
assertTrue(1L == configurationValue.attributeId);
assertEquals("value1", configurationValue.value);
}
@Test
public void simpleIntegerValueTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
Mockito.mock(Cryptor.class),
1L,
1L,
valueCollector,
attributeResolver);
final String attribute = "param2";
final String value = "22";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "integer", null);
candidate.characters(value.toCharArray(), 0, value.length());
candidate.endElement(null, null, "integer");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
final ConfigurationValue configurationValue = valueCollector.values.get(0);
assertNotNull(configurationValue);
assertTrue(2L == configurationValue.attributeId);
assertEquals("22", configurationValue.value);
}
@Test
public void simpleBooleanValueTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
Mockito.mock(Cryptor.class),
1L,
1L,
valueCollector,
attributeResolver);
final String attribute = "param3";
final String value = "true";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, value, null);
candidate.endElement(null, null, value);
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
final ConfigurationValue configurationValue = valueCollector.values.get(0);
assertNotNull(configurationValue);
assertTrue(3L == configurationValue.attributeId);
assertEquals("true", configurationValue.value);
}
@Test
public void arrayOfStringValueTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
Mockito.mock(Cryptor.class),
1L,
1L,
valueCollector,
attributeResolver);
final String attribute = "array1";
final String value1 = "val1";
final String value2 = "val2";
final String value3 = "val3";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "array", null);
candidate.startElement(null, null, "string", null);
candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "string", null);
candidate.characters(value2.toCharArray(), 0, value2.length());
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "string", null);
candidate.characters(value3.toCharArray(), 0, value3.length());
candidate.endElement(null, null, "string");
candidate.endElement(null, null, "array");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
assertTrue(valueCollector.values.size() == 1);
final ConfigurationValue configurationValue1 = valueCollector.values.get(0);
assertEquals("val1,val2,val3", configurationValue1.value);
assertTrue(configurationValue1.listIndex == 0);
}
@Test
public void dictOfValuesTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final List<String> attrNamesCollector = new ArrayList<>();
final Function<String, ConfigurationAttribute> attrConverter = attrName -> {
attrNamesCollector.add(attrName);
return attributeResolver.apply(attrName);
};
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
Mockito.mock(Cryptor.class),
1L,
1L,
valueCollector,
attrConverter);
final String attribute = "dict1";
final String attr1 = "attr1";
final String attr2 = "attr2";
final String attr3 = "attr3";
final String value1 = "val1";
final String value2 = "2";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attr1.toCharArray(), 0, attr1.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null);
candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "key", null);
candidate.characters(attr2.toCharArray(), 0, attr2.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "integer", null);
candidate.characters(value2.toCharArray(), 0, value2.length());
candidate.endElement(null, null, "integer");
candidate.startElement(null, null, "key", null);
candidate.characters(attr3.toCharArray(), 0, attr3.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "true", null);
candidate.endElement(null, null, "true");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
assertTrue(valueCollector.values.size() == 3);
assertEquals(
"[ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=0, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=0, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=0, value=true]]",
valueCollector.values.toString());
assertEquals(
"[attr1, attr2, attr3]",
attrNamesCollector.toString());
}
@Test
public void arrayOfDictOfValuesTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector();
final List<String> attrNamesCollector = new ArrayList<>();
final Function<String, ConfigurationAttribute> attrConverter = attrName -> {
attrNamesCollector.add(attrName);
return attributeResolver.apply(attrName);
};
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
Mockito.mock(Cryptor.class),
1L,
1L,
valueCollector,
attrConverter);
final String attribute = "attribute";
final String attr1 = "attr1";
final String attr2 = "attr2";
final String attr3 = "attr3";
final String value1 = "val1";
final String value2 = "2";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "array", null);
for (int i = 0; i < 3; i++) {
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attr1.toCharArray(), 0, attr1.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null);
candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "key", null);
candidate.characters(attr2.toCharArray(), 0, attr2.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "integer", null);
candidate.characters(value2.toCharArray(), 0, value2.length());
candidate.endElement(null, null, "integer");
candidate.startElement(null, null, "key", null);
candidate.characters(attr3.toCharArray(), 0, attr3.length());
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "true", null);
candidate.endElement(null, null, "true");
candidate.endElement(null, null, "dict");
}
candidate.endElement(null, null, "array");
candidate.endElement(null, null, "dict");
candidate.endElement(null, null, "plist");
assertFalse(valueCollector.values.isEmpty());
assertTrue(valueCollector.values.size() == 9);
assertEquals(
"[ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=0, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=0, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=0, value=true], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=1, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=1, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=1, value=true], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=2, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=2, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=2, value=true]]",
valueCollector.values.toString());
assertEquals(
"[attribute.attr1, attribute.attr2, attribute.attr3, attribute.attr1, attribute.attr2, attribute.attr3, attribute.attr1, attribute.attr2, attribute.attr3, attribute]",
attrNamesCollector.toString());
}
private static final class ValueCollector implements Consumer<ConfigurationValue> {
List<ConfigurationValue> values = new ArrayList<>();
@Override
public void accept(final ConfigurationValue value) {
this.values.add(value);
}
}
}

View file

@ -1,328 +1,330 @@
/*
* 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 static org.junit.Assert.assertEquals;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.mockito.Mockito;
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.util.Result;
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.sebconfig.AttributeValueConverter;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverterService;
public class TableConverterTest {
// ********************************
// **** table
// ********************************
private final ConfigurationAttribute TABLE_ATTR =
new ConfigurationAttribute(1L, null, "table", AttributeType.TABLE, null, null, null, null);
private final ConfigurationValue TABLE_VALUE =
new ConfigurationValue(1L, 1L, 1L, 1L, 0, null);
private final ConfigurationAttribute COLUMN_ATTR_1 =
new ConfigurationAttribute(2L, 1L, "attr1", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationAttribute COLUMN_ATTR_2 =
new ConfigurationAttribute(3L, 1L, "attr2", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationValue ROW_1_ATTR_1 =
new ConfigurationValue(2L, 1L, 1L, 2L, 0, "1");
private final ConfigurationValue ROW_1_ATTR_2 =
new ConfigurationValue(3L, 1L, 1L, 3L, 0, "2");
private final ConfigurationValue ROW_2_ATTR_1 =
new ConfigurationValue(4L, 1L, 1L, 2L, 1, "3");
private final ConfigurationValue ROW_2_ATTR_2 =
new ConfigurationValue(5L, 1L, 1L, 3L, 1, "4");
private final Collection<ConfigurationAttribute> TABLE_COLUMNS = Arrays.asList(
this.COLUMN_ATTR_1,
this.COLUMN_ATTR_2);
private final List<List<ConfigurationValue>> TABLE_VALUES = Arrays.asList(
Arrays.asList(this.ROW_1_ATTR_1, this.ROW_1_ATTR_2),
Arrays.asList(this.ROW_2_ATTR_1, this.ROW_2_ATTR_2));
// ********************************
// **** Composite table
// ********************************
private final ConfigurationAttribute COMPOSITE_TABLE_ATTR =
new ConfigurationAttribute(1L, null, "table", AttributeType.COMPOSITE_TABLE, null, null, null, null);
private final ConfigurationValue COMPOSITE_TABLE_VALUE =
new ConfigurationValue(1L, 1L, 1L, 1L, 0, null);
private final ConfigurationAttribute COMPOSITE_COLUMN_ATTR_1 =
new ConfigurationAttribute(2L, 1L, "attr1", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationAttribute COMPOSITE_COLUMN_ATTR_2 =
new ConfigurationAttribute(3L, 1L, "attr2", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationValue COMPOSITE_ROW_1_ATTR_1 =
new ConfigurationValue(2L, 1L, 1L, 2L, 0, "1");
private final ConfigurationValue COMPOSITE_ROW_1_ATTR_2 =
new ConfigurationValue(3L, 1L, 1L, 3L, 0, "2");
private final Collection<ConfigurationAttribute> COMPOSITE_TABLE_ENTRIES = Arrays.asList(
this.COMPOSITE_COLUMN_ATTR_1,
this.COMPOSITE_COLUMN_ATTR_2);
private final List<List<ConfigurationValue>> COMPOSITE_TABLE_VALUES = Arrays.asList(
Arrays.asList(this.COMPOSITE_ROW_1_ATTR_1, this.COMPOSITE_ROW_1_ATTR_2));
@Test
public void testXMLNormalTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals(
"<key>table</key>"
+ "<array>"
+ "<dict>"
+ "<key>attr1</key>"
+ "<string>1</string>"
+ "<key>attr2</key>"
+ "<string>2</string>"
+ "</dict>"
+ "<dict>"
+ "<key>attr1</key>"
+ "<string>3</string>"
+ "<key>attr2</key>"
+ "<string>4</string>"
+ "</dict>"
+ "</array>",
xmlString);
}
@Test
public void testXMLNormalTableNoValues() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals(
"<key>table</key><array />",
xmlString);
}
@Test
public void testXMLCompositeTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.COMPOSITE_TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals(
"<key>table</key>"
+ "<dict>"
+ "<key>attr1</key>"
+ "<string>1</string>"
+ "<key>attr2</key>"
+ "<string>2</string>"
+ "</dict>",
xmlString);
}
@Test
public void testXMLCompositeTableEmpty() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals(
"",
xmlString);
}
@Test
public void testJSONNormalTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : "table":[{"attr1":"1","attr2":"2"},{"attr1":"3","attr2":"4"}]
assertEquals(
"\"table\":[{\"attr1\":\"1\",\"attr2\":\"2\"},{\"attr1\":\"3\",\"attr2\":\"4\"}]",
xmlString);
}
@Test
public void testJSONNormalTableEmpty() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : "table":[]
assertEquals(
"\"table\":[]",
xmlString);
}
@Test
public void testJSONCompositeTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.COMPOSITE_TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : "table":{"attr1":"1","attr2":"2"}
assertEquals(
"\"table\":{\"attr1\":\"1\",\"attr2\":\"2\"}",
xmlString);
}
@Test
public void testJSONCompositeTableEmpty() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected :
assertEquals(
"",
xmlString);
}
private AttributeValueConverterService createAttributeValueConverterService() {
final List<AttributeValueConverter> converter = new ArrayList<>();
converter.add(new StringConverter());
return new AttributeValueConverterServiceImpl(converter);
}
}
/*
* 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 static org.junit.Assert.assertEquals;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import org.junit.Test;
import org.mockito.Mockito;
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.util.Result;
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.sebconfig.AttributeValueConverter;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverterService;
public class TableConverterTest {
// ********************************
// **** table
// ********************************
private final ConfigurationAttribute TABLE_ATTR =
new ConfigurationAttribute(1L, null, "table", AttributeType.TABLE, null, null, null, null);
private final ConfigurationValue TABLE_VALUE =
new ConfigurationValue(1L, 1L, 1L, 1L, 0, null);
private final ConfigurationAttribute COLUMN_ATTR_1 =
new ConfigurationAttribute(2L, 1L, "attr1", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationAttribute COLUMN_ATTR_2 =
new ConfigurationAttribute(3L, 1L, "attr2", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationValue ROW_1_ATTR_1 =
new ConfigurationValue(2L, 1L, 1L, 2L, 0, "1");
private final ConfigurationValue ROW_1_ATTR_2 =
new ConfigurationValue(3L, 1L, 1L, 3L, 0, "2");
private final ConfigurationValue ROW_2_ATTR_1 =
new ConfigurationValue(4L, 1L, 1L, 2L, 1, "3");
private final ConfigurationValue ROW_2_ATTR_2 =
new ConfigurationValue(5L, 1L, 1L, 3L, 1, "4");
private final Collection<ConfigurationAttribute> TABLE_COLUMNS = Arrays.asList(
this.COLUMN_ATTR_1,
this.COLUMN_ATTR_2);
private final List<List<ConfigurationValue>> TABLE_VALUES = Arrays.asList(
Arrays.asList(this.ROW_1_ATTR_1, this.ROW_1_ATTR_2),
Arrays.asList(this.ROW_2_ATTR_1, this.ROW_2_ATTR_2));
// ********************************
// **** Composite table
// ********************************
private final ConfigurationAttribute COMPOSITE_TABLE_ATTR =
new ConfigurationAttribute(1L, null, "table", AttributeType.COMPOSITE_TABLE, null, null, null, null);
private final ConfigurationValue COMPOSITE_TABLE_VALUE =
new ConfigurationValue(1L, 1L, 1L, 1L, 0, null);
private final ConfigurationAttribute COMPOSITE_COLUMN_ATTR_1 =
new ConfigurationAttribute(2L, 1L, "attr1", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationAttribute COMPOSITE_COLUMN_ATTR_2 =
new ConfigurationAttribute(3L, 1L, "attr2", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationValue COMPOSITE_ROW_1_ATTR_1 =
new ConfigurationValue(2L, 1L, 1L, 2L, 0, "1");
private final ConfigurationValue COMPOSITE_ROW_1_ATTR_2 =
new ConfigurationValue(3L, 1L, 1L, 3L, 0, "2");
private final Collection<ConfigurationAttribute> COMPOSITE_TABLE_ENTRIES = Arrays.asList(
this.COMPOSITE_COLUMN_ATTR_1,
this.COMPOSITE_COLUMN_ATTR_2);
private final List<List<ConfigurationValue>> COMPOSITE_TABLE_VALUES = Arrays.asList(
Arrays.asList(this.COMPOSITE_ROW_1_ATTR_1, this.COMPOSITE_ROW_1_ATTR_2));
@Test
public void testXMLNormalTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals(
"<key>table</key>"
+ "<array>"
+ "<dict>"
+ "<key>attr1</key>"
+ "<string>1</string>"
+ "<key>attr2</key>"
+ "<string>2</string>"
+ "</dict>"
+ "<dict>"
+ "<key>attr1</key>"
+ "<string>3</string>"
+ "<key>attr2</key>"
+ "<string>4</string>"
+ "</dict>"
+ "</array>",
xmlString);
}
@Test
public void testXMLNormalTableNoValues() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals(
"<key>table</key><array />",
xmlString);
}
@Test
public void testXMLCompositeTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.COMPOSITE_TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals(
"<key>table</key>"
+ "<dict>"
+ "<key>attr1</key>"
+ "<string>1</string>"
+ "<key>attr2</key>"
+ "<string>2</string>"
+ "</dict>",
xmlString);
}
@Test
public void testXMLCompositeTableEmpty() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals(
"",
xmlString);
}
@Test
public void testJSONNormalTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : "table":[{"attr1":"1","attr2":"2"},{"attr1":"3","attr2":"4"}]
assertEquals(
"\"table\":[{\"attr1\":\"1\",\"attr2\":\"2\"},{\"attr1\":\"3\",\"attr2\":\"4\"}]",
xmlString);
}
@Test
public void testJSONNormalTableEmpty() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : "table":[]
assertEquals(
"\"table\":[]",
xmlString);
}
@Test
public void testJSONCompositeTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.COMPOSITE_TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : "table":{"attr1":"1","attr2":"2"}
assertEquals(
"\"table\":{\"attr1\":\"1\",\"attr2\":\"2\"}",
xmlString);
}
@Test
public void testJSONCompositeTableEmpty() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class);
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class);
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected :
assertEquals(
"",
xmlString);
}
private AttributeValueConverterService createAttributeValueConverterService() {
final ClientCredentialService clientCredentialServiceMock = Mockito.mock(ClientCredentialService.class);
final List<AttributeValueConverter> converter = new ArrayList<>();
converter.add(new StringConverter(clientCredentialServiceMock));
return new AttributeValueConverterServiceImpl(converter);
}
}