From 800494b1b72004865c2fefb060e2053cd478b0ce Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 25 Aug 2022 13:28:35 +0200 Subject: [PATCH] SEBSERV-163 validation --- .../seb/sebserver/gbl/api/APIMessage.java | 4 + .../sebserver/gbl/model/exam/ClientGroup.java | 140 ++++++++++- .../gbl/model/exam/ClientGroupData.java | 29 +++ .../gbl/model/exam/ClientGroupTemplate.java | 83 +++++-- .../IPv4RangeClientGroupMatcher.java | 9 +- .../gui/content/action/ActionCategory.java | 1 + .../gui/content/action/ActionDefinition.java | 26 ++ .../activity/PageStateDefinitionImpl.java | 2 + .../content/exam/ClientGroupTemplateForm.java | 234 ++++++++++++++++++ .../gui/content/exam/ExamTemplateForm.java | 106 ++++++++ .../gui/service/ResourceService.java | 15 ++ .../sebserver/gui/widget/WidgetFactory.java | 1 + .../dao/impl/ClientGroupDAOImpl.java | 4 +- .../servicelayer/exam/ExamAdminService.java | 76 ++++++ .../exam/impl/ExamTemplateServiceImpl.java | 4 +- .../weblayer/api/ExamTemplateController.java | 22 +- src/main/resources/messages.properties | 48 +++- .../integration/UseCasesIntegrationTest.java | 4 +- 18 files changed, 762 insertions(+), 46 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupTemplateForm.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java index 37da4881..f7f30c25 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java @@ -250,6 +250,10 @@ public class APIMessage implements Serializable { this.apiMessages = Arrays.asList(apiMessage); } + public APIMessageException(final APIMessage... apiMessage) { + this.apiMessages = Arrays.asList(apiMessage); + } + public APIMessageException(final ErrorMessage errorMessage) { super(errorMessage.systemMessage); this.apiMessages = Arrays.asList(errorMessage.of()); 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 f7ed5eb8..f1b720e4 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 @@ -11,21 +11,28 @@ package ch.ethz.seb.sebserver.gbl.model.exam; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import org.apache.commons.lang3.StringUtils; + import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 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.Domain.CLIENT_GROUP; -import ch.ethz.seb.sebserver.gbl.model.Entity; @JsonIgnoreProperties(ignoreUnknown = true) -public class ClientGroup implements Entity { +public class ClientGroup implements ClientGroupData { public static final String FILTER_ATTR_EXAM_ID = "examId"; + 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"; public enum ClientGroupType { + NONE, IP_V4_RANGE, CLIENT_OS } @@ -52,8 +59,14 @@ public class ClientGroup implements Entity { @JsonProperty(CLIENT_GROUP.ATTR_ICON) public final String icon; - @JsonProperty(CLIENT_GROUP.ATTR_DATA) - public final String data; + @JsonProperty(ATTR_IP_RANGE_START) + public final String ipRangeStart; + + @JsonProperty(ATTR_IP_RANGE_END) + public final String ipRangeEnd; + + @JsonProperty(ATTR_CLIENT_OS) + public final String clientOS; @JsonCreator public ClientGroup( @@ -63,7 +76,9 @@ public class ClientGroup implements Entity { @JsonProperty(CLIENT_GROUP.ATTR_TYPE) final ClientGroupType type, @JsonProperty(CLIENT_GROUP.ATTR_COLOR) final String color, @JsonProperty(CLIENT_GROUP.ATTR_ICON) final String icon, - @JsonProperty(CLIENT_GROUP.ATTR_DATA) final String data) { + @JsonProperty(ATTR_IP_RANGE_START) final String ipRangeStart, + @JsonProperty(ATTR_IP_RANGE_END) final String ipRangeEnd, + @JsonProperty(ATTR_CLIENT_OS) final String clientOS) { super(); this.id = id; @@ -72,7 +87,48 @@ public class ClientGroup implements Entity { this.type = type; this.color = color; this.icon = icon; - this.data = data; + this.ipRangeStart = ipRangeStart; + this.ipRangeEnd = ipRangeEnd; + this.clientOS = clientOS; + } + + public ClientGroup( + final Long id, + final Long examId, + final String name, + final ClientGroupType type, + final String color, + final String icon, + final String data) { + + super(); + this.id = id; + this.examId = examId; + this.name = name; + this.type = type; + this.color = color; + this.icon = icon; + + switch (type) { + case IP_V4_RANGE: { + final String[] split = StringUtils.split(data, Constants.EMBEDDED_LIST_SEPARATOR); + this.ipRangeStart = split[0]; + this.ipRangeEnd = split[1]; + this.clientOS = null; + break; + } + case CLIENT_OS: { + this.ipRangeStart = null; + this.ipRangeEnd = null; + this.clientOS = data; + break; + } + default: { + this.ipRangeStart = null; + this.ipRangeEnd = null; + this.clientOS = null; + } + } } public ClientGroup(final Long examId, final POSTMapper postParams) { @@ -82,7 +138,9 @@ public class ClientGroup implements Entity { this.type = postParams.getEnum(CLIENT_GROUP.ATTR_TYPE, ClientGroupType.class); this.color = postParams.getString(CLIENT_GROUP.ATTR_COLOR); this.icon = postParams.getString(CLIENT_GROUP.ATTR_ICON); - this.data = postParams.getString(CLIENT_GROUP.ATTR_DATA); + this.ipRangeStart = postParams.getString(ATTR_IP_RANGE_START); + this.ipRangeEnd = postParams.getString(ATTR_IP_RANGE_END); + this.clientOS = postParams.getString(ATTR_CLIENT_OS); } @Override @@ -100,6 +158,7 @@ public class ClientGroup implements Entity { return this.name; } + @Override public Long getId() { return this.id; } @@ -108,20 +167,49 @@ public class ClientGroup implements Entity { return this.examId; } + @Override public ClientGroupType getType() { return this.type; } + @Override public String getColor() { return this.color; } + @Override public String getIcon() { return this.icon; } + @Override + public String getIpRangeStart() { + return this.ipRangeStart; + } + + @Override + public String getIpRangeEnd() { + return this.ipRangeEnd; + } + + @Override + public String getClientOS() { + return this.clientOS; + } + + @JsonIgnore public String getData() { - return this.data; + switch (this.type) { + case IP_V4_RANGE: { + return this.ipRangeStart + Constants.EMBEDDED_LIST_SEPARATOR + this.ipRangeEnd; + } + case CLIENT_OS: { + return this.clientOS; + } + default: { + return StringUtils.EMPTY; + } + } } @Override @@ -139,10 +227,42 @@ public class ClientGroup implements Entity { builder.append(this.color); builder.append(", icon="); builder.append(this.icon); - builder.append(", data="); - builder.append(this.data); + 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; + } + } 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 new file mode 100644 index 00000000..d1a939ec --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 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.exam; + +import ch.ethz.seb.sebserver.gbl.model.Entity; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType; + +public interface ClientGroupData extends Entity { + + Long getId(); + + ClientGroupType getType(); + + String getColor(); + + String getIcon(); + + String getIpRangeStart(); + + String getIpRangeEnd(); + + String getClientOS(); +} 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 468d5265..28596402 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 @@ -11,18 +11,21 @@ package ch.ethz.seb.sebserver.gbl.model.exam; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import org.apache.commons.lang3.StringUtils; + import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 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.Domain.CLIENT_GROUP; -import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType; @JsonIgnoreProperties(ignoreUnknown = true) -public class ClientGroupTemplate implements Entity { +public class ClientGroupTemplate implements ClientGroupData { public static final String ATTR_EXAM_TEMPLATE_ID = "examTemplateId"; @@ -47,8 +50,14 @@ public class ClientGroupTemplate implements Entity { @JsonProperty(CLIENT_GROUP.ATTR_ICON) public final String icon; - @JsonProperty(CLIENT_GROUP.ATTR_DATA) - public final String data; + @JsonProperty(ClientGroup.ATTR_IP_RANGE_START) + public final String ipRangeStart; + + @JsonProperty(ClientGroup.ATTR_IP_RANGE_END) + public final String ipRangeEnd; + + @JsonProperty(ClientGroup.ATTR_CLIENT_OS) + public final String clientOS; @JsonCreator public ClientGroupTemplate( @@ -58,7 +67,9 @@ public class ClientGroupTemplate implements Entity { @JsonProperty(CLIENT_GROUP.ATTR_TYPE) final ClientGroupType type, @JsonProperty(CLIENT_GROUP.ATTR_COLOR) final String color, @JsonProperty(CLIENT_GROUP.ATTR_ICON) final String icon, - @JsonProperty(CLIENT_GROUP.ATTR_DATA) final String data) { + @JsonProperty(ClientGroup.ATTR_IP_RANGE_START) final String ipRangeStart, + @JsonProperty(ClientGroup.ATTR_IP_RANGE_END) final String ipRangeEnd, + @JsonProperty(ClientGroup.ATTR_CLIENT_OS) final String clientOS) { super(); this.id = id; @@ -67,7 +78,9 @@ public class ClientGroupTemplate implements Entity { this.type = type; this.color = color; this.icon = icon; - this.data = data; + this.ipRangeStart = ipRangeStart; + this.ipRangeEnd = ipRangeEnd; + this.clientOS = clientOS; } public ClientGroupTemplate(final Long id, final Long examTemplateId, final POSTMapper postParams) { @@ -78,7 +91,9 @@ public class ClientGroupTemplate implements Entity { this.type = postParams.getEnum(CLIENT_GROUP.ATTR_TYPE, ClientGroupType.class); this.color = postParams.getString(CLIENT_GROUP.ATTR_COLOR); this.icon = postParams.getString(CLIENT_GROUP.ATTR_ICON); - this.data = postParams.getString(CLIENT_GROUP.ATTR_DATA); + this.ipRangeStart = postParams.getString(ClientGroup.ATTR_IP_RANGE_START); + this.ipRangeEnd = postParams.getString(ClientGroup.ATTR_IP_RANGE_END); + this.clientOS = postParams.getString(ClientGroup.ATTR_CLIENT_OS); } public ClientGroupTemplate(final Long id, final ClientGroupTemplate other) { @@ -89,7 +104,9 @@ public class ClientGroupTemplate implements Entity { this.type = other.type; this.color = other.color; this.icon = other.icon; - this.data = other.data; + this.ipRangeStart = other.ipRangeStart; + this.ipRangeEnd = other.ipRangeEnd; + this.clientOS = other.clientOS; } @Override @@ -107,34 +124,64 @@ public class ClientGroupTemplate implements Entity { return this.name; } + @Override public Long getId() { return this.id; } - public Long getExamTempateId() { - return this.examTemplateId; - } - + @Override public ClientGroupType getType() { return this.type; } + @Override public String getColor() { return this.color; } + @Override public String getIcon() { return this.icon; } + public Long getExamTemplateId() { + return this.examTemplateId; + } + + @Override + public String getIpRangeStart() { + return this.ipRangeStart; + } + + @Override + public String getIpRangeEnd() { + return this.ipRangeEnd; + } + + @Override + public String getClientOS() { + return this.clientOS; + } + + @JsonIgnore public String getData() { - return this.data; + switch (this.type) { + case IP_V4_RANGE: { + return this.ipRangeStart + Constants.EMBEDDED_LIST_SEPARATOR + this.ipRangeEnd; + } + case CLIENT_OS: { + return this.clientOS; + } + default: { + return StringUtils.EMPTY; + } + } } @Override public String toString() { final StringBuilder builder = new StringBuilder(); - builder.append("ClientGroup [id="); + builder.append("ClientGroupTemplate [id="); builder.append(this.id); builder.append(", examTemplateId="); builder.append(this.examTemplateId); @@ -146,8 +193,12 @@ public class ClientGroupTemplate implements Entity { builder.append(this.color); builder.append(", icon="); builder.append(this.icon); - builder.append(", data="); - builder.append(this.data); + 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(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/IPv4RangeClientGroupMatcher.java b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/IPv4RangeClientGroupMatcher.java index 784396c7..d36dfdb4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/IPv4RangeClientGroupMatcher.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/IPv4RangeClientGroupMatcher.java @@ -8,13 +8,11 @@ package ch.ethz.seb.sebserver.gbl.monitoring; -import org.apache.commons.lang3.StringUtils; 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.Constants; import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; @@ -34,15 +32,14 @@ public class IPv4RangeClientGroupMatcher implements ClientGroupConnectionMatcher @Override public boolean isInGroup(final ClientConnection clientConnection, final ClientGroup group) { try { - final String[] split = StringUtils.split(group.data, Constants.LIST_SEPARATOR); - final long startIPAddress = Utils.ipToLong(split[0]); - final long endIPAddress = Utils.ipToLong(split[1]); + final long startIPAddress = Utils.ipToLong(group.ipRangeStart); + final long endIPAddress = Utils.ipToLong(group.ipRangeEnd); final long inputIPAddress = Utils.ipToLong(clientConnection.clientAddress); return (inputIPAddress >= startIPAddress && inputIPAddress <= endIPAddress); } catch (final Exception e) { - log.error("Failed to verify ip range for group: {} connection: {}", group, clientConnection, e); + log.error("Failed to verify IP range for group: {} connection: {}", group, clientConnection, e); return false; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java index 6594f68e..ada235f3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java @@ -20,6 +20,7 @@ public enum ActionCategory { EXAM_LIST(new LocTextKey("sebserver.exam.list.actions"), 1), EXAM_TEMPLATE_LIST(new LocTextKey("sebserver.examtemplate.list.actions"), 1), INDICATOR_TEMPLATE_LIST(new LocTextKey("sebserver.examtemplate.indicator.list.actions"), 1), + CLIENT_GROUP_TEMPLATE_LIST(new LocTextKey("sebserver.examtemplate.clientgroup.list.actions"), 2), EXAM_CONFIG_MAPPING_LIST(new LocTextKey("sebserver.exam.configuration.list.actions"), 1), INDICATOR_LIST(new LocTextKey("sebserver.exam.indicator.list.actions"), 2), SEB_CLIENT_CONFIG_LIST(new LocTextKey("sebserver.clientconfig.list.actions"), 1), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 586e478c..7f60bd0a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -469,6 +469,32 @@ public enum ActionDefinition { PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW, ActionCategory.FORM), + CLIENT_GROUP_TEMPLATE_NEW( + new LocTextKey("sebserver.examtemplate.clientgroup.action.list.new"), + ImageIcon.CLIENT_GROUP, + PageStateDefinitionImpl.CLIENT_GROUP_TEMPLATE_EDIT, + ActionCategory.CLIENT_GROUP_TEMPLATE_LIST), + CLIENT_GROUP_TEMPLATE_MODIFY_FROM_LIST( + new LocTextKey("sebserver.examtemplate.clientgroup.action.list.modify"), + ImageIcon.EDIT, + PageStateDefinitionImpl.CLIENT_GROUP_TEMPLATE_EDIT, + ActionCategory.CLIENT_GROUP_TEMPLATE_LIST), + CLIENT_GROUP_TEMPLATE_DELETE_FROM_LIST( + new LocTextKey("sebserver.examtemplate.clientgroup.action.list.delete"), + ImageIcon.DELETE, + PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW, + ActionCategory.CLIENT_GROUP_TEMPLATE_LIST), + CLIENT_GROUP_TEMPLATE_SAVE( + new LocTextKey("sebserver.examtemplate.clientgroup.action.save"), + ImageIcon.SAVE, + PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW, + ActionCategory.FORM), + CLIENT_GROUP_TEMPLATE_CANCEL_MODIFY( + new LocTextKey("sebserver.overall.action.modify.cancel"), + ImageIcon.CANCEL, + PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW, + ActionCategory.FORM), + SEB_CLIENT_CONFIG_LIST( new LocTextKey("sebserver.clientconfig.list.title"), PageStateDefinitionImpl.SEB_CLIENT_CONFIG_LIST), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java index 2a485fd2..225ad150 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java @@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gui.content.configs.SEBClientConfigList; import ch.ethz.seb.sebserver.gui.content.configs.SEBExamConfigForm; import ch.ethz.seb.sebserver.gui.content.configs.SEBExamConfigList; import ch.ethz.seb.sebserver.gui.content.configs.SEBSettingsForm; +import ch.ethz.seb.sebserver.gui.content.exam.ClientGroupTemplateForm; import ch.ethz.seb.sebserver.gui.content.exam.ExamForm; import ch.ethz.seb.sebserver.gui.content.exam.ExamList; import ch.ethz.seb.sebserver.gui.content.exam.ExamTemplateForm; @@ -70,6 +71,7 @@ public enum PageStateDefinitionImpl implements PageStateDefinition { EXAM_TEMPLATE_VIEW(Type.LIST_VIEW, ExamTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE), EXAM_TEMPLATE_EDIT(Type.FORM_EDIT, ExamTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE), INDICATOR_TEMPLATE_EDIT(Type.FORM_EDIT, IndicatorTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE), + CLIENT_GROUP_TEMPLATE_EDIT(Type.FORM_EDIT, ClientGroupTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE), SEB_CLIENT_CONFIG_LIST(Type.LIST_VIEW, SEBClientConfigList.class, ActivityDefinition.SEB_CLIENT_CONFIG), SEB_CLIENT_CONFIG_VIEW(Type.FORM_VIEW, SEBClientConfigForm.class, ActivityDefinition.SEB_CLIENT_CONFIG), 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 new file mode 100644 index 00000000..dd52c1e2 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ClientGroupTemplateForm.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2022 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.exam; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.swt.widgets.Composite; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.api.API; +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.exam.ClientGroup; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupTemplate; +import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate; +import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +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.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.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroupTemplate; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.NewClientGroupTemplate; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.SaveClientGroupTemplate; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.template.GetExamTemplate; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; + +@Lazy +@Component +@GuiProfile +public class ClientGroupTemplateForm implements TemplateComposer { + + private static final LocTextKey NEW_CLIENT_GROUP_TILE_TEXT_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.title.new"); + private static final LocTextKey CLIENT_GROUP_TILE_TEXT_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.title"); + private static final LocTextKey FORM_COLOR_TEXT_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.color"); + private static final LocTextKey FORM_TYPE_TEXT_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.type"); + private static final LocTextKey FORM_NAME_TEXT_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.name"); + private static final LocTextKey FORM_EXAM_TEXT_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.exam"); + private static final LocTextKey FORM_DESC_TEXT_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.description"); + private static final LocTextKey FORM_IP_START_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.ipstart"); + private static final LocTextKey FORM_IP_END_KEY = + new LocTextKey("sebserver.exam.clientgroup.form.ipend"); + + private static final String CLIENT_GROUP_TYPE_DESC_PREFIX = + "sebserver.exam.clientgroup.type.description."; + private static final String TYPE_DESCRIPTION_FIELD_NAME = + "typeDescription"; + + private final PageService pageService; + private final ResourceService resourceService; + private final I18nSupport i18nSupport; + + public ClientGroupTemplateForm( + final PageService pageService, + final ResourceService resourceService, + final I18nSupport i18nSupport) { + + super(); + this.pageService = pageService; + this.resourceService = resourceService; + this.i18nSupport = i18nSupport; + } + + @Override + public void compose(final PageContext pageContext) { + final RestService restService = this.resourceService.getRestService(); + final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); + final EntityKey entityKey = pageContext.getEntityKey(); + final EntityKey parentEntityKey = pageContext.getParentEntityKey(); + final boolean isNew = entityKey == null; + final boolean isReadonly = pageContext.isReadonly(); + + final ExamTemplate examTemplate = restService + .getBuilder(GetExamTemplate.class) + .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId) + .call() + .onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error)) + .getOrThrow(); + + // 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) + : restService + .getBuilder(GetClientGroupTemplate.class) + .withURIVariable(API.PARAM_PARENT_MODEL_ID, parentEntityKey.modelId) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .call() + .onError(error -> pageContext.notifyLoadError(EntityType.CLIENT_GROUP, error)) + .getOrThrow(); + + final boolean typeSet = clientGroupTemplate.type != null; + final String typeDescription = (typeSet) + ? Utils.formatLineBreaks( + this.i18nSupport.getText(CLIENT_GROUP_TYPE_DESC_PREFIX + clientGroupTemplate.type.name())) + : Constants.EMPTY_NOTE; + + // new PageContext with actual EntityKey + final PageContext formContext = pageContext.withEntityKey(clientGroupTemplate.getEntityKey()); + + // the default page layout + final LocTextKey titleKey = (isNew) + ? NEW_CLIENT_GROUP_TILE_TEXT_KEY + : CLIENT_GROUP_TILE_TEXT_KEY; + final Composite content = widgetFactory.defaultPageLayout( + formContext.getParent(), + titleKey); + + final FormHandle formHandle = this.pageService.formBuilder( + formContext.copyOf(content)) + .readonly(isReadonly) + .putStaticValueIf(() -> !isNew, + Domain.CLIENT_GROUP.ATTR_ID, + clientGroupTemplate.getModelId()) + .putStaticValue( + Domain.EXAM.ATTR_INSTITUTION_ID, + String.valueOf(examTemplate.getInstitutionId())) + .putStaticValue( + IndicatorTemplate.ATTR_EXAM_TEMPLATE_ID, + parentEntityKey.getModelId()) + + .addField(FormBuilder.text( + Domain.EXAM_TEMPLATE.ATTR_NAME, + FORM_EXAM_TEXT_KEY, + examTemplate.name) + .readonly(true)) + .addField(FormBuilder.text( + Domain.CLIENT_GROUP.ATTR_NAME, + FORM_NAME_TEXT_KEY, + clientGroupTemplate.name) + .mandatory(!isReadonly)) + + .addField(FormBuilder.colorSelection( + Domain.CLIENT_GROUP.ATTR_COLOR, + FORM_COLOR_TEXT_KEY, + clientGroupTemplate.color) + .withEmptyCellSeparation(false)) + + .addField(FormBuilder.singleSelection( + Domain.CLIENT_GROUP.ATTR_TYPE, + FORM_TYPE_TEXT_KEY, + (clientGroupTemplate.type != null) ? clientGroupTemplate.type.name() : null, + this.resourceService::clientGroupTypeResources) + .withSelectionListener(this::updateForm) + .mandatory(!isReadonly)) + + .addField(FormBuilder.text( + TYPE_DESCRIPTION_FIELD_NAME, + FORM_DESC_TEXT_KEY, + typeDescription) + .asArea() + //.asHTML(true) + .readonly(true)) + + .addField(FormBuilder.text( + ClientGroup.ATTR_IP_RANGE_START, + FORM_IP_START_KEY, + clientGroupTemplate::getIpRangeStart) + .visibleIf(clientGroupTemplate.type != null + && clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE)) + + .addField(FormBuilder.text( + ClientGroup.ATTR_IP_RANGE_END, + FORM_IP_END_KEY, + clientGroupTemplate::getIpRangeEnd) + .visibleIf(clientGroupTemplate.type != null + && clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE)) + + .buildFor((isNew) + ? restService.getRestCall(NewClientGroupTemplate.class) + : restService.getRestCall(SaveClientGroupTemplate.class)); + + // propagate content actions to action-pane + this.pageService.pageActionBuilder(formContext.clearEntityKeys()) + + .newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_SAVE) + .withEntityKey(parentEntityKey) + .withExec(formHandle::processFormSave) + .ignoreMoveAwayFromEdit() + .publishIf(() -> !isReadonly) + + .newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_CANCEL_MODIFY) + .withEntityKey(parentEntityKey) + .withExec(this.pageService.backToCurrentFunction()) + .publishIf(() -> !isReadonly); + + } + + private void updateForm(final Form form) { + final String typeValue = form.getFieldValue(Domain.CLIENT_GROUP.ATTR_TYPE); + if (StringUtils.isNotBlank(typeValue)) { + final String text = this.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); + if (type == ClientGroupType.IP_V4_RANGE) { + form.setFieldVisible(true, ClientGroup.ATTR_IP_RANGE_START); + form.setFieldVisible(true, ClientGroup.ATTR_IP_RANGE_END); + } + + } else { + form.setFieldValue(TYPE_DESCRIPTION_FIELD_NAME, Constants.EMPTY_NOTE); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java index 9aab1b69..3c041a8c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java @@ -20,6 +20,7 @@ import ch.ethz.seb.sebserver.gbl.api.API; 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.exam.ClientGroupTemplate; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate; import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate; @@ -37,6 +38,8 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.DeleteClientGroupTemplate; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroupTemplatePage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.DeleteIndicatorTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.GetIndicatorTemplatePage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.template.DeleteExamTemplate; @@ -88,6 +91,21 @@ public class ExamTemplateForm implements TemplateComposer { private static final LocTextKey INDICATOR_EMPTY_LIST_MESSAGE = new LocTextKey("sebserver.examtemplate.indicator.list.empty"); + private final static LocTextKey CLIENT_GROUP_LIST_TITLE_KEY = + new LocTextKey("sebserver.examtemplate.clientgroup.list.title"); + private final static LocTextKey CLIENT_GROUP_LIST_TITLE_TOOLTIP_KEY = + new LocTextKey("sebserver.examtemplate.clientgroup.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX); + private final static LocTextKey CLIENT_GROUP_TYPE_COLUMN_KEY = + new LocTextKey("sebserver.examtemplate.clientgroup.list.column.type"); + private final static LocTextKey CLIENT_GROUP_NAME_COLUMN_KEY = + new LocTextKey("sebserver.examtemplate.clientgroup.list.column.name"); + private final static LocTextKey CLIENT_GROUP_COLOR_COLUMN_KEY = + new LocTextKey("sebserver.examtemplate.clientgroup.list.column.color"); + private final static LocTextKey CLIENT_GROUP_EMPTY_SELECTION_TEXT_KEY = + new LocTextKey("sebserver.examtemplate.clientgroup.list.pleaseSelect"); + private static final LocTextKey CLIENT_GROUP_EMPTY_LIST_MESSAGE = + new LocTextKey("sebserver.examtemplate.clientgroup.list.empty"); + private static final LocTextKey EXAM_TEMPLATE_DELETE_CONFIRM = new LocTextKey("sebserver.examtemplate.form.action.delete.confirm"); @@ -307,6 +325,74 @@ public class ExamTemplateForm implements TemplateComposer { .newAction(ActionDefinition.INDICATOR_TEMPLATE_NEW) .withParentEntityKey(entityKey) .publishIf(() -> userGrantCheck.m()); + + // List of Client Groups + this.widgetFactory.addFormSubContextHeader( + content, + CLIENT_GROUP_LIST_TITLE_KEY, + CLIENT_GROUP_LIST_TITLE_TOOLTIP_KEY); + + final EntityTable clientGroupTable = + this.pageService + .entityTableBuilder(this.restService.getRestCall(GetClientGroupTemplatePage.class)) + .withRestCallAdapter(builder -> builder.withURIVariable( + API.PARAM_PARENT_MODEL_ID, + entityKey.modelId)) + .withEmptyMessage(CLIENT_GROUP_EMPTY_LIST_MESSAGE) + .withMarkup() + .withPaging(100) + .hideNavigation() + .withColumn(new ColumnDefinition<>( + Domain.CLIENT_GROUP.ATTR_NAME, + CLIENT_GROUP_NAME_COLUMN_KEY, + ClientGroupTemplate::getName) + .widthProportion(2)) + .withColumn(new ColumnDefinition<>( + Domain.CLIENT_GROUP.ATTR_TYPE, + CLIENT_GROUP_TYPE_COLUMN_KEY, + this::clientGroupTypeName) + .widthProportion(1)) + .withColumn(new ColumnDefinition<>( + Domain.CLIENT_GROUP.ATTR_COLOR, + CLIENT_GROUP_COLOR_COLUMN_KEY, + ClientGroupTemplate::getColor) + .asMarkup() + .widthProportion(4)) + .withDefaultActionIf( + () -> userGrantCheck.m(), + () -> actionBuilder + .newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_MODIFY_FROM_LIST) + .withParentEntityKey(entityKey) + .create()) + + .withSelectionListener(this.pageService.getSelectionPublisher( + pageContext, + ActionDefinition.CLIENT_GROUP_TEMPLATE_MODIFY_FROM_LIST, + ActionDefinition.CLIENT_GROUP_TEMPLATE_DELETE_FROM_LIST)) + + .compose(pageContext.copyOf(content)); + + actionBuilder + + .newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_MODIFY_FROM_LIST) + .withParentEntityKey(entityKey) + .withSelect( + indicatorTable::getMultiSelection, + PageAction::applySingleSelectionAsEntityKey, + CLIENT_GROUP_EMPTY_SELECTION_TEXT_KEY) + .publishIf(() -> userGrantCheck.m() && clientGroupTable.hasAnyContent(), false) + + .newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_DELETE_FROM_LIST) + .withEntityKey(entityKey) + .withSelect( + indicatorTable::getMultiSelection, + this::deleteSelectedClientGroup, + CLIENT_GROUP_EMPTY_SELECTION_TEXT_KEY) + .publishIf(() -> userGrantCheck.m() && clientGroupTable.hasAnyContent(), false) + + .newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_NEW) + .withParentEntityKey(entityKey) + .publishIf(() -> userGrantCheck.m()); } } @@ -329,6 +415,17 @@ public class ExamTemplateForm implements TemplateComposer { return action; } + private PageAction deleteSelectedClientGroup(final PageAction action) { + final EntityKey entityKey = action.getEntityKey(); + final EntityKey indicatorKey = action.getSingleSelection(); + this.resourceService.getRestService() + .getBuilder(DeleteClientGroupTemplate.class) + .withURIVariable(API.PARAM_PARENT_MODEL_ID, entityKey.modelId) + .withURIVariable(API.PARAM_MODEL_ID, indicatorKey.modelId) + .call(); + return action; + } + private String indicatorTypeName(final IndicatorTemplate indicator) { if (indicator.type == null) { return Constants.EMPTY_NOTE; @@ -338,4 +435,13 @@ public class ExamTemplateForm implements TemplateComposer { .getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name()); } + private String clientGroupTypeName(final ClientGroupTemplate clientGroup) { + if (clientGroup.type == null) { + return Constants.EMPTY_NOTE; + } + + return this.resourceService.getI18nSupport() + .getText(ResourceService.EXAM_CLIENT_GROUP_TYPE_PREFIX + clientGroup.type.name()); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index f4049428..93793cef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -36,6 +36,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Activatable; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.EntityName; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType; @@ -123,6 +124,7 @@ public class ResourceService { public static final String EXAM_TYPE_PREFIX = "sebserver.exam.type."; public static final String USERACCOUNT_ROLE_PREFIX = "sebserver.useraccount.role."; public static final String EXAM_INDICATOR_TYPE_PREFIX = "sebserver.exam.indicator.type."; + public static final String EXAM_CLIENT_GROUP_TYPE_PREFIX = "sebserver.exam.clientgroup.type."; public static final String LMSSETUP_TYPE_PREFIX = "sebserver.lmssetup.type."; 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."; @@ -260,6 +262,19 @@ public class ResourceService { .collect(Collectors.toList()); } + public List> clientGroupTypeResources() { + return Arrays.stream(ClientGroupType.values()) + .filter(type -> type != ClientGroupType.NONE) + .map(type -> new Tuple3<>( + type.name(), + this.i18nSupport.getText(EXAM_CLIENT_GROUP_TYPE_PREFIX + type.name(), type.name()), + Utils.formatLineBreaks(this.i18nSupport.getText( + EXAM_CLIENT_GROUP_TYPE_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX, + StringUtils.EMPTY)))) + .sorted(RESOURCE_COMPARATOR_TUPLE_3) + .collect(Collectors.toList()); + } + public List> examConfigurationSelectionResources() { return getExamConfigurationSelection() .getOr(Collections.emptyList()) 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 7ee7f9f6..a9f15f78 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 @@ -130,6 +130,7 @@ public class WidgetFactory { INSTITUTION("institution.png"), LMS_SETUP("lmssetup.png"), INDICATOR("indicator.png"), + CLIENT_GROUP("indicator.png"), TEMPLATE("template.png"), DISABLE("disable.png"), SEND_QUIT("send-quit.png"), 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 abd4e3f9..0927fad9 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 @@ -129,7 +129,7 @@ public class ClientGroupDAOImpl implements ClientGroupDAO { data.type.name(), data.color, data.icon, - data.data); + data.getData()); this.clientGroupRecordMapper.insert(newRecord); @@ -151,7 +151,7 @@ public class ClientGroupDAOImpl implements ClientGroupDAO { data.type.name(), data.color, data.icon, - data.data); + data.getData()); this.clientGroupRecordMapper.updateByPrimaryKeySelective(newRecord); return this.clientGroupRecordMapper.selectByPrimaryKey(data.id); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java index 1ed5da11..e9864917 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java @@ -18,10 +18,14 @@ import org.springframework.validation.FieldError; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupData; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; public interface ExamAdminService { @@ -139,4 +143,76 @@ public interface ExamAdminService { } } + /** Used to check client group consistency for a given ClientGroup. + * Checks if correct entries for specific type + * + * If a check fails, the methods throws a APIMessageException with a FieldError to notify the caller + * + * @param clientGroup ClientGroup instance to check */ + public static void checkClientGroupConsistency(final ClientGroupData clientGroup) { + final ClientGroupType type = clientGroup.getType(); + if (type == null || type == ClientGroupType.NONE) { + throw new APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + Domain.CLIENT_GROUP.ATTR_TYPE, + "clientGroup:type:mandatory"))); + } + + switch (type) { + case IP_V4_RANGE: { + checkIPRange(clientGroup.getIpRangeStart(), clientGroup.getIpRangeEnd()); + break; + } + case CLIENT_OS: { + checkClientOS(clientGroup.getClientOS()); + break; + } + default: { + throw new APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + Domain.CLIENT_GROUP.ATTR_TYPE, + "clientGroup:type:typeInvalid"))); + } + } + } + + static void checkIPRange(final String ipRangeStart, final String ipRangeEnd) { + final long startIP = Utils.ipToLong(ipRangeStart); + if (startIP < 0) { + throw new APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_IP_RANGE_START, + "clientGroup:ipRangeStart:invalidIP"))); + } + final long endIP = Utils.ipToLong(ipRangeEnd); + if (endIP < 0) { + throw new APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_IP_RANGE_END, + "clientGroup:ipRangeEnd:invalidIP"))); + } + + if (endIP <= startIP) { + throw new APIMessageException(APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_IP_RANGE_START, + "clientGroup:ipRangeStart:invalidIPRange")), + APIMessage.fieldValidationError( + new FieldError( + Domain.CLIENT_GROUP.TYPE_NAME, + ClientGroup.ATTR_IP_RANGE_END, + "clientGroup:ipRangeEnd:invalidIPRange"))); + } + + } + + static void checkClientOS(final String clientOS) { + // TODO + } + } 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 af47b13a..87b069cd 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 @@ -363,7 +363,9 @@ public class ExamTemplateServiceImpl implements ExamTemplateService { template.type, template.color, template.icon, - template.data)) + template.ipRangeStart, + template.ipRangeEnd, + template.clientOS)) .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/weblayer/api/ExamTemplateController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java index 22638eb0..d9cdd72d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java @@ -192,10 +192,7 @@ public class ExamTemplateController extends EntityController { - ExamAdminService.checkThresholdConsistency(indicator.thresholds); - return indicator; - }) + .map(this::checkIndicatorConsistency) .flatMap(this.examTemplateDAO::createNewIndicatorTemplate) .flatMap(this.userActivityLogDAO::logCreate) .getOrThrow(); @@ -217,10 +214,7 @@ public class ExamTemplateController extends EntityController { - ExamAdminService.checkThresholdConsistency(indicator.thresholds); - return indicator; - }) + .map(this::checkIndicatorConsistency) .flatMap(this.examTemplateDAO::saveIndicatorTemplate) .flatMap(this.userActivityLogDAO::logModify) .getOrThrow(); @@ -343,6 +337,7 @@ public class ExamTemplateController extends EntityController
On the exam monitoring view a cell of the indicator is displayed in the specified color when the defined threshold value is reached sebserver.exam.indicator.thresholds.select.color=Please select a color +sebserver.exam.clientgroup.form.title=Client Group +sebserver.exam.clientgroup.form.title.subtitle= +sebserver.exam.clientgroup.form.title.new=Add Client Group +sebserver.exam.clientgroup.form.exam=Exam +sebserver.exam.clientgroup.form.exam.tooltip=The exam this client group belongs to +sebserver.exam.clientgroup.form.name=Name +sebserver.exam.clientgroup.form.name.tooltip=The name of the client group.

This name is also displayed in the column cell of in the exam monitoring +sebserver.exam.clientgroup.form.type=Type +sebserver.exam.clientgroup.form.type.tooltip=The type of the client group

There are only a set of defined client group types to choose from.
Choose one to see a detailed description for each client group type below. +sebserver.exam.clientgroup.form.description=Type Description +sebserver.exam.clientgroup.form.description.tooltip=A detailed description of the selected client group type +sebserver.exam.clientgroup.form.color=Color +sebserver.exam.clientgroup.form.color.tooltip=The color that is displayed on the exam monitoring for this client group +sebserver.exam.clientgroup.form.color.action=Please select a color +sebserver.exam.clientgroup.form.ipstart=Start of IP range +sebserver.exam.clientgroup.form.ipstart.tooltip=An IP v4 formatted PI address like 111.111.111.111 that defines the start of the range +sebserver.exam.clientgroup.form.ipend=End of IP range +sebserver.exam.clientgroup.form.ipend.tooltip=An IP v4 formatted PI address like 111.111.111.111 that defines the end of the range + + sebserver.exam.indicator.thresholds.list.title=Thresholds sebserver.exam.indicator.thresholds.list.value=Value sebserver.exam.indicator.thresholds.list.value.tooltip=The threshold value @@ -1748,7 +1776,7 @@ sebserver.examtemplate.form.action.edit=Edit Exam Template sebserver.examtemplate.form.action.delete=Delete Exam Template sebserver.examtemplate.form.action.delete.confirm=Are you sure to delete this exam template?

Please note that a reference from a exam that uses this template will also be deleted but the exam itself is not affected. -sebserver.examtemplate.indicator.list.actions= +sebserver.examtemplate.indicator.list.actions=  sebserver.examtemplate.indicator.list.title=Indicators sebserver.examtemplate.indicator.list.title.tooltip=A list of indicators that will automatically be created when importing a exam with this template sebserver.examtemplate.indicator.list.column.type=Type @@ -1760,12 +1788,28 @@ sebserver.examtemplate.indicator.list.column.thresholds.tooltip=The thresholds o sebserver.examtemplate.indicator.list.empty=There is currently no indicator defined for this exam template. Please create a new one sebserver.examtemplate.indicator.list.pleaseSelect=At first please select an indicator from the list +sebserver.examtemplate.clientgroup.list.actions=  +sebserver.examtemplate.clientgroup.list.title=Client Groups +sebserver.examtemplate.clientgroup.list.title.tooltip=A list of client groups that will automatically be created when importing a exam with this template +sebserver.examtemplate.clientgroup.list.column.type=Type +sebserver.examtemplate.clientgroup.list.column.type.tooltip=The type of the client group +sebserver.examtemplate.clientgroup.list.column.name=Name +sebserver.examtemplate.clientgroup.list.column.name.tooltip=The name of the client group +sebserver.examtemplate.clientgroup.list.column.color=Color +sebserver.examtemplate.clientgroup.list.column.color.tooltip=The color of the client group +sebserver.examtemplate.clientgroup.list.empty=There is currently no client group defined for this exam template. Please create a new one +sebserver.examtemplate.clientgroup.list.pleaseSelect=At first please select an client group from the list + sebserver.examtemplate.indicator.action.save=Save Indicator -sebserver.examtemplate.indicator.list.actions= sebserver.examtemplate.indicator.action.list.new=Add Indicator sebserver.examtemplate.indicator.action.list.modify=Edit Indicator sebserver.examtemplate.indicator.action.list.delete=Delete Indicator +sebserver.examtemplate.clientgroup.action.save=Save Client Group +sebserver.examtemplate.clientgroup.action.list.new=Add Client Group +sebserver.examtemplate.clientgroup.action.list.modify=Edit Client Group +sebserver.examtemplate.clientgroup.action.list.delete=Delete Client Group + sebserver.examtemplate.proctoring.actions.open=Proctoring Settings ################################ 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 d2adbcd9..4b9e5925 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 @@ -1086,7 +1086,9 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { newClientGroup.type, "000002", newClientGroup.icon, - newClientGroup.data); + newClientGroup.ipRangeStart, + newClientGroup.ipRangeEnd, + newClientGroup.clientOS); final Result savedClientGroupResult = restService .getBuilder(SaveClientGroup.class)