From 95e3a70d346291858460686e536a2d602a0581c2 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 2 Nov 2022 12:28:54 +0100 Subject: [PATCH] SEBSERV-347 implementation & testing --- .../ch/ethz/seb/sebserver/gbl/api/API.java | 1 + .../model/session/ClientConnectionData.java | 80 +----- .../model/session/ClientMonitoringData.java | 64 +---- .../session/ClientMonitoringDataView.java | 13 - .../gbl/model/session/ClientStaticData.java | 76 ++++++ .../IPv4RangeClientGroupMatcher.java | 6 +- .../MonitoringSEBConnectionData.java | 8 +- .../MonitoringStaticClientData.java | 47 ++++ .../ch/ethz/seb/sebserver/gbl/util/Utils.java | 10 +- .../remote/webservice/api/RestCall.java | 2 - .../GetMonitoringStaticClientData.java | 41 +++ .../session/ClientConnectionTable.java | 253 +++++++++--------- .../session/ExamSessionService.java | 14 + .../impl/ClientConnectionDataInternal.java | 57 +++- .../session/impl/ExamSessionServiceImpl.java | 38 ++- .../InternalClientConnectionDataFactory.java | 18 +- .../impl/SEBClientConnectionServiceImpl.java | 8 +- .../api/ExamMonitoringController.java | 30 +++ .../gbl/model/ModelObjectJSONGenerator.java | 12 +- 19 files changed, 478 insertions(+), 300 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientStaticData.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringStaticClientData.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetMonitoringStaticClientData.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index e8f203c3..a3680a71 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -193,6 +193,7 @@ public final class API { public static final String EXAM_MONITORING_ENDPOINT = "/monitoring"; public static final String EXAM_MONITORING_FULLPAGE = "/fullpage"; + public static final String EXAM_MONITORING_STATIC_CLIENT_DATA = "/static-client-data"; public static final String EXAM_MONITORING_INSTRUCTION_ENDPOINT = "/instruction"; public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification"; public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java index 42745f48..adfe03c4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java @@ -9,15 +9,10 @@ package ch.ethz.seb.sebserver.gbl.model.session; import java.util.Collection; -import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.BooleanUtils; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -27,9 +22,7 @@ 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.model.GrantEntity; -import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; -import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue; import ch.ethz.seb.sebserver.gbl.monitoring.SimpleIndicatorValue; import ch.ethz.seb.sebserver.gbl.util.Utils; @@ -48,7 +41,7 @@ public class ClientConnectionData implements GrantEntity { @JsonProperty(ATTR_INDICATOR_VALUE) public final List indicatorValues; @JsonProperty(ATTR_CLIENT_GROUPS) - public Set groups = null; + public final Set groups; public final Boolean missingPing; public final Boolean pendingNotification; @@ -58,30 +51,26 @@ public class ClientConnectionData implements GrantEntity { @JsonProperty(ATTR_MISSING_PING) final Boolean missingPing, @JsonProperty(ATTR_PENDING_NOTIFICATION) final Boolean pendingNotification, @JsonProperty(ATTR_CLIENT_CONNECTION) final ClientConnection clientConnection, - @JsonProperty(ATTR_INDICATOR_VALUE) final Collection indicatorValues) { + @JsonProperty(ATTR_INDICATOR_VALUE) final Collection indicatorValues, + @JsonProperty(ATTR_CLIENT_GROUPS) final Set groups) { this.missingPing = missingPing; this.pendingNotification = pendingNotification; this.clientConnection = clientConnection; this.indicatorValues = Utils.immutableListOf(indicatorValues); + this.groups = (groups == null) ? null : Utils.immutableSetOf(groups); } public ClientConnectionData( final ClientConnection clientConnection, - final List indicatorValues) { + final List indicatorValues, + final Set groups) { this.missingPing = null; this.pendingNotification = Boolean.FALSE; this.clientConnection = clientConnection; this.indicatorValues = Utils.immutableListOf(indicatorValues); - } - - @JsonIgnore - public void addToClientGroup(final ClientGroup group) { - if (this.groups == null) { - this.groups = new HashSet<>(1); - } - this.groups.add(group.id); + this.groups = (groups == null) ? null : Utils.immutableSetOf(groups); } @JsonIgnore @@ -221,59 +210,4 @@ public class ClientConnectionData implements GrantEntity { return builder.toString(); } - /** This is a wrapper for the live monitoring data view of this client connection data */ - @JsonIgnore - public final ClientMonitoringDataView monitoringDataView = new ClientMonitoringDataView() { - - @Override - public Long getId() { - return ClientConnectionData.this.clientConnection.id; - } - - @Override - public ConnectionStatus getStatus() { - return ClientConnectionData.this.clientConnection.status; - } - - @Override - public String getConnectionToken() { - // TODO Auto-generated method stub - return ClientConnectionData.this.clientConnection.connectionToken; - } - - @Override - public String getUserSessionId() { - return ClientConnectionData.this.clientConnection.userSessionId; - } - - @Override - public String getInfo() { - return ClientConnectionData.this.clientConnection.info; - } - - @Override - public Map getIndicatorValues() { - return ClientConnectionData.this.indicatorValues - .stream() - .collect(Collectors.toMap( - iv -> iv.getIndicatorId(), - iv -> IndicatorValue.getDisplayValue(iv))); - } - - @Override - public Set getGroups() { - return ClientConnectionData.this.groups; - } - - @Override - public boolean isMissingPing() { - return BooleanUtils.isTrue(ClientConnectionData.this.missingPing); - } - - @Override - public boolean isPendingNotification() { - return BooleanUtils.isTrue(ClientConnectionData.this.pendingNotification); - } - }; - } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringData.java index b1ef1535..fd2f52cc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringData.java @@ -10,43 +10,34 @@ package ch.ethz.seb.sebserver.gbl.model.session; import java.util.Map; import java.util.Objects; -import java.util.Set; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; +@JsonIgnoreProperties(ignoreUnknown = true) public class ClientMonitoringData implements ClientMonitoringDataView { public final Long id; public final ConnectionStatus status; - public final String connectionToken; - public final String userSessionId; - public final String info; public final Map indicatorVals; - public final Set groups; public final boolean missingPing; public final boolean pendingNotification; + @JsonCreator public ClientMonitoringData( @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) final Long id, @JsonProperty(ATTR_STATUS) final ConnectionStatus status, - @JsonProperty(ATTR_CONNECTION_TOKEN) final String connectionToken, - @JsonProperty(ATTR_EXAM_USER_SESSION_ID) final String userSessionId, - @JsonProperty(ATTR_INFO) final String info, @JsonProperty(ATTR_INDICATOR_VALUES) final Map indicatorVals, - @JsonProperty(ATTR_CLIENT_GROUPS) final Set groups, @JsonProperty(ATTR_MISSING_PING) final boolean missingPing, @JsonProperty(ATTR_PENDING_NOTIFICATION) final boolean pendingNotification) { this.id = id; this.status = status; - this.connectionToken = connectionToken; - this.userSessionId = userSessionId; - this.info = info; this.indicatorVals = indicatorVals; - this.groups = groups; this.missingPing = missingPing; this.pendingNotification = pendingNotification; } @@ -61,31 +52,11 @@ public class ClientMonitoringData implements ClientMonitoringDataView { return this.status; } - @Override - public String getConnectionToken() { - return this.connectionToken; - } - - @Override - public String getUserSessionId() { - return this.userSessionId; - } - - @Override - public String getInfo() { - return this.info; - } - @Override public Map getIndicatorValues() { return this.indicatorVals; } - @Override - public Set getGroups() { - return this.groups; - } - @Override public boolean isMissingPing() { return this.missingPing; @@ -96,33 +67,6 @@ public class ClientMonitoringData implements ClientMonitoringDataView { return this.pendingNotification; } - public boolean dataEquals(final ClientMonitoringData other) { - if (other == null) { - return true; - } - if (this.connectionToken == null) { - if (other.connectionToken != null) - return false; - } else if (!this.connectionToken.equals(other.connectionToken)) - return false; - - if (this.status != other.status) - return false; - - if (this.userSessionId == null) { - if (other.userSessionId != null) - return false; - } else if (!this.userSessionId.equals(other.userSessionId)) { - return false; - } - - if (!Objects.equals(this.groups, other.groups)) { - return false; - } - - return true; - } - public boolean indicatorValuesEquals(final ClientMonitoringData other) { return Objects.equals(this.indicatorVals, other.indicatorVals); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringDataView.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringDataView.java index 3d5c02d5..e361744c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringDataView.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringDataView.java @@ -11,7 +11,6 @@ package ch.ethz.seb.sebserver.gbl.model.session; import java.util.Collections; import java.util.EnumSet; import java.util.Map; -import java.util.Set; import java.util.function.Predicate; import com.fasterxml.jackson.annotation.JsonProperty; @@ -36,21 +35,9 @@ public interface ClientMonitoringDataView { @JsonProperty(ATTR_STATUS) ConnectionStatus getStatus(); - @JsonProperty(ATTR_CONNECTION_TOKEN) - String getConnectionToken(); - - @JsonProperty(ATTR_EXAM_USER_SESSION_ID) - String getUserSessionId(); - - @JsonProperty(ATTR_INFO) - String getInfo(); - @JsonProperty(ATTR_INDICATOR_VALUES) Map getIndicatorValues(); - @JsonProperty(ATTR_CLIENT_GROUPS) - Set getGroups(); - @JsonProperty(ATTR_MISSING_PING) boolean isMissingPing(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientStaticData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientStaticData.java new file mode 100644 index 00000000..9ee0008c --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientStaticData.java @@ -0,0 +1,76 @@ +/* + * 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.session; + +import java.util.Collections; +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import ch.ethz.seb.sebserver.gbl.model.Domain; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClientStaticData { + + public static final ClientStaticData NULL_DATA = + new ClientStaticData(-1L, null, null, null, Collections.emptySet()); + + @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) + public final Long id; + + @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN) + public final String connectionToken; + + @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID) + public final String userSessionId; + + @JsonProperty(ClientConnection.ATTR_INFO) + public final String info; + + @JsonProperty(ClientConnectionData.ATTR_CLIENT_GROUPS) + public final Set groups; + + @JsonCreator + public ClientStaticData( + @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) final Long id, + @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN) final String connectionToken, + @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID) final String userSessionId, + @JsonProperty(ClientConnection.ATTR_INFO) final String info, + @JsonProperty(ClientConnectionData.ATTR_CLIENT_GROUPS) final Set groups) { + + this.id = id; + this.connectionToken = connectionToken; + this.userSessionId = userSessionId; + this.info = info; + this.groups = groups; + } + + public Long getId() { + return this.id; + } + + public String getConnectionToken() { + return this.connectionToken; + } + + public String getUserSessionId() { + return this.userSessionId; + } + + public String getInfo() { + return this.info; + } + + public Set getGroups() { + return this.groups; + } + +} 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 03dcc456..4cd343bc 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 @@ -39,7 +39,11 @@ public class IPv4RangeClientGroupMatcher implements ClientGroupConnectionMatcher 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: {}, error", + group, + clientConnection, + e.getMessage()); return false; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringSEBConnectionData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringSEBConnectionData.java index 9c357342..cf25d1a2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringSEBConnectionData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringSEBConnectionData.java @@ -24,14 +24,16 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringDataView; @JsonIgnoreProperties(ignoreUnknown = true) public class MonitoringSEBConnectionData { - public static final String ATTR_CONNECTIONS = "connections"; - public static final String ATTR_STATUS_MAPPING = "statusMapping"; - public static final String ATTR_CLIENT_GROUP_MAPPING = "clientGroupMapping"; + public static final String ATTR_CONNECTIONS = "cons"; + public static final String ATTR_STATUS_MAPPING = "sm"; + public static final String ATTR_CLIENT_GROUP_MAPPING = "cgm"; @JsonProperty(ATTR_CONNECTIONS) public final Collection monitoringData; + @JsonProperty(ATTR_STATUS_MAPPING) public final int[] connectionsPerStatus; + @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) public final Map connectionsPerClientGroup; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringStaticClientData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringStaticClientData.java new file mode 100644 index 00000000..80a4e0a6 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringStaticClientData.java @@ -0,0 +1,47 @@ +/* + * 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.monitoring; + +import java.util.Collection; +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import ch.ethz.seb.sebserver.gbl.model.session.ClientStaticData; +import ch.ethz.seb.sebserver.gbl.util.Utils; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class MonitoringStaticClientData { + + public static final String ATTR_STATIC_CONNECTION_DATA = "staticClientConnectionData"; + public static final String ATTR_DUPLICATIONS = "duplications"; + + @JsonProperty(ATTR_STATIC_CONNECTION_DATA) + public final Collection staticClientConnectionData; + @JsonProperty(ATTR_DUPLICATIONS) + public final Set duplications; + + public MonitoringStaticClientData( + @JsonProperty(ATTR_STATIC_CONNECTION_DATA) final Collection staticClientConnectionData, + @JsonProperty(ATTR_DUPLICATIONS) final Set duplications) { + + this.staticClientConnectionData = Utils.immutableCollectionOf(staticClientConnectionData); + this.duplications = Utils.immutableSetOf(duplications); + } + + public Collection getStaticClientConnectionData() { + return this.staticClientConnectionData; + } + + public Set getDuplications() { + return this.duplications; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java index 1fe189d1..ad553cc5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java @@ -775,7 +775,7 @@ public final class Utils { try { return ipToLong(InetAddress.getByName(ipV4Address)); } catch (final UnknownHostException e) { - log.error("Failed to convert IPv4 address: {}", ipV4Address, e); + log.error("Failed to convert IPv4 address: {}, error: ", ipV4Address, e.getMessage()); return -1L; } } @@ -805,4 +805,12 @@ public final class Utils { return false; } + public static Long toLong(final String longValue) { + try { + return Long.valueOf(longValue); + } catch (final Exception e) { + return null; + } + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java index f7787246..8f1dd4c2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java @@ -132,8 +132,6 @@ public abstract class RestCall { return Result.ofEmpty(); } - System.out.println("************** size = " + responseEntity.getBody().length()); - return Result.of(RestCall.this.jsonMapper.readValue( responseEntity.getBody(), RestCall.this.typeKey.typeRef)); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetMonitoringStaticClientData.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetMonitoringStaticClientData.java new file mode 100644 index 00000000..8c3644e0 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetMonitoringStaticClientData.java @@ -0,0 +1,41 @@ +/* + * 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.service.remote.webservice.api.session; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringStaticClientData; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class GetMonitoringStaticClientData extends RestCall { + + public GetMonitoringStaticClientData() { + super(new TypeKey<>( + CallType.GET_LIST, + null, + new TypeReference() { + }), + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_MONITORING_ENDPOINT + + API.PARENT_MODEL_ID_VAR_PATH_SEGMENT + + API.EXAM_MONITORING_STATIC_CLIENT_DATA); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 5fbd8ffa..12264b00 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -15,7 +15,6 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -38,30 +37,36 @@ import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; 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.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; -import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringData; import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringDataView; +import ch.ethz.seb.sebserver.gbl.model.session.ClientStaticData; import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue; +import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringStaticClientData; +import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Tuple; 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.PageService; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetMonitoringStaticClientData; import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate { + private static final Logger log = LoggerFactory.getLogger(ClientConnectionTable.class); + private static final int BOTTOM_PADDING = 20; //private static final int[] TABLE_PROPORTIONS = new int[] { 3, 3, 2, 1 }; //private static final int NUMBER_OF_NONE_INDICATOR_COLUMNS = 3; @@ -100,7 +105,8 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private boolean needsSort = false; private LinkedHashMap tableMapping; private final Set toDelete = new HashSet<>(); - private final MultiValueMap sessionIds; + private final Set toUpdateStatic = new HashSet<>(); + private final Set duplicates = new HashSet<>(); private final Color darkFontColor; private final Color lightFontColor; @@ -194,7 +200,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } this.tableMapping = new LinkedHashMap<>(); - this.sessionIds = new LinkedMultiValueMap<>(); this.table.layout(); } @@ -244,8 +249,9 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate final UpdatableTableItem updatableTableItem = new ArrayList<>(this.tableMapping.values()) .get(selectionIndices[i]); - if (filter.test(updatableTableItem.monitoringData)) { - result.add(updatableTableItem.monitoringData.connectionToken); + if (filter.test(updatableTableItem.monitoringData) + && updatableTableItem.staticData != ClientStaticData.NULL_DATA) { + result.add(updatableTableItem.staticData.connectionToken); } } return result; @@ -253,9 +259,8 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate return this.tableMapping .values() .stream() - .map(item -> item.monitoringData) - .filter(filter) - .map(ClientMonitoringData::getConnectionToken) + .filter(item -> filter.test(item.monitoringData) && item.staticData != ClientStaticData.NULL_DATA) + .map(item -> item.staticData.connectionToken) .collect(Collectors.toSet()); } } @@ -304,7 +309,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate (updatableTableItem.connectionId != null) ? String.valueOf(updatableTableItem.connectionId) : null, - updatableTableItem.monitoringData.connectionToken); + updatableTableItem.staticData.connectionToken); } public void forceUpdateAll() { @@ -329,33 +334,32 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate this.toDelete.addAll(this.tableMapping.keySet()); } + this.toUpdateStatic.clear(); monitoringStatus.getConnectionData() .forEach(data -> { final UpdatableTableItem tableItem = this.tableMapping.computeIfAbsent( data.id, UpdatableTableItem::new); - tableItem.push(data); + if (tableItem.push(data)) { + this.toUpdateStatic.add(data.id); + } if (needsSync) { this.toDelete.remove(data.id); } }); + if (!this.toUpdateStatic.isEmpty()) { + fetchStaticClientConnectionData(); + } + if (!this.toDelete.isEmpty()) { - this.toDelete.forEach(id -> { - final UpdatableTableItem item = this.tableMapping.remove(id); - if (item != null) { - final List list = this.sessionIds.get(item.monitoringData.userSessionId); - if (list != null) { - list.remove(id); - } - } - }); + this.toDelete.forEach(id -> this.tableMapping.remove(id)); monitoringStatus.resetFilterChanged(); this.toDelete.clear(); } this.forceUpdateAll = false; - this.needsSort = sizeChanged; + this.needsSort = this.needsSort || sizeChanged; updateGUI(); } @@ -447,9 +451,9 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private boolean dataChanged = false; private boolean indicatorValueChanged = false; private ClientMonitoringData monitoringData; + private ClientStaticData staticData = ClientStaticData.NULL_DATA; private int thresholdsWeight; private int[] indicatorWeights = null; - private boolean duplicateChecked = false; UpdatableTableItem(final Long connectionId) { this.connectionId = connectionId; @@ -457,6 +461,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } private void update(final TableItem tableItem, final boolean force) { + updateDuplicateColor(tableItem); if (force || this.dataChanged) { updateData(tableItem); } @@ -483,13 +488,26 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate ClientConnectionTable.this.localizedClientConnectionStatusNameFunction .apply(this.monitoringData)); } + if (this.monitoringData != null) { updateConnectionStatusColor(tableItem); - updateDuplicateColor(tableItem); updateNotifications(tableItem); } } + private void updateDuplicateColor(final TableItem tableItem) { + //System.out.println("ClientConnectionTable.this.duplicates : " + ClientConnectionTable.this.duplicates); + if (ClientConnectionTable.this.duplicates.contains(this.connectionId) && + tableItem.getBackground(0) != ClientConnectionTable.this.colorData.color3) { + tableItem.setBackground(0, ClientConnectionTable.this.colorData.color3); + tableItem.setForeground(0, ClientConnectionTable.this.lightFontColor); + } else if (!ClientConnectionTable.this.duplicates.contains(this.connectionId) && + tableItem.getBackground(0) == ClientConnectionTable.this.colorData.color3) { + tableItem.setBackground(0, null); + tableItem.setForeground(0, ClientConnectionTable.this.darkFontColor); + } + } + private void updateNotifications(final TableItem tableItem) { if (BooleanUtils.isTrue(this.monitoringData.pendingNotification)) { tableItem.setImage(0, @@ -509,29 +527,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate tableItem.setForeground(index, statusTextColor); } - private void updateDuplicateColor(final TableItem tableItem) { - - tableItem.setBackground(0, null); - tableItem.setForeground(0, ClientConnectionTable.this.darkFontColor); - - if (!this.duplicateChecked) { - return; - } - - if (this.monitoringData != null - && StringUtils.isNotBlank(this.monitoringData.userSessionId)) { - final List list = - ClientConnectionTable.this.sessionIds.get(this.monitoringData.userSessionId); - if (list != null && list.size() > 1) { - tableItem.setBackground(0, ClientConnectionTable.this.colorData.color3); - tableItem.setForeground(0, ClientConnectionTable.this.lightFontColor); - } else { - tableItem.setBackground(0, null); - tableItem.setForeground(0, ClientConnectionTable.this.darkFontColor); - } - } - } - private Consumer> indicatorUpdate(final TableItem tableItem) { return entry -> { final Long id = entry.getKey(); @@ -541,10 +536,11 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate return; } - updateIndicatorWeight(displayValue, indicatorData); - if (!this.monitoringData.status.clientActiveStatus) { - tableItem.setText(indicatorData.tableIndex, displayValue); + final String value = (indicatorData.indicator.type.showOnlyInActiveState) + ? Constants.EMPTY_NOTE + : displayValue; + tableItem.setText(indicatorData.tableIndex, value); tableItem.setBackground(indicatorData.tableIndex, indicatorData.defaultColor); tableItem.setForeground(indicatorData.tableIndex, indicatorData.defaultTextColor); } else { @@ -571,38 +567,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate .entrySet() .stream() .forEach(indicatorUpdate(tableItem)); - -// for (int i = 0; i < this.monitoringData.indicatorVals.size(); i++) { -// final IndicatorValue indicatorValue = this.monitoringData.indicatorValues.get(i); -// final IndicatorData indicatorData = ClientConnectionTable.this.indicatorMapping -// .get(indicatorValue.getIndicatorId()); -// -// if (indicatorData == null) { -// continue; -// } -// -// if (!this.connectionData.clientConnection.status.clientActiveStatus) { -// final String value = (indicatorData.indicator.type.showOnlyInActiveState) -// ? Constants.EMPTY_NOTE -// : IndicatorValue.getDisplayValue(indicatorValue, indicatorData.indicator.type); -// tableItem.setText(indicatorData.tableIndex, value); -// tableItem.setBackground(indicatorData.tableIndex, indicatorData.defaultColor); -// tableItem.setForeground(indicatorData.tableIndex, indicatorData.defaultTextColor); -// } else { -// tableItem.setText(indicatorData.tableIndex, IndicatorValue.getDisplayValue( -// indicatorValue, -// indicatorData.indicator.type)); -// final int weight = this.indicatorWeights[indicatorData.index]; -// if (weight >= 0 && weight < indicatorData.thresholdColor.length) { -// final ThresholdColor thresholdColor = indicatorData.thresholdColor[weight]; -// tableItem.setBackground(indicatorData.tableIndex, thresholdColor.color); -// tableItem.setForeground(indicatorData.tableIndex, thresholdColor.textColor); -// } else { -// tableItem.setBackground(indicatorData.tableIndex, indicatorData.defaultColor); -// tableItem.setForeground(indicatorData.tableIndex, indicatorData.defaultTextColor); -// } -// } -// } } @Override @@ -650,8 +614,8 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } String getConnectionInfo() { - if (this.monitoringData != null && this.monitoringData.info != null) { - return this.monitoringData.info; + if (this.staticData != null && this.staticData.info != null) { + return this.staticData.info; } return Constants.EMPTY_NOTE; } @@ -659,7 +623,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private String getGroupInfo() { final StringBuilder sb = new StringBuilder(); ClientConnectionTable.this.clientGroupMapping.keySet().stream().forEach(key -> { - if (this.monitoringData.groups != null && this.monitoringData.groups.contains(key)) { + if (this.staticData.groups != null && this.staticData.groups.contains(key)) { final ClientGroup clientGroup = ClientConnectionTable.this.clientGroupMapping.get(key); sb.append(WidgetFactory.getTextWithBackgroundHTML(clientGroup.name, clientGroup.color)); } @@ -673,16 +637,17 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } String getConnectionIdentifier() { - if (this.monitoringData != null && this.monitoringData.userSessionId != null) { - return this.monitoringData.userSessionId; + if (this.staticData != null && this.staticData.userSessionId != null) { + return this.staticData.userSessionId; } return "--"; } - void push(final ClientMonitoringData monitoringData) { + boolean push(final ClientMonitoringData monitoringData) { this.dataChanged = this.monitoringData == null || - !this.monitoringData.dataEquals(monitoringData); + this.monitoringData.status != monitoringData.status || + this.monitoringData.missingPing != monitoringData.missingPing; this.indicatorValueChanged = this.monitoringData == null || (this.monitoringData.status.clientActiveStatus && !this.monitoringData.indicatorValuesEquals(monitoringData)); @@ -697,55 +662,89 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate if (this.indicatorWeights == null) { this.indicatorWeights = new int[ClientConnectionTable.this.indicatorMapping.size()]; } - -// for (int i = 0; i < monitoringData.indicatorValues.size(); i++) { -// final IndicatorValue indicatorValue = connectionData.indicatorValues.get(i); -// final IndicatorData indicatorData = -// ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getIndicatorId()); -// -// if (indicatorData != null) { -// updateIndicatorWeight(indicatorValue, indicatorData); -// } -// } - + if (this.indicatorValueChanged) { + updateIndicatorWeight(); + } this.monitoringData = monitoringData; - if (!this.duplicateChecked && - this.monitoringData.status != ConnectionStatus.DISABLED && - StringUtils.isNotBlank(monitoringData.userSessionId)) { - - ClientConnectionTable.this.sessionIds.add( - monitoringData.userSessionId, - this.connectionId); - this.duplicateChecked = true; - } + return this.staticData == null + || this.staticData == ClientStaticData.NULL_DATA + || this.dataChanged + || this.monitoringData.status.connectingStatus + || StringUtils.isBlank(this.staticData.userSessionId); } - private void updateIndicatorWeight( - final String indicatorValue, - final IndicatorData indicatorData) { + void push(final ClientStaticData staticData) { + this.staticData = staticData; + this.dataChanged = true; + } - final int indicatorWeight = IndicatorData.getWeight( - indicatorData, - IndicatorValue.getFromDisplayValue(indicatorValue)); - if (this.indicatorWeights[indicatorData.index] != indicatorWeight) { - ClientConnectionTable.this.needsSort = true; - this.thresholdsWeight -= (indicatorData.indicator.type.inverse) - ? indicatorData.indicator.thresholds.size() - - this.indicatorWeights[indicatorData.index] - : this.indicatorWeights[indicatorData.index]; - this.indicatorWeights[indicatorData.index] = indicatorWeight; - this.thresholdsWeight += (indicatorData.indicator.type.inverse) - ? indicatorData.indicator.thresholds.size() - - this.indicatorWeights[indicatorData.index] - : this.indicatorWeights[indicatorData.index]; + private void updateIndicatorWeight() { + if (this.monitoringData == null) { + return; } + + this.monitoringData.indicatorVals.entrySet().stream().forEach(entry -> { + final Long id = entry.getKey(); + final String displayValue = entry.getValue(); + final IndicatorData indicatorData = ClientConnectionTable.this.indicatorMapping.get(id); + if (indicatorData == null) { + return; + } + + final int indicatorWeight = IndicatorData.getWeight( + indicatorData, + IndicatorValue.getFromDisplayValue(displayValue)); + if (this.indicatorWeights[indicatorData.index] != indicatorWeight) { + ClientConnectionTable.this.needsSort = true; + this.thresholdsWeight -= (indicatorData.indicator.type.inverse) + ? indicatorData.indicator.thresholds.size() + - this.indicatorWeights[indicatorData.index] + : this.indicatorWeights[indicatorData.index]; + this.indicatorWeights[indicatorData.index] = indicatorWeight; + this.thresholdsWeight += (indicatorData.indicator.type.inverse) + ? indicatorData.indicator.thresholds.size() + - this.indicatorWeights[indicatorData.index] + : this.indicatorWeights[indicatorData.index]; + } + }); } private ClientConnectionTable getOuterType() { return ClientConnectionTable.this; } + } + private void fetchStaticClientConnectionData() { + final String ids = this.toUpdateStatic + .stream() + .map(String::valueOf) + .reduce("", (acc, str) -> acc + str + Constants.LIST_SEPARATOR, (acc1, acc2) -> acc1 + acc2); + + final Result call = this.pageService + .getRestService() + .getBuilder(GetMonitoringStaticClientData.class) + .withFormParam(API.PARAM_MODEL_ID_LIST, ids) + .withURIVariable(API.PARAM_PARENT_MODEL_ID, this.exam.getModelId()) + .call(); + + if (call.hasError()) { + log.error("Failed to get client connection static data for: {}", ids, call.getError()); + } else { + final MonitoringStaticClientData monitoringStaticClientData = call.get(); + this.duplicates.clear(); + this.duplicates.addAll(monitoringStaticClientData.duplications); + monitoringStaticClientData.staticClientConnectionData + .stream() + .forEach(staticData -> { + final UpdatableTableItem updatableTableItem = this.tableMapping.get(staticData.id); + if (updatableTableItem != null) { + updatableTableItem.push(staticData); + } else { + log.error("Failed to find table entry for static data: {}", staticData); + } + }); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java index db5b2915..bbfe94df 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java @@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import java.io.OutputStream; import java.util.Collection; +import java.util.Set; import java.util.function.Predicate; import org.springframework.cache.CacheManager; @@ -20,6 +21,7 @@ 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.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; +import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringStaticClientData; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; @@ -184,6 +186,18 @@ public interface ExamSessionService { final Long examId, final Predicate filter); + /** Get SEB client connection statically data for the given exam and list of connection ids. + * This is useful if you have monitoring real-time data as MonitoringSEBConnectionData for example and you need to + * complete this data with the more static client connection data of SEB client, one can reload the static data like + * so. + * + * @param examId the exam identifier + * @param connectionIds Set of client connection identifiers (modelIds) + * @return Result refer to the MonitoringStaticSEBConnectionData or to an error when happened */ + Result getMonitoringSEBConnectionStaticData( + final Long examId, + final Set connectionIds); + /** Gets all connection tokens of client connection that are in ACTIVE state and related to a specified exam * from persistence storage without caching involved. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java index 2d239ee8..5c312e6d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java @@ -13,7 +13,11 @@ import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.lang3.BooleanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,8 +25,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; 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.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; +import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringDataView; +import ch.ethz.seb.sebserver.gbl.model.session.ClientStaticData; +import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.PendingNotificationIndication; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.PingIntervalClientIndicator; @@ -39,9 +47,10 @@ public class ClientConnectionDataInternal extends ClientConnectionData { protected ClientConnectionDataInternal( final ClientConnection clientConnection, final PendingNotificationIndication pendingNotificationIndication, - final List clientIndicators) { + final List clientIndicators, + final Set groups) { - super(clientConnection, clientIndicators); + super(clientConnection, clientIndicators, groups); this.pendingNotificationIndication = pendingNotificationIndication; this.indicatorMapping = new EnumMap<>(EventType.class); @@ -100,4 +109,48 @@ public class ClientConnectionDataInternal extends ClientConnectionData { .isPresent(); } + /** This is a wrapper for the live monitoring data view of this client connection data */ + @JsonIgnore + public final ClientMonitoringDataView monitoringDataView = new ClientMonitoringDataView() { + + @Override + public Long getId() { + return ClientConnectionDataInternal.this.clientConnection.id; + } + + @Override + public ConnectionStatus getStatus() { + return ClientConnectionDataInternal.this.clientConnection.status; + } + + @Override + public Map getIndicatorValues() { + return ClientConnectionDataInternal.this.indicatorValues + .stream() + .collect(Collectors.toMap( + iv -> iv.getIndicatorId(), + iv -> IndicatorValue.getDisplayValue(iv))); + } + + @Override + public boolean isMissingPing() { + return BooleanUtils.isTrue(getMissingPing()); + } + + @Override + public boolean isPendingNotification() { + return BooleanUtils.isTrue(pendingNotification()); + } + }; + + /** This is a static monitoring connection data wrapper/holder */ + @JsonIgnore + public final ClientStaticData clientStaticData = + new ClientStaticData( + ClientConnectionDataInternal.this.clientConnection.id, + ClientConnectionDataInternal.this.clientConnection.connectionToken, + ClientConnectionDataInternal.this.clientConnection.userSessionId, + ClientConnectionDataInternal.this.clientConnection.info, + this.groups); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index 67532af2..80d346f8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -44,6 +45,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringDataView; import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; +import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringStaticClientData; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; @@ -425,7 +427,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { .getConnectionTokens(examId) .getOrThrow() .stream() - .map(token -> getConnectionDataInternal(token)) + .map(this::getConnectionDataInternal) .filter(Objects::nonNull) .map(c -> { statusMapping[c.clientConnection.status.code]++; @@ -443,6 +445,23 @@ public class ExamSessionServiceImpl implements ExamSessionService { }); } + @Override + public synchronized Result getMonitoringSEBConnectionStaticData( + final Long examId, + final Set connectionIds) { + + this.duplicateCheck.clear(); + this.duplicates.clear(); + return this.clientConnectionDAO + .getConnectionTokens(examId) + .map(tokens -> tokens.stream() + .map(this::getForTokenAndCheckDuplication) + .filter(ccd -> connectionIds.contains(ccd.clientConnection.id)) + .map(ccd -> ccd.clientStaticData) + .collect(Collectors.toList())) + .map(staticData -> new MonitoringStaticClientData(staticData, this.duplicates)); + } + @Override public Result> getActiveConnectionTokens(final Long examId) { return this.clientConnectionDAO @@ -587,4 +606,21 @@ public class ExamSessionServiceImpl implements ExamSessionService { }); } + private final Map duplicateCheck = new HashMap<>(); + private final Set duplicates = new HashSet<>(); + + private ClientConnectionDataInternal getForTokenAndCheckDuplication(final String token) { + final ClientConnectionDataInternal cc = this.examSessionCacheService.getClientConnection(token); + if (cc.clientConnection.status.establishedStatus) { + final Long id = this.duplicateCheck.put( + cc.clientConnection.userSessionId, + cc.getConnectionId()); + if (id != null) { + this.duplicates.add(id); + this.duplicates.add(cc.getConnectionId()); + } + } + return cc; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InternalClientConnectionDataFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InternalClientConnectionDataFactory.java index 80afbc6a..b5260369 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InternalClientConnectionDataFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InternalClientConnectionDataFactory.java @@ -10,6 +10,8 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,17 +60,24 @@ public class InternalClientConnectionDataFactory { result = new ClientConnectionDataInternal( clientConnection, () -> false, - this.clientIndicatorFactory.createFor(clientConnection)); + this.clientIndicatorFactory.createFor(clientConnection), + getGroupIds(clientConnection)); } else { result = new ClientConnectionDataInternal( clientConnection, () -> this.sebClientNotificationService .hasAnyPendingNotification(clientConnection), - this.clientIndicatorFactory.createFor(clientConnection)); + this.clientIndicatorFactory.createFor(clientConnection), + getGroupIds(clientConnection)); } + return result; + } + + public Set getGroupIds(final ClientConnection clientConnection) { // set client groups for connection + final Set clientGroupIds = new HashSet<>(); if (clientConnection.examId != null) { final Collection clientGroups = this.clientGroupDAO .allForExam(clientConnection.examId) @@ -80,13 +89,12 @@ public class InternalClientConnectionDataFactory { if (!clientGroups.isEmpty()) { clientGroups.forEach(clientGroup -> { if (this.clientGroupMatcherService.isInGroup(clientConnection, clientGroup)) { - result.addToClientGroup(clientGroup); + clientGroupIds.add(clientGroup.id); } }); } } - - return result; + return clientGroupIds; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java index 4eef5111..05323eed 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java @@ -75,6 +75,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic private final ExamAdminService examAdminService; private final DistributedIndicatorValueService distributedPingCache; private final ClientIndicatorFactory clientIndicatorFactory; + private final InternalClientConnectionDataFactory internalClientConnectionDataFactory; private final boolean isDistributedSetup; protected SEBClientConnectionServiceImpl( @@ -84,7 +85,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic final SEBClientInstructionService sebInstructionService, final ExamAdminService examAdminService, final DistributedIndicatorValueService distributedPingCache, - final ClientIndicatorFactory clientIndicatorFactory) { + final ClientIndicatorFactory clientIndicatorFactory, + final InternalClientConnectionDataFactory internalClientConnectionDataFactory) { this.examSessionService = examSessionService; this.examSessionCacheService = examSessionService.getExamSessionCacheService(); @@ -95,6 +97,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic this.examAdminService = examAdminService; this.clientIndicatorFactory = clientIndicatorFactory; this.distributedPingCache = distributedPingCache; + this.internalClientConnectionDataFactory = internalClientConnectionDataFactory; this.isDistributedSetup = sebInstructionService.getWebserviceInfo().isDistributed(); } @@ -740,7 +743,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic public Result getIndicatorValues(final ClientConnection clientConnection) { return Result.tryCatch(() -> new ClientConnectionData( clientConnection, - this.clientIndicatorFactory.getIndicatorValues(clientConnection))); + this.clientIndicatorFactory.getIndicatorValues(clientConnection), + this.internalClientConnectionDataFactory.getGroupIds(clientConnection))); } private void checkExamRunning(final Long examId, final String user, final String address) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index 87e40a52..6ed60e70 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -16,6 +16,8 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; @@ -53,7 +55,9 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; +import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringStaticClientData; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; @@ -262,6 +266,32 @@ public class ExamMonitoringController { .getOrThrow(); } + @RequestMapping( + path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT + + API.EXAM_MONITORING_STATIC_CLIENT_DATA, + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + public MonitoringStaticClientData getMonitoringStaticClientData( + @RequestParam( + name = API.PARAM_INSTITUTION_ID, + required = true, + defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, + @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, + @RequestParam(name = API.PARAM_MODEL_ID_LIST, required = true) final String clientConnectionIds) { + + final Exam runningExam = checkPrivileges(institutionId, examId); + + final Set ids = Stream.of(StringUtils.split(clientConnectionIds, Constants.LIST_SEPARATOR)) + .map(Utils::toLong) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + return this.examSessionService + .getMonitoringSEBConnectionStaticData(runningExam.id, ids) + .getOrThrow(); + } + @RequestMapping( path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT + API.EXAM_MONITORING_FULLPAGE, diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java index 339611d9..df55adb7 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java @@ -25,7 +25,6 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; -import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; 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; @@ -276,21 +275,14 @@ public class ModelObjectJSONGenerator { Arrays.asList( new SimpleIndicatorValue(1L, 1.0), new SimpleIndicatorValue(2L, 2.0), - new SimpleIndicatorValue(3L, 3.0))); + new SimpleIndicatorValue(3L, 3.0)), + new HashSet<>(Arrays.asList(1L, 2L))); System.out.println(domainObject.getClass().getSimpleName() + ":"); System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); - ((ClientConnectionData) domainObject) - .addToClientGroup(new ClientGroup(1L, 1L, "group1", null, null, null, null)); - ((ClientConnectionData) domainObject) - .addToClientGroup(new ClientGroup(2L, 1L, "group2", null, null, null, null)); System.out.println(domainObject.getClass().getSimpleName() + ":"); System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); - System.out.println("ClientMonitoringData" + ":"); - System.out.println(writerWithDefaultPrettyPrinter - .writeValueAsString(((ClientConnectionData) domainObject).monitoringDataView)); - domainObject = new ClientEvent(1L, 1L, EventType.WARN_LOG, System.currentTimeMillis(), System.currentTimeMillis(), 123.0, "text"); System.out.println(domainObject.getClass().getSimpleName() + ":");