From 4fd9f0751b830cb0f5620a6d8056c0d48c1bc98b Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 24 Oct 2024 16:40:12 +0200 Subject: [PATCH] SEBSP-116 added Client Group for alphabetical name --- .../seb/sebserver/gbl/api/POSTMapper.java | 11 +- .../sebserver/gbl/model/exam/ClientGroup.java | 112 +++++++++--------- .../gbl/model/exam/ClientGroupData.java | 26 ++-- .../gbl/model/exam/ClientGroupTemplate.java | 91 +++++++------- .../AlphabeticalNameRangeMatcher.java | 38 ++++++ .../monitoring/ClientGroupMatcherService.java | 2 +- .../gui/content/exam/ClientGroupForm.java | 34 +++++- .../content/exam/ClientGroupTemplateForm.java | 29 +++-- .../sebserver/gui/widget/WidgetFactory.java | 64 ++++------ .../servicelayer/dao/ClientGroupDAO.java | 2 +- .../dao/impl/ClientGroupDAOImpl.java | 25 ++-- .../servicelayer/exam/ExamUtils.java | 35 ++++++ .../exam/impl/ExamTemplateServiceImpl.java | 4 +- .../ScreenProctoringAPIBinding.java | 16 ++- src/main/resources/messages.properties | 12 +- .../integration/UseCasesIntegrationTest.java | 4 +- 16 files changed, 313 insertions(+), 192 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/AlphabeticalNameRangeMatcher.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/POSTMapper.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/POSTMapper.java index 9d873f3fb..e3c20457b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/POSTMapper.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/POSTMapper.java @@ -94,6 +94,15 @@ public String getString(final String name) { return this.params.getFirst(name); } + public Character getCharacter(final String name) { + final String result = getString(name); + if (result == null) { + return null; + } + + return name.charAt(0); + } + public char[] getCharArray(final String name) { final String value = getString(name); if (value == null || value.length() <= 0) { @@ -283,5 +292,5 @@ public String toString() { builder.append("]"); return builder.toString(); } - + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroup.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroup.java index d3b0b97fc..329b45d18 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroup.java @@ -63,6 +63,12 @@ public class ClientGroup implements ClientGroupData, Comparable { @JsonProperty(ATTR_CLIENT_OS) public final ClientOS clientOS; + @JsonProperty(ATTR_NAME_RANGE_START_LETTER) + public final String nameRangeStartLetter; + + @JsonProperty(ATTR_NAME_RANGE_END_LETTER) + public final String nameRangeEndLetter; + @JsonCreator public ClientGroup( @JsonProperty(CLIENT_GROUP.ATTR_ID) final Long id, @@ -73,7 +79,9 @@ public ClientGroup( @JsonProperty(CLIENT_GROUP.ATTR_ICON) final String icon, @JsonProperty(ATTR_IP_RANGE_START) final String ipRangeStart, @JsonProperty(ATTR_IP_RANGE_END) final String ipRangeEnd, - @JsonProperty(ATTR_CLIENT_OS) final ClientOS clientOS) { + @JsonProperty(ATTR_CLIENT_OS) final ClientOS clientOS, + @JsonProperty(ATTR_NAME_RANGE_START_LETTER) final String nameRangeStartLetter, + @JsonProperty(ATTR_NAME_RANGE_END_LETTER) final String nameRangeEndLetter) { super(); this.id = id; @@ -85,6 +93,8 @@ public ClientGroup( this.ipRangeStart = ipRangeStart; this.ipRangeEnd = ipRangeEnd; this.clientOS = clientOS == null ? ClientOS.NONE : clientOS; + this.nameRangeStartLetter = nameRangeStartLetter; + this.nameRangeEndLetter = nameRangeEndLetter; } public ClientGroup( @@ -110,18 +120,33 @@ public ClientGroup( this.ipRangeStart = split[0]; this.ipRangeEnd = split[1]; this.clientOS = ClientOS.NONE; + this.nameRangeStartLetter = null; + this.nameRangeEndLetter = null; break; } case CLIENT_OS: { this.ipRangeStart = null; this.ipRangeEnd = null; this.clientOS = Utils.enumFromString(data, ClientOS.class, ClientOS.NONE); + this.nameRangeStartLetter = null; + this.nameRangeEndLetter = null; + break; + } + case NAME_ALPHABETICAL_RANGE: { + this.ipRangeStart = null; + this.ipRangeEnd = null; + this.clientOS = ClientOS.NONE; + final String[] split = StringUtils.split(data, Constants.EMBEDDED_LIST_SEPARATOR); + this.nameRangeStartLetter = split[0]; + this.nameRangeEndLetter = split[1]; break; } default: { this.ipRangeStart = null; this.ipRangeEnd = null; this.clientOS = ClientOS.NONE; + this.nameRangeStartLetter = null; + this.nameRangeEndLetter = null; break; } } @@ -137,14 +162,12 @@ public ClientGroup(final Long examId, final POSTMapper postParams) { this.ipRangeStart = postParams.getString(ATTR_IP_RANGE_START); this.ipRangeEnd = postParams.getString(ATTR_IP_RANGE_END); this.clientOS = postParams.getEnum(ATTR_CLIENT_OS, ClientOS.class); + this.nameRangeStartLetter = postParams.getString(ATTR_NAME_RANGE_START_LETTER); + this.nameRangeEndLetter = postParams.getString(ATTR_NAME_RANGE_END_LETTER); } public static ClientGroup createNew(final String examId) { - try { - return new ClientGroup(null, Long.parseLong(examId), null, null, null, null, null, null, null); - } catch (final Exception e) { - return new ClientGroup(null, null, null, null, null, null, null, null, null); - } + return new ClientGroup(null, Long.parseLong(examId), null, null, null, null, null, null, null, null, null); } @Override @@ -217,6 +240,16 @@ public ClientOS getClientOS() { return this.clientOS; } + @Override + public String getNameRangeStartLetter() { + return this.nameRangeStartLetter; + } + + @Override + public String getNameRangeEndLetter() { + return this.nameRangeEndLetter; + } + @JsonIgnore public String getData() { switch (this.type) { @@ -226,63 +259,30 @@ public String getData() { case CLIENT_OS: { return this.clientOS.name(); } + case NAME_ALPHABETICAL_RANGE: { + return this.nameRangeStartLetter + Constants.EMBEDDED_LIST_SEPARATOR + this.nameRangeEndLetter; + } default: { return StringUtils.EMPTY; } } } - + @Override public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append("ClientGroup [id="); - builder.append(this.id); - builder.append(", examId="); - builder.append(this.examId); - builder.append(", name="); - builder.append(this.name); - builder.append(", type="); - builder.append(this.type); - builder.append(", color="); - builder.append(this.color); - builder.append(", icon="); - builder.append(this.icon); - builder.append(", ipRangeStart="); - builder.append(this.ipRangeStart); - builder.append(", ipRangeEnd="); - builder.append(this.ipRangeEnd); - builder.append(", clientOS="); - builder.append(this.clientOS); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); - result = prime * result + ((this.type == null) ? 0 : this.type.hashCode()); - return result; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final ClientGroup other = (ClientGroup) obj; - if (this.id == null) { - if (other.id != null) - return false; - } else if (!this.id.equals(other.id)) - return false; - if (this.type != other.type) - return false; - return true; + return "ClientGroup{" + + "id=" + id + + ", examId=" + examId + + ", name='" + name + '\'' + + ", type=" + type + + ", color='" + color + '\'' + + ", icon='" + icon + '\'' + + ", ipRangeStart='" + ipRangeStart + '\'' + + ", ipRangeEnd='" + ipRangeEnd + '\'' + + ", clientOS=" + clientOS + + ", nameRangeStartLetter=" + nameRangeStartLetter + + ", nameRangeEndLetter=" + nameRangeEndLetter + + '}'; } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java index 10b8d3c2b..dab75aa5a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java @@ -13,19 +13,22 @@ /** Overall interface for client group data used either for template or real client groups */ public interface ClientGroupData extends Entity { - public static final String ATTR_IP_RANGE_START = "ipRangeStart"; - public static final String ATTR_IP_RANGE_END = "ipRangeEnd"; - public static final String ATTR_CLIENT_OS = "clientOS"; + String ATTR_IP_RANGE_START = "ipRangeStart"; + String ATTR_IP_RANGE_END = "ipRangeEnd"; + String ATTR_CLIENT_OS = "clientOS"; + String ATTR_NAME_RANGE_START_LETTER = "nameRangeStartLetter"; + String ATTR_NAME_RANGE_END_LETTER = "nameRangeEndLetter"; /** All known and implemented client group types */ - public enum ClientGroupType { + enum ClientGroupType { NONE, IP_V4_RANGE, - CLIENT_OS + CLIENT_OS, + NAME_ALPHABETICAL_RANGE } /** All known and implemented SEB OS types */ - public enum ClientOS { + enum ClientOS { NONE(null), WINDOWS("Windows"), MAC_OS("macOS"), @@ -36,16 +39,15 @@ public enum ClientOS { public final String queryString1; public final String queryString2; - private ClientOS(final String queryString1) { + ClientOS(final String queryString1) { this.queryString1 = queryString1; this.queryString2 = null; } - private ClientOS(final String queryString1, final String queryString2) { + ClientOS(final String queryString1, final String queryString2) { this.queryString1 = queryString1; this.queryString2 = queryString2; } - } Long getId(); @@ -59,7 +61,11 @@ private ClientOS(final String queryString1, final String queryString2) { String getIpRangeStart(); String getIpRangeEnd(); - + ClientOS getClientOS(); + String getNameRangeStartLetter(); + + String getNameRangeEndLetter(); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupTemplate.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupTemplate.java index 0de151896..574489952 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupTemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupTemplate.java @@ -10,6 +10,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Objects; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @@ -61,6 +62,12 @@ public class ClientGroupTemplate implements ClientGroupData { @JsonProperty(ClientGroup.ATTR_CLIENT_OS) public final ClientOS clientOS; + @JsonProperty(ATTR_NAME_RANGE_START_LETTER) + public final String nameRangeStartLetter; + + @JsonProperty(ATTR_NAME_RANGE_END_LETTER) + public final String nameRangeEndLetter; + @JsonCreator public ClientGroupTemplate( @JsonProperty(CLIENT_GROUP.ATTR_ID) final Long id, @@ -71,7 +78,9 @@ public ClientGroupTemplate( @JsonProperty(CLIENT_GROUP.ATTR_ICON) final String icon, @JsonProperty(ClientGroup.ATTR_IP_RANGE_START) final String ipRangeStart, @JsonProperty(ClientGroup.ATTR_IP_RANGE_END) final String ipRangeEnd, - @JsonProperty(ClientGroup.ATTR_CLIENT_OS) final ClientOS clientOS) { + @JsonProperty(ClientGroup.ATTR_CLIENT_OS) final ClientOS clientOS, + @JsonProperty(ATTR_NAME_RANGE_START_LETTER) final String nameRangeStartLetter, + @JsonProperty(ATTR_NAME_RANGE_END_LETTER) final String nameRangeEndLetter) { super(); this.id = id; @@ -83,6 +92,8 @@ public ClientGroupTemplate( this.ipRangeStart = ipRangeStart; this.ipRangeEnd = ipRangeEnd; this.clientOS = clientOS; + this.nameRangeStartLetter = nameRangeStartLetter; + this.nameRangeEndLetter = nameRangeEndLetter; } public ClientGroupTemplate(final Long id, final Long examTemplateId, final POSTMapper postParams) { @@ -96,6 +107,8 @@ public ClientGroupTemplate(final Long id, final Long examTemplateId, final POSTM this.ipRangeStart = postParams.getString(ClientGroup.ATTR_IP_RANGE_START); this.ipRangeEnd = postParams.getString(ClientGroup.ATTR_IP_RANGE_END); this.clientOS = postParams.getEnum(ClientGroup.ATTR_CLIENT_OS, ClientOS.class); + this.nameRangeStartLetter = postParams.getString(ATTR_NAME_RANGE_START_LETTER); + this.nameRangeEndLetter = postParams.getString(ATTR_NAME_RANGE_END_LETTER); } public ClientGroupTemplate(final Long id, final ClientGroupTemplate other) { @@ -109,6 +122,8 @@ public ClientGroupTemplate(final Long id, final ClientGroupTemplate other) { this.ipRangeStart = other.ipRangeStart; this.ipRangeEnd = other.ipRangeEnd; this.clientOS = other.clientOS; + this.nameRangeStartLetter = other.nameRangeStartLetter; + this.nameRangeEndLetter = other.nameRangeEndLetter; } @Override @@ -181,6 +196,16 @@ public ClientOS getClientOS() { return this.clientOS; } + @Override + public String getNameRangeStartLetter() { + return this.nameRangeStartLetter; + } + + @Override + public String getNameRangeEndLetter() { + return this.nameRangeEndLetter; + } + @JsonIgnore public String getData() { switch (this.type) { @@ -198,55 +223,31 @@ public String getData() { @Override public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append("ClientGroupTemplate [id="); - builder.append(this.id); - builder.append(", examTemplateId="); - builder.append(this.examTemplateId); - builder.append(", name="); - builder.append(this.name); - builder.append(", type="); - builder.append(this.type); - builder.append(", color="); - builder.append(this.color); - builder.append(", icon="); - builder.append(this.icon); - builder.append(", ipRangeStart="); - builder.append(this.ipRangeStart); - builder.append(", ipRangeEnd="); - builder.append(this.ipRangeEnd); - builder.append(", clientOS="); - builder.append(this.clientOS); - builder.append("]"); - return builder.toString(); + return "ClientGroupTemplate{" + + "id=" + id + + ", examTemplateId=" + examTemplateId + + ", name='" + name + '\'' + + ", type=" + type + + ", color='" + color + '\'' + + ", icon='" + icon + '\'' + + ", ipRangeStart='" + ipRangeStart + '\'' + + ", ipRangeEnd='" + ipRangeEnd + '\'' + + ", clientOS=" + clientOS + + ", nameRangeStartLetter=" + nameRangeStartLetter + + ", nameRangeEndLetter=" + nameRangeEndLetter + + '}'; } @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); - result = prime * result + ((this.type == null) ? 0 : this.type.hashCode()); - return result; + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final ClientGroupTemplate that = (ClientGroupTemplate) o; + return Objects.equals(id, that.id) && type == that.type; } @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final ClientGroupTemplate other = (ClientGroupTemplate) obj; - if (this.id == null) { - if (other.id != null) - return false; - } else if (!this.id.equals(other.id)) - return false; - if (this.type != other.type) - return false; - return true; + public int hashCode() { + return Objects.hash(id, type); } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/AlphabeticalNameRangeMatcher.java b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/AlphabeticalNameRangeMatcher.java new file mode 100644 index 000000000..9cb5a0104 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/AlphabeticalNameRangeMatcher.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 ETH Zürich, IT Services + * + * 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.monitoring; + +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupData; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +@Lazy +@Component +public class AlphabeticalNameRangeMatcher implements ClientGroupConnectionMatcher { + @Override + public ClientGroupData.ClientGroupType matcherType() { + return ClientGroupData.ClientGroupType.NAME_ALPHABETICAL_RANGE; + } + + @Override + public boolean isInGroup(final ClientConnection clientConnection, final ClientGroup group) { + if (StringUtils.isBlank(clientConnection.userSessionId)) { + return false; + } + + final String start = group.nameRangeStartLetter != null ? group.nameRangeStartLetter : "A"; + final String end = group.nameRangeStartLetter != null ? group.nameRangeStartLetter : "Z"; + + return clientConnection.userSessionId.compareToIgnoreCase(start) >= 0 && + clientConnection.userSessionId.compareToIgnoreCase(end) <= 0; + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/ClientGroupMatcherService.java b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/ClientGroupMatcherService.java index 18e4a6544..54a5dc226 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/ClientGroupMatcherService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/ClientGroupMatcherService.java @@ -30,7 +30,7 @@ public ClientGroupMatcherService(final Collection this.matcher = matcher .stream() .collect(Collectors.toMap( - k -> k.matcherType(), + ClientGroupConnectionMatcher::matcherType, Function.identity())); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupForm.java index bb19f82a4..c77b809c3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupForm.java @@ -67,6 +67,10 @@ public class ClientGroupForm implements TemplateComposer { new LocTextKey("sebserver.exam.clientgroup.form.ipend"); private static final LocTextKey FORM_OS_TYPE_KEY = new LocTextKey("sebserver.exam.clientgroup.form.ostype"); + private static final LocTextKey FORM_UNAME_START_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.usernamestart"); + private static final LocTextKey FORM_UNAME_END_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.usernameend"); private static final String CLIENT_GROUP_TYPE_DESC_PREFIX = "sebserver.exam.clientgroup.type.description."; @@ -207,6 +211,20 @@ public void compose(final PageContext pageContext) { .visibleIf(clientGroup.type == ClientGroupType.CLIENT_OS) .mandatory(!isReadonly)) + .addField(FormBuilder.text( + ClientGroup.ATTR_NAME_RANGE_START_LETTER, + FORM_UNAME_START_KEY, + clientGroup::getNameRangeStartLetter) + .mandatory(!isReadonly) + .visibleIf(clientGroup.type == ClientGroupType.NAME_ALPHABETICAL_RANGE)) + + .addField(FormBuilder.text( + ClientGroup.ATTR_NAME_RANGE_END_LETTER, + FORM_UNAME_END_KEY, + clientGroup::getNameRangeEndLetter) + .mandatory(!isReadonly) + .visibleIf(clientGroup.type == ClientGroupType.NAME_ALPHABETICAL_RANGE)) + .buildFor((isNew) ? restService.getRestCall(NewClientGroup.class) : restService.getRestCall(SaveClientGroup.class)); @@ -228,15 +246,20 @@ public void compose(final PageContext pageContext) { public static void updateForm(final Form form, final I18nSupport i18nSupport) { final String typeValue = form.getFieldValue(Domain.CLIENT_GROUP.ATTR_TYPE); + form.setFieldVisible(false, ClientGroup.ATTR_IP_RANGE_START); + form.setFieldVisible(false, ClientGroup.ATTR_IP_RANGE_END); + form.setFieldVisible(false, ClientGroupTemplate.ATTR_CLIENT_OS); + form.setFieldVisible(false, ClientGroup.ATTR_NAME_RANGE_START_LETTER); + form.setFieldVisible(false, ClientGroup.ATTR_NAME_RANGE_END_LETTER); + if (StringUtils.isNotBlank(typeValue)) { + final String text = i18nSupport.getText(CLIENT_GROUP_TYPE_DESC_PREFIX + typeValue); form.setFieldValue( TYPE_DESCRIPTION_FIELD_NAME, Utils.formatLineBreaks(text)); + final ClientGroupType type = ClientGroupType.valueOf(typeValue); - form.setFieldVisible(false, ClientGroup.ATTR_IP_RANGE_START); - form.setFieldVisible(false, ClientGroup.ATTR_IP_RANGE_END); - form.setFieldVisible(false, ClientGroupTemplate.ATTR_CLIENT_OS); if (type == ClientGroupType.IP_V4_RANGE) { form.setFieldVisible(true, ClientGroup.ATTR_IP_RANGE_START); form.setFieldVisible(true, ClientGroup.ATTR_IP_RANGE_END); @@ -244,10 +267,15 @@ public static void updateForm(final Form form, final I18nSupport i18nSupport) { if (type == ClientGroupType.CLIENT_OS) { form.setFieldVisible(true, ClientGroupTemplate.ATTR_CLIENT_OS); } + if (type == ClientGroupType.NAME_ALPHABETICAL_RANGE) { + form.setFieldVisible(true, ClientGroup.ATTR_NAME_RANGE_START_LETTER); + form.setFieldVisible(true, ClientGroup.ATTR_NAME_RANGE_END_LETTER); + } } else { form.setFieldValue(TYPE_DESCRIPTION_FIELD_NAME, Constants.EMPTY_NOTE); } + } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupTemplateForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupTemplateForm.java index fad167329..7c54749bf 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupTemplateForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupTemplateForm.java @@ -65,6 +65,10 @@ public class ClientGroupTemplateForm implements TemplateComposer { new LocTextKey("sebserver.exam.clientgroup.form.ipend"); private static final LocTextKey FORM_OS_TYPE_KEY = new LocTextKey("sebserver.exam.clientgroup.form.ostype"); + private static final LocTextKey FORM_UNAME_START_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.usernamestart"); + private static final LocTextKey FORM_UNAME_END_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.usernameend"); private static final String CLIENT_GROUP_TYPE_DESC_PREFIX = "sebserver.exam.clientgroup.type.description."; @@ -105,7 +109,7 @@ public void compose(final PageContext pageContext) { // get data or create new. Handle error if happen final ClientGroupTemplate clientGroupTemplate = (isNew) ? new ClientGroupTemplate(null, Long.parseLong(parentEntityKey.modelId), - null, null, null, null, null, null, null) + null, null, null, null, null, null, null, null, null) : restService .getBuilder(GetClientGroupTemplate.class) .withURIVariable(API.PARAM_PARENT_MODEL_ID, parentEntityKey.modelId) @@ -181,26 +185,37 @@ public void compose(final PageContext pageContext) { FORM_IP_START_KEY, clientGroupTemplate::getIpRangeStart) .mandatory(!isReadonly) - .visibleIf(clientGroupTemplate.type != null - && clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE)) + .visibleIf(clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE)) .addField(FormBuilder.text( ClientGroup.ATTR_IP_RANGE_END, FORM_IP_END_KEY, clientGroupTemplate::getIpRangeEnd) .mandatory(!isReadonly) - .visibleIf(clientGroupTemplate.type != null - && clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE)) + .visibleIf(clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE)) .addField(FormBuilder.singleSelection( ClientGroupTemplate.ATTR_CLIENT_OS, FORM_OS_TYPE_KEY, (clientGroupTemplate.clientOS != null) ? clientGroupTemplate.clientOS.name() : null, this.resourceService::clientClientOSResources) - .visibleIf(clientGroupTemplate.type != null - && clientGroupTemplate.type == ClientGroupType.CLIENT_OS) + .visibleIf(clientGroupTemplate.type == ClientGroupType.CLIENT_OS) .mandatory(!isReadonly)) + .addField(FormBuilder.text( + ClientGroup.ATTR_NAME_RANGE_START_LETTER, + FORM_UNAME_START_KEY, + clientGroupTemplate::getNameRangeStartLetter) + .mandatory(!isReadonly) + .visibleIf(clientGroupTemplate.type == ClientGroupType.NAME_ALPHABETICAL_RANGE)) + + .addField(FormBuilder.text( + ClientGroup.ATTR_NAME_RANGE_END_LETTER, + FORM_UNAME_END_KEY, + clientGroupTemplate::getNameRangeEndLetter) + .mandatory(!isReadonly) + .visibleIf(clientGroupTemplate.type == ClientGroupType.NAME_ALPHABETICAL_RANGE)) + .buildFor((isNew) ? restService.getRestCall(NewClientGroupTemplate.class) : restService.getRestCall(SaveClientGroupTemplate.class)); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index e833ea0a4..fc57ec661 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -647,8 +647,7 @@ public ExpandBar expandBarLocalized( expandBar.addListener(SWT.Expand, event -> { try { final Widget expandItem = event.item; - Arrays.asList(expandBar.getItems()) - .stream() + Arrays.stream(expandBar.getItems()) .filter(item -> !item.equals(expandItem)) .forEach(item -> item.setExpanded(false)); } catch (final Exception e) { @@ -835,37 +834,16 @@ public Selection selectionLocalized( final String testKey, final String ariaLabel) { - final Selection selection; - switch (type) { - case SINGLE: - selection = new SingleSelection(parent, SWT.READ_ONLY, testKey); - break; - case SINGLE_COMBO: - selection = new SingleSelection(parent, SWT.NONE, testKey); - break; - case RADIO: - selection = new RadioSelection(parent, testKey); - break; - case MULTI: - selection = new MultiSelection(parent, testKey); - break; - case MULTI_COMBO: - selection = new MultiSelectionCombo( - parent, - this, - testKey, - // NOTE parent would work for firefox but on IE and Chrome only parent.getParent().getParent() works - parent.getParent().getParent()); - break; - case MULTI_CHECKBOX: - selection = new MultiSelectionCheckbox(parent, testKey); - break; - case COLOR: - selection = new ColorSelection(parent, this, testKey); - break; - default: - throw new IllegalArgumentException("Unsupported Selection.Type: " + type); - } + final Selection selection = switch (type) { + case SINGLE -> new SingleSelection(parent, SWT.READ_ONLY, testKey); + case SINGLE_COMBO -> new SingleSelection(parent, SWT.NONE, testKey); + case RADIO -> new RadioSelection(parent, testKey); + case MULTI -> new MultiSelection(parent, testKey); + // NOTE parent would work for firefox but on IE and Chrome only parent.getParent().getParent() works + case MULTI_COMBO -> new MultiSelectionCombo(parent, this, testKey, parent.getParent().getParent()); + case MULTI_CHECKBOX -> new MultiSelectionCheckbox(parent, testKey); + case COLOR -> new ColorSelection(parent, this, testKey); + }; if (itemsSupplier != null) { final Consumer updateFunction = ss -> { @@ -1079,6 +1057,9 @@ public String clientGroupDataToHTML(final ClientGroupData data) { case CLIENT_OS: { return this.i18nSupport.getText(ResourceService.CLIENT_OS_TYPE_PREFIX + data.getClientOS().name()); } + case NAME_ALPHABETICAL_RANGE:{ + return data.getNameRangeStartLetter() + " - " + data.getNameRangeEndLetter(); + } default: { return Constants.EMPTY_NOTE; } @@ -1086,17 +1067,16 @@ public String clientGroupDataToHTML(final ClientGroupData data) { } public static String getTextWithBackgroundHTML(final String text, final String color) { - return new StringBuilder().append("") - .append(text) + : "color: #FFFFFF;") + + "'>" + + text + // .append(" ") - .append("") - .toString(); + ""; } public static String getColorValueHTML(final ClientGroupData data) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientGroupDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientGroupDAO.java index 9314613b1..50f8774c4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientGroupDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientGroupDAO.java @@ -21,7 +21,7 @@ /** Concrete EntityDAO interface of ClientGroup entities */ public interface ClientGroupDAO extends EntityDAO, BulkActionSupportDAO { - public static final String CACHE_NAME_RUNNING_EXAM_CLIENT_GROUP_CACHE = "RUNNING_EXAM_CLIENT_GROUP_CACHE"; + String CACHE_NAME_RUNNING_EXAM_CLIENT_GROUP_CACHE = "RUNNING_EXAM_CLIENT_GROUP_CACHE"; /** Get a collection of all ClientGroup entities for a specified exam. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientGroupDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientGroupDAOImpl.java index 532f0854a..1a567fb42 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientGroupDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientGroupDAOImpl.java @@ -211,24 +211,13 @@ public Set getDependencies(final BulkAction bulkAction) { } // define the select function in case of source type - Function>> selectionFunction; - switch (bulkAction.sourceType) { - case INSTITUTION: - selectionFunction = this::allIdsOfInstitution; - break; - case LMS_SETUP: - selectionFunction = this::allIdsOfLmsSetup; - break; - case USER: - selectionFunction = this::allIdsOfUser; - break; - case EXAM: - selectionFunction = this::allIdsOfExam; - break; - default: - selectionFunction = key -> Result.of(Collections.emptyList()); //empty select function - break; - } + final Function>> selectionFunction = switch (bulkAction.sourceType) { + case INSTITUTION -> this::allIdsOfInstitution; + case LMS_SETUP -> this::allIdsOfLmsSetup; + case USER -> this::allIdsOfUser; + case EXAM -> this::allIdsOfExam; + default -> key -> Result.of(Collections.emptyList()); //empty select function + }; return getDependencies(bulkAction, selectionFunction); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamUtils.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamUtils.java index 90b1d3a94..4ecf4255d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamUtils.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamUtils.java @@ -144,6 +144,10 @@ public static T checkClientGroupConsistency(final T checkClientOS(clientGroup.getClientOS()); break; } + case NAME_ALPHABETICAL_RANGE: { + checkAlphabetConsistency(clientGroup); + break; + } default: { throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( new FieldError( @@ -156,6 +160,37 @@ public static T checkClientGroupConsistency(final T return clientGroup; } + private static void checkAlphabetConsistency(final ClientGroupData group) { + final String nameRangeStartLetter = group.getNameRangeStartLetter(); + final String nameRangeEndLetter = group.getNameRangeEndLetter(); + if (nameRangeStartLetter == null) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_NAME_RANGE_START_LETTER, + "clientGroup:nameRangeStartLetter:mandatory"))); + } + if (nameRangeEndLetter == null) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_NAME_RANGE_END_LETTER, + "clientGroup:nameRangeEndLetter:mandatory"))); + } + if (nameRangeStartLetter.compareToIgnoreCase(nameRangeEndLetter) > 0) { + throw new APIMessage.APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_NAME_RANGE_START_LETTER, + "clientGroup:nameRangeStartLetter:invalidAlphaRange")), + APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_NAME_RANGE_END_LETTER, + "clientGroup:nameRangeEndLetter:invalidAlphaRange"))); + } + } + static void checkIPRange(final String ipRangeStart, final String ipRangeEnd) { final long startIP = Utils.ipToLong(ipRangeStart); if (StringUtils.isBlank(ipRangeStart) || startIP < 0) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java index 70ad5b4b8..5665ee5e2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java @@ -407,7 +407,9 @@ private void createClientGroupFromTemplate(final ClientGroupTemplate template, f template.icon, template.ipRangeStart, template.ipRangeEnd, - template.clientOS)) + template.clientOS, + template.nameRangeStartLetter, + template.nameRangeEndLetter)) .onError( error -> log.error("Failed to automatically create client group from template: {} for exam: {}", template, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java index 8ff1cb2a2..6f26f8970 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java @@ -8,9 +8,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring; -import javax.validation.constraints.Size; import java.util.*; -import java.util.stream.Collectors; import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.exam.SPSAPIAccessData; @@ -343,6 +341,11 @@ Result> startScreenProctoring(final Exam exam) } synchronizeUserAccounts(exam); + + // TODO synchronize groups and check if it still match (what if groups has changed meanwhile) + // If new groups settings do not match old groups + // --> if there are already sessions for any group on SPS, deny change + // --> if there are no session on SPS yet, delete old groups on SPS and create new one and also locally return Collections.emptyList(); } @@ -456,9 +459,8 @@ void deleteSPSUser(final String userUUID) { public void synchronizeGroups(final Exam exam) { try { - // TODO try to sync groups for exam with SPS Service. - // If some group on SPS service has been delete, - // delete (or mark) it also locally + // TODO try to sync groups for exam with SPS Service. If some group on SPS service has been deleted. + // We need a proper strategy to do that and think also about intentional deletion of groups on SPS Service // Set localGroupIds = this.screenProctoringGroupDAO // .getCollectingGroups(exam.id) @@ -670,6 +672,10 @@ Collection reinitializeScreenProctoring(final Exam exam) this.activateScreenProctoring(exam); // recreate groups on SEB Server if needed + // TODO synchronize groups and check if it still match (what if groups has changed meanwhile) + // If new groups settings do not match old groups + // --> if there are already sessions for any group on SPS, deny change + // --> if there are no session on SPS yet, delete old groups on SPS and create new one and also locally try { final Collection groups = new ArrayList<>(); final String groupRequestURI = UriComponentsBuilder diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 6ae273b94..5dc59032b 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -117,7 +117,9 @@ sebserver.form.validation.fieldError.url.noaccess=There has no access been grant sebserver.form.validation.fieldError.thresholdDuplicate=There are duplicate threshold values. sebserver.form.validation.fieldError.thresholdEmpty=There are missing values or colors for the threshold declaration sebserver.form.validation.fieldError.invalidIP=Invalid IP v4. Please enter a valid IP-address (v4) -sebserver.form.validation.fieldError.invalidIPRange=Invalid IP-address range +sebserver.form.validation.fieldError.invalidAlphaRange=Invalid Alphabetical Range +sebserver.form.validation.fieldError.invalidIPRange=Invalid IP-address Range +sebserver.form.validation.fieldError.mandatory=This field is mandatory sebserver.form.validation.fieldError.url.noAccess=Access was denied sebserver.form.validation.fieldError.invalidDateRange=Invalid Date Range sebserver.form.validation.fieldError.endBeforeStart=Invalid Date Range, End before Start @@ -753,9 +755,11 @@ sebserver.exam.indicator.type.description.WLAN_STATUS=This indicator shows the p sebserver.exam.clientgroup.type.IP_V4_RANGE=IP v4 Range sebserver.exam.clientgroup.type.CLIENT_OS=SEB Client OS +sebserver.exam.clientgroup.type.NAME_ALPHABETICAL_RANGE=Alphabetical User Name Range sebserver.exam.clientgroup.type.description.NONE=No Client Group type is selected.
Please select one. sebserver.exam.clientgroup.type.description.IP_V4_RANGE=With IP v4 range groups you can group the SEB client connections within a IP ranges.
Every SEB client that is connected with a given client IP address that is within the
defined range (including start and end address) belongs to this group.
This is mostly useful if you have dedicated managed devices where you know
the IP ranges for e.g. your computer rooms. sebserver.exam.clientgroup.type.description.CLIENT_OS=With SEB Client groups you can group the SEB client connections within the
operating system they are running on.
For this grouping type there is a defined SEB OS type for each
supported OS; Windows, MacOS and iOS.
This is mostly useful if you want to monitor and manage
the SEB clients by different operating system types to give respective support. +sebserver.exam.clientgroup.type.description.NAME_ALPHABETICAL_RANGE=With this group you can define an alphabetical range to collect SEB Clients by user session name.
The range is defined by an inclusive starting letter and an inclusive end letter.
There is no additional check if your defined groups covering the whole alphabet
Furthermore since the user session name can change during the SEB Connection phase, the group belonging of a SEB Client can change too. sebserver.exam.indicator.info.pleaseSelect=At first please select an indicator from the list @@ -810,6 +814,12 @@ sebserver.exam.clientgroup.form.ipend.tooltip=An IP v4 formatted PI address like sebserver.exam.clientgroup.form.ostype=Client OS Type sebserver.exam.clientgroup.form.ostype.tooltip=The operating system type of the machine where SEB is running on +sebserver.exam.clientgroup.form.usernamestart=Start Letter +sebserver.exam.clientgroup.form.usernamestart.tooltip=An alphabetical letter that defines the start of the range (inclusive) +sebserver.exam.clientgroup.form.usernameend=End Letter +sebserver.exam.clientgroup.form.ipend.usernameend=An alphabetical letter that defines the end of the range (inclusive) + + sebserver.exam.indicator.thresholds.list.title=Thresholds sebserver.exam.indicator.thresholds.list.value=Value diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java index 63c4e50e9..a44d51a92 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java @@ -1100,7 +1100,9 @@ public void testUsecase08_CreateExamClientGroup() { newClientGroup.icon, newClientGroup.ipRangeStart, newClientGroup.ipRangeEnd, - newClientGroup.clientOS); + newClientGroup.clientOS, + newClientGroup.nameRangeStartLetter, + newClientGroup.nameRangeEndLetter); final Result savedClientGroupResult = restService .getBuilder(SaveClientGroup.class)