fallback attributes and new password attribute handling
This commit is contained in:
parent
26561288c9
commit
7238369550
52 changed files with 11407 additions and 10482 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
101
src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java
Normal file
101
src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
/ExamDAO.java
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
BIN
src/main/resources/static/images/visibility.png
Normal file
BIN
src/main/resources/static/images/visibility.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 255 B |
BIN
src/main/resources/static/images/visibility_off.png
Normal file
BIN
src/main/resources/static/images/visibility_off.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 290 B |
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue