From 9fbc5bdbc1646f498fceabdd8017f488220f35fd Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 15 Dec 2021 16:16:17 +0100 Subject: [PATCH] SEBSERV-250 service for all indicators with separated table and update --- .../sebserver/gbl/model/exam/Indicator.java | 16 +- .../model/session/ClientConnectionData.java | 2 +- .../gbl/model/session/IndicatorValue.java | 10 +- .../model/session/SimpleIndicatorValue.java | 13 - .../session/ClientConnectionDetails.java | 2 +- .../session/ClientConnectionTable.java | 6 +- .../sebserver/webservice/WebserviceInfo.java | 10 +- .../sebserver/webservice/WebserviceInit.java | 4 - ...r.java => ClientIndicatorValueMapper.java} | 46 ++- .../servicelayer/session/ClientIndicator.java | 11 +- .../impl/SEBClientConnectionServiceImpl.java | 28 +- .../indicator/AbstractClientIndicator.java | 64 +++- .../impl/indicator/AbstractLogIndicator.java | 16 +- .../AbstractLogLevelCountIndicator.java | 27 +- .../indicator/AbstractLogNumberIndicator.java | 29 +- .../impl/indicator/AbstractPingIndicator.java | 46 +-- .../indicator/BatteryStatusIndicator.java | 12 +- .../impl/indicator/ClientIndicatorType.java | 39 -- .../DistributedIndicatorValueService.java | 362 ++++++++++++++++++ .../indicator/DistributedPingService.java | 344 ----------------- .../ErrorLogCountClientIndicator.java | 12 +- .../InfoLogCountClientIndicator.java | 12 +- .../PingIntervalClientIndicator.java | 44 ++- .../impl/indicator/WLANStatusIndicator.java | 12 +- .../WarnLogCountClientIndicator.java | 11 +- .../config/application-dev-ws.properties | 2 +- .../config/application-ws.properties | 2 +- .../gbl/model/ModelObjectJSONGenerator.java | 8 +- .../integration/UseCasesIntegrationTest.java | 2 +- .../api/exam/SebConnectionTest.java | 5 +- .../services/ClientEventServiceTest.java | 39 +- .../indicator/IndicatorValueJSONTest.java | 8 +- .../PingIntervalClientIndicatorTest.java | 15 +- 33 files changed, 625 insertions(+), 634 deletions(-) rename src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/batis/{ClientPingMapper.java => ClientIndicatorValueMapper.java} (69%) delete mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/ClientIndicatorType.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java delete mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedPingService.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java index 4ff18f45..2f15b2f2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java @@ -34,13 +34,15 @@ public final class Indicator implements Entity { public static final String FILTER_ATTR_EXAM_ID = "examId"; public enum IndicatorType { - LAST_PING(Names.LAST_PING, false, true, true, false, false), - ERROR_COUNT(Names.ERROR_COUNT, false, true, false, true, false), - WARN_COUNT(Names.WARN_COUNT, false, true, false, true, false), - INFO_COUNT(Names.INFO_COUNT, false, true, false, true, false), - BATTERY_STATUS(Names.BATTERY_STATUS, true, true, true, true, true), - WLAN_STATUS(Names.WLAN_STATUS, true, true, true, true, true); + NONE(0, "UNKNOWN", false, false, false, false, false), + LAST_PING(1, Names.LAST_PING, false, true, true, false, false), + ERROR_COUNT(2, Names.ERROR_COUNT, false, true, false, true, false), + WARN_COUNT(3, Names.WARN_COUNT, false, true, false, true, false), + INFO_COUNT(4, Names.INFO_COUNT, false, true, false, true, false), + BATTERY_STATUS(5, Names.BATTERY_STATUS, true, true, true, true, true), + WLAN_STATUS(6, Names.WLAN_STATUS, true, true, true, true, true); + public final int id; public final String name; public final boolean inverse; public final boolean integerValue; @@ -49,6 +51,7 @@ public final class Indicator implements Entity { public final boolean tagsReadonly; IndicatorType( + final int id, final String name, final boolean inverse, final boolean integerValue, @@ -56,6 +59,7 @@ public final class Indicator implements Entity { final boolean tags, final boolean tagsReadonly) { + this.id = id; this.name = name; this.inverse = inverse; this.integerValue = integerValue; 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 71134108..6e2e40e3 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 @@ -95,7 +95,7 @@ public class ClientConnectionData { while (i1.hasNext()) { final IndicatorValue iv1 = i1.next(); final IndicatorValue iv2 = i2.next(); - if (iv1.getType() != iv2.getType() || Math.abs(iv1.getValue() - iv2.getValue()) > 0.1) { + if (iv1.getIndicatorId() != iv2.getIndicatorId() || Math.abs(iv1.getValue() - iv2.getValue()) > 0.1) { return false; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/IndicatorValue.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/IndicatorValue.java index b0e0f78d..5a192c9b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/IndicatorValue.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/IndicatorValue.java @@ -22,22 +22,16 @@ public interface IndicatorValue extends IndicatorValueHolder { @JsonProperty(SimpleIndicatorValue.ATTR_INDICATOR_ID) Long getIndicatorId(); - /** Use this to get the type of indicator this value was computed from. - * - * @return the type of indicator this value was computed from. */ - @JsonProperty(SimpleIndicatorValue.ATTR_INDICATOR_TYPE) - IndicatorType getType(); - /** Use this to get the display value of the value of given IndicatorValue. * Since the internal value is a double this gets the correct display value for the IndicatorType * * @param indicatorValue The indicator value instance * @return the display value of the given IndicatorValue */ - static String getDisplayValue(final IndicatorValue indicatorValue) { + static String getDisplayValue(final IndicatorValue indicatorValue, final IndicatorType type) { if (Double.isNaN(indicatorValue.getValue())) { return Constants.EMPTY_NOTE; } - if (indicatorValue.getType().integerValue) { + if (type.integerValue) { return String.valueOf((int) indicatorValue.getValue()); } else { return String.valueOf(indicatorValue.getValue()); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/SimpleIndicatorValue.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/SimpleIndicatorValue.java index 91c9e740..7474824d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/SimpleIndicatorValue.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/SimpleIndicatorValue.java @@ -11,25 +11,19 @@ package ch.ethz.seb.sebserver.gbl.model.session; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; - public final class SimpleIndicatorValue implements IndicatorValue { @JsonProperty(ATTR_INDICATOR_ID) public final Long indicatorId; - @JsonProperty(ATTR_INDICATOR_TYPE) - public final IndicatorType type; @JsonProperty(ATTR_INDICATOR_VALUE) public final double value; @JsonCreator public SimpleIndicatorValue( @JsonProperty(ATTR_INDICATOR_ID) final Long indicatorId, - @JsonProperty(ATTR_INDICATOR_TYPE) final IndicatorType type, @JsonProperty(ATTR_INDICATOR_VALUE) final double value) { this.indicatorId = indicatorId; - this.type = type; this.value = value; } @@ -38,11 +32,6 @@ public final class SimpleIndicatorValue implements IndicatorValue { return this.indicatorId; } - @Override - public IndicatorType getType() { - return this.type; - } - @Override public double getValue() { return this.value; @@ -53,8 +42,6 @@ public final class SimpleIndicatorValue implements IndicatorValue { final StringBuilder builder = new StringBuilder(); builder.append("SimpleIndicatorValue [indicatorId="); builder.append(this.indicatorId); - builder.append(", type="); - builder.append(this.type); builder.append(", value="); builder.append(this.value); builder.append("]"); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java index ffd1975e..bc4f406a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java @@ -196,7 +196,7 @@ public class ClientConnectionDetails { .forEach(indValue -> { final IndicatorData indData = this.indicatorMapping.get(indValue.getIndicatorId()); final double value = indValue.getValue(); - final String displayValue = IndicatorValue.getDisplayValue(indValue); + final String displayValue = IndicatorValue.getDisplayValue(indValue, indData.indicator.type); if (!this.connectionData.clientConnection.status.clientActiveStatus) { 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 7a8053d0..5fb9ca65 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 @@ -602,12 +602,14 @@ public final class ClientConnectionTable { if (!this.connectionData.clientConnection.status.clientActiveStatus) { final String value = (indicatorData.indicator.type.showOnlyInActiveState) ? Constants.EMPTY_NOTE - : IndicatorValue.getDisplayValue(indicatorValue); + : 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)); + 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]; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java index 917f57b9..d4721202 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java @@ -60,7 +60,7 @@ public class WebserviceInfo { private final boolean isDistributed; private final String webserviceUUID; - private final long distributedPingUpdateInterval; + private final long distributedUpdateInterval; private Map lmsExternalAddressAlias; @@ -76,8 +76,8 @@ public class WebserviceInfo { this.discoveryEndpoint = environment.getRequiredProperty(WEB_SERVICE_EXAM_API_DISCOVERY_ENDPOINT_KEY); this.contextPath = environment.getProperty(WEB_SERVICE_CONTEXT_PATH, ""); - this.distributedPingUpdateInterval = environment.getProperty( - "sebserver.webservice.distributed.pingUpdate", + this.distributedUpdateInterval = environment.getProperty( + "sebserver.webservice.distributed.updateInterval", Long.class, 3000L); @@ -167,8 +167,8 @@ public class WebserviceInfo { return this.serverURLPrefix + this.discoveryEndpoint; } - public long getDistributedPingUpdateInterval() { - return this.distributedPingUpdateInterval; + public long getDistributedUpdateInterval() { + return this.distributedUpdateInterval; } public String getLocalHostName() { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java index db5302cb..b5964ad1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java @@ -146,10 +146,6 @@ public class WebserviceInit implements ApplicationListener *********************************************************"); SEBServerInit.INIT_LOGGER.info("----> *** Webservice successfully started up! ***"); SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); - - SEBServerInit.INIT_LOGGER.info("----> log4j2.formatMsgNoLookups = {}", - this.environment.getProperty("log4j2.formatMsgNoLookups", "none")); - } @PreDestroy diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/batis/ClientPingMapper.java b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/batis/ClientIndicatorValueMapper.java similarity index 69% rename from src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/batis/ClientPingMapper.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/batis/ClientIndicatorValueMapper.java index 3327d646..657a82d9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/batis/ClientPingMapper.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/batis/ClientIndicatorValueMapper.java @@ -18,6 +18,7 @@ import org.apache.ibatis.annotations.ConstructorArgs; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.ResultType; import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.Update; import org.apache.ibatis.annotations.UpdateProvider; import org.apache.ibatis.type.JdbcType; import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter; @@ -28,11 +29,11 @@ import org.mybatis.dynamic.sql.update.UpdateDSL; import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider; import org.mybatis.dynamic.sql.util.SqlProviderAdapter; +import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordDynamicSqlSupport; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.ClientIndicatorType; @Mapper -public interface ClientPingMapper { +public interface ClientIndicatorValueMapper { @SelectProvider(type = SqlProviderAdapter.class, method = "select") @ConstructorArgs({ @Arg(column = "value", javaType = Long.class, jdbcType = JdbcType.BIGINT) }) @@ -50,14 +51,14 @@ public interface ClientPingMapper { int update(UpdateStatementProvider updateStatement); @SelectProvider(type = SqlProviderAdapter.class, method = "select") - @ResultType(ClientLastPingRecord.class) + @ResultType(ClientIndicatorValueRecord.class) @ConstructorArgs({ @Arg(column = "id", javaType = Long.class, jdbcType = JdbcType.BIGINT), @Arg(column = "value", javaType = Long.class, jdbcType = JdbcType.BIGINT) }) - Collection selectMany(SelectStatementProvider select); + Collection selectMany(SelectStatementProvider select); - default Long selectPingTimeByPrimaryKey(final Long id_) { + default Long selectValueByPrimaryKey(final Long id_) { return SelectDSL.selectWithMapper( this::selectPingTime, value.as("value")) @@ -67,18 +68,29 @@ public interface ClientPingMapper { .execute(); } + default Long indicatorRecordIdByConnectionId(final Long connectionId, final IndicatorType indicatorType) { + return SelectDSL.selectDistinctWithMapper( + this::selectPK, + id.as("id")) + .from(clientIndicatorRecord) + .where(clientConnectionId, isEqualTo(connectionId)) + .and(type, isEqualTo(indicatorType.id)) + .build() + .execute(); + } + default Long pingRecordIdByConnectionId(final Long connectionId) { return SelectDSL.selectDistinctWithMapper( this::selectPK, id.as("id")) .from(clientIndicatorRecord) .where(clientConnectionId, isEqualTo(connectionId)) - .and(type, isEqualTo(ClientIndicatorType.LAST_PING.id)) + .and(type, isEqualTo(IndicatorType.LAST_PING.id)) .build() .execute(); } - default QueryExpressionDSL>> selectByExample() { + default QueryExpressionDSL>> selectByExample() { return SelectDSL.selectWithMapper( this::selectMany, @@ -87,25 +99,31 @@ public interface ClientPingMapper { .from(ClientIndicatorRecordDynamicSqlSupport.clientIndicatorRecord); } - default int updatePingTime(final Long _id, final Long pingTime) { + @Update("UPDATE client_indicator SET value = value + 1 WHERE id =#{pk}") + int incrementIndicatorValue(final Long pk); + + @Update("UPDATE client_indicator SET value = value - 1 WHERE id =#{pk}") + int decrementIndicatorValue(final Long pk); + + default int updateIndicatorValue(final Long pk, final Long v) { return UpdateDSL.updateWithMapper(this::update, clientIndicatorRecord) - .set(value).equalTo(pingTime) - .where(id, isEqualTo(_id)) + .set(value).equalTo(v) + .where(id, isEqualTo(pk)) .build() .execute(); } - final class ClientLastPingRecord { + final class ClientIndicatorValueRecord { public final Long id; - public final Long lastPingTime; + public final Long indicatorValue; - public ClientLastPingRecord( + public ClientIndicatorValueRecord( final Long id, final Long value) { this.id = id; - this.lastPingTime = value; + this.indicatorValue = value; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java index 3efb8302..b5a80ee9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java @@ -10,12 +10,14 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import java.util.Set; +import com.fasterxml.jackson.annotation.JsonIgnore; + import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; +import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.ClientIndicatorType; /** A client indicator is a indicator value holder for a specific Indicator * on a running client connection. @@ -32,10 +34,9 @@ public interface ClientIndicator extends IndicatorValue { * @param cachingEnabled defines whether indicator value caching is enabled or not. */ void init(Indicator indicatorDefinition, Long connectionId, boolean active, boolean cachingEnabled); - /** Get the client indicator type - * - * @return the client indicator type */ - ClientIndicatorType indicatorType(); + /** get the indicator type */ + @JsonIgnore + IndicatorType getType(); /** Get the exam identifier of the client connection of this ClientIndicator * 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 7d459357..f52a11ab 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 @@ -48,7 +48,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedPingService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedIndicatorValueService; import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException; @Lazy @@ -74,7 +74,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic private final SEBClientInstructionService sebInstructionService; private final SEBClientNotificationService sebClientNotificationService; private final ExamAdminService examAdminService; - private final DistributedPingService distributedPingCache; + // TODO get rid of this dependency and use application events for signaling client connection state changes + private final DistributedIndicatorValueService distributedPingCache; private final Executor indicatorUpdateExecutor; private final boolean isDistributedSetup; @@ -85,7 +86,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic final SEBClientInstructionService sebInstructionService, final SEBClientNotificationService sebClientNotificationService, final ExamAdminService examAdminService, - final DistributedPingService distributedPingCache, + final DistributedIndicatorValueService distributedPingCache, @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) final Executor indicatorUpdateExecutor) { this.examSessionService = examSessionService; @@ -495,7 +496,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic // delete stored ping if this is a distributed setup if (this.isDistributedSetup) { this.distributedPingCache - .deletePingIndicator(updatedClientConnection.id); + .deleteIndicatorValues(updatedClientConnection.id); } reloadConnectionCache(connectionToken); @@ -549,7 +550,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic // delete stored ping if this is a distributed setup if (this.isDistributedSetup) { this.distributedPingCache - .deletePingIndicator(updatedClientConnection.id); + .deleteIndicatorValues(updatedClientConnection.id); } reloadConnectionCache(connectionToken); @@ -637,7 +638,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic activeClientConnection.getConnectionId())); this.indicatorUpdateExecutor - .execute(() -> updateIndicator(connectionToken, event, activeClientConnection)); + .execute(() -> handleEvent(connectionToken, event, activeClientConnection)); } else { log.warn("No active ClientConnection found for connectionToken: {}", connectionToken); @@ -647,7 +648,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic } } - private void updateIndicator( + private void handleEvent( final String connectionToken, final ClientEvent event, final ClientConnectionDataInternal activeClientConnection) { @@ -827,17 +828,18 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic // store event and and flush cache this.eventHandlingStrategy.accept(clientEventRecord); + + // update indicators + if (clientEventRecord.getType() != null && EventType.ERROR_LOG.id == clientEventRecord.getType()) { + connection.getIndicatorMapping(EventType.ERROR_LOG) + .forEach(indicator -> indicator.notifyValueChange(clientEventRecord)); + } + if (this.isDistributedSetup) { // mark for update and flush the cache this.clientConnectionDAO.save(connection.clientConnection); this.examSessionCacheService.evictClientConnection( connection.clientConnection.connectionToken); - } else { - // update indicators - if (clientEventRecord.getType() != null && EventType.ERROR_LOG.id == clientEventRecord.getType()) { - connection.getIndicatorMapping(EventType.ERROR_LOG) - .forEach(indicator -> indicator.notifyValueChange(clientEventRecord)); - } } } }; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractClientIndicator.java index eafc5e89..714540d1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractClientIndicator.java @@ -8,22 +8,35 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator; -import org.joda.time.DateTimeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; public abstract class AbstractClientIndicator implements ClientIndicator { + private static final Logger log = LoggerFactory.getLogger(AbstractClientIndicator.class); + + protected final DistributedIndicatorValueService distributedPingCache; + protected Long indicatorId; protected Long examId; protected Long connectionId; protected boolean cachingEnabled; protected boolean active = true; - protected boolean valueInitializes = false; + protected Long ditributedIndicatorValueRecordId = null; + + protected boolean initialized = false; protected double currentValue = Double.NaN; + public AbstractClientIndicator(final DistributedIndicatorValueService distributedPingCache) { + super(); + this.distributedPingCache = distributedPingCache; + } + @Override public void init( final Indicator indicatorDefinition, @@ -40,6 +53,40 @@ public abstract class AbstractClientIndicator implements ClientIndicator { this.connectionId = connectionId; this.active = active; this.cachingEnabled = cachingEnabled; + + if (!this.cachingEnabled && this.active) { + try { + this.ditributedIndicatorValueRecordId = this.distributedPingCache.initIndicatorForConnection( + connectionId, + getType(), + initValue()); + } catch (final Exception e) { + tryRecoverIndicatorRecord(); + } + } + + this.currentValue = computeValueAt(Utils.getMillisecondsNow()); + this.initialized = true; + } + + protected long initValue() { + return 0; + } + + protected void tryRecoverIndicatorRecord() { + + if (log.isWarnEnabled()) { + log.warn("*** Missing indicator value record for connection: {}. Try to recover...", this.connectionId); + } + + try { + this.ditributedIndicatorValueRecordId = this.distributedPingCache.initIndicatorForConnection( + this.connectionId, + getType(), + initValue()); + } catch (final Exception e) { + log.error("Failed to recover indicator value record for connection: {}", this.connectionId, e); + } } @Override @@ -58,22 +105,11 @@ public abstract class AbstractClientIndicator implements ClientIndicator { } public void reset() { - this.currentValue = Double.NaN; - this.valueInitializes = false; + this.currentValue = computeValueAt(Utils.getMillisecondsNow()); } @Override public double getValue() { - final long now = DateTimeUtils.currentTimeMillis(); - if (!this.valueInitializes) { - this.currentValue = computeValueAt(now); - this.valueInitializes = true; - } - - if (!this.cachingEnabled && this.active) { - this.currentValue = computeValueAt(now); - } - return this.currentValue; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogIndicator.java index 39edc2d5..5e91c5ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogIndicator.java @@ -30,10 +30,11 @@ public abstract class AbstractLogIndicator extends AbstractClientIndicator { protected final List eventTypeIds; protected String[] tags; - protected long lastDistributedUpdate = 0L; - - protected AbstractLogIndicator(final EventType... eventTypes) { + protected AbstractLogIndicator( + final DistributedIndicatorValueService distributedPingCache, + final EventType... eventTypes) { + super(distributedPingCache); this.observed = Collections.unmodifiableSet(EnumSet.of(eventTypes[0], eventTypes)); this.eventTypeIds = Utils.immutableListOf(Arrays.stream(eventTypes) .map(et -> et.id) @@ -50,6 +51,7 @@ public abstract class AbstractLogIndicator extends AbstractClientIndicator { super.init(indicatorDefinition, connectionId, active, cachingEnabled); + // init tags if (indicatorDefinition == null || StringUtils.isBlank(indicatorDefinition.tags)) { this.tags = null; } else { @@ -79,12 +81,4 @@ public abstract class AbstractLogIndicator extends AbstractClientIndicator { return this.observed; } - protected boolean loadFromPersistent(final long timestamp) { - if (!super.valueInitializes) { - return true; - } - - return timestamp - this.lastDistributedUpdate > DISTRIBUTED_LOG_UPDATE_INTERVAL; - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java index 92b3f77c..47f3af9f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java @@ -29,10 +29,11 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato protected final ClientEventRecordMapper clientEventRecordMapper; protected AbstractLogLevelCountIndicator( + final DistributedIndicatorValueService distributedPingCache, final ClientEventRecordMapper clientEventRecordMapper, final EventType... eventTypes) { - super(eventTypes); + super(distributedPingCache, eventTypes); this.clientEventRecordMapper = clientEventRecordMapper; } @@ -47,9 +48,10 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato } private void valueChanged(final String eventText) { - if (this.tags == null || this.tags.length == 0) { - this.currentValue = getValue() + 1d; - } else if (hasTag(eventText)) { + if (this.tags == null || this.tags.length == 0 || hasTag(eventText)) { + if (super.ditributedIndicatorValueRecordId != null) { + this.distributedPingCache.incrementIndicatorValue(super.ditributedIndicatorValueRecordId); + } this.currentValue = getValue() + 1d; } } @@ -57,15 +59,9 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato @Override public double computeValueAt(final long timestamp) { - if (!loadFromPersistent(timestamp)) { - return super.currentValue; - } - - // TODO do this within a better reactive way like ping updates - try { - final Long errors = this.clientEventRecordMapper + final Long numberOfLogs = this.clientEventRecordMapper .countByExample() .where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(this.connectionId)) .and(ClientEventRecordDynamicSqlSupport.type, isIn(this.eventTypeIds)) @@ -77,13 +73,16 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato .build() .execute(); - return errors.doubleValue(); + // update active indicator value record on persistent when caching is not enabled + if (!this.cachingEnabled && this.active && this.ditributedIndicatorValueRecordId != null) { + this.distributedPingCache.updateIndicatorValue(this.connectionId, numberOfLogs.longValue()); + } + + return numberOfLogs.doubleValue(); } catch (final Exception e) { log.error("Failed to get indicator count from persistent storage: ", e); return super.currentValue; - } finally { - super.lastDistributedUpdate = timestamp; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java index 3fa51e81..64df36e0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java @@ -32,10 +32,11 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { protected final ClientEventRecordMapper clientEventRecordMapper; protected AbstractLogNumberIndicator( + final DistributedIndicatorValueService distributedPingCache, final ClientEventRecordMapper clientEventRecordMapper, final EventType... eventTypes) { - super(eventTypes); + super(distributedPingCache, eventTypes); this.clientEventRecordMapper = clientEventRecordMapper; } @@ -53,9 +54,15 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { } private void valueChanged(final String text, final double value) { - if (this.tags == null || this.tags.length == 0) { - this.currentValue = value; - } else if (hasTag(text)) { + + this.currentValue = getValue() + 1d; + + if (this.tags == null || this.tags.length == 0 || hasTag(text)) { + if (super.ditributedIndicatorValueRecordId != null) { + this.distributedPingCache.updateIndicatorValueAsync( + this.ditributedIndicatorValueRecordId, + Double.valueOf(value).longValue()); + } this.currentValue = value; } } @@ -63,12 +70,6 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { @Override public double computeValueAt(final long timestamp) { - if (!loadFromPersistent(timestamp)) { - return super.currentValue; - } - - // TODO do this within a better reactive way like ping updates - try { final List execute = this.clientEventRecordMapper.selectByExample() @@ -89,6 +90,12 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { final BigDecimal numericValue = execute.get(execute.size() - 1).getNumericValue(); if (numericValue != null) { + + // update active indicator value record on persistent when caching is not enabled + if (!this.cachingEnabled && this.active && this.ditributedIndicatorValueRecordId != null) { + this.distributedPingCache.updateIndicatorValue(this.connectionId, numericValue.longValue()); + } + return numericValue.doubleValue(); } else { return super.currentValue; @@ -97,8 +104,6 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { } catch (final Exception e) { log.error("Failed to get indicator number from persistent storage: {}", e.getMessage()); return this.currentValue; - } finally { - super.lastDistributedUpdate = timestamp; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java index 437c5694..f19b2eb5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java @@ -12,25 +12,15 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedPingService.PingUpdate; public abstract class AbstractPingIndicator extends AbstractClientIndicator { - private static final Logger log = LoggerFactory.getLogger(AbstractPingIndicator.class); - private final Set EMPTY_SET = Collections.unmodifiableSet(EnumSet.noneOf(EventType.class)); - protected final DistributedPingService distributedPingCache; - protected PingUpdate pingUpdate = null; - - protected AbstractPingIndicator(final DistributedPingService distributedPingCache) { - super(); - this.distributedPingCache = distributedPingCache; + protected AbstractPingIndicator(final DistributedIndicatorValueService distributedPingCache) { + super(distributedPingCache); } @Override @@ -41,14 +31,6 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator { final boolean cachingEnabled) { super.init(indicatorDefinition, connectionId, active, cachingEnabled); - - if (!this.cachingEnabled && this.active) { - try { - this.pingUpdate = this.distributedPingCache.createPingUpdate(connectionId); - } catch (final Exception e) { - this.pingUpdate = this.distributedPingCache.createPingUpdate(connectionId); - } - } } public final void notifyPing(final long timestamp, final int pingNumber) { @@ -56,30 +38,14 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator { if (!this.cachingEnabled) { - if (this.pingUpdate == null) { - tryRecoverPingRecord(); - if (this.pingUpdate == null) { + if (super.ditributedIndicatorValueRecordId == null) { + tryRecoverIndicatorRecord(); + if (this.ditributedIndicatorValueRecordId == null) { return; } } - this.distributedPingCache.updatePingAsync(this.pingUpdate); - } - } - - private void tryRecoverPingRecord() { - - if (log.isWarnEnabled()) { - log.warn("*** Missing ping record for connection: {}. Try to recover...", this.connectionId); - } - - try { - this.pingUpdate = this.distributedPingCache.createPingUpdate(this.connectionId); - if (this.pingUpdate == null) { - this.pingUpdate = this.distributedPingCache.createPingUpdate(this.connectionId); - } - } catch (final Exception e) { - log.error("Failed to recover ping record for connection: {}", this.connectionId, e); + this.distributedPingCache.updatePingAsync(this.ditributedIndicatorValueRecordId); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/BatteryStatusIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/BatteryStatusIndicator.java index 53994747..387efbb8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/BatteryStatusIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/BatteryStatusIndicator.java @@ -24,8 +24,11 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecord @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class BatteryStatusIndicator extends AbstractLogNumberIndicator { - protected BatteryStatusIndicator(final ClientEventRecordMapper clientEventRecordMapper) { - super(clientEventRecordMapper, EventType.INFO_LOG); + protected BatteryStatusIndicator( + final DistributedIndicatorValueService distributedPingCache, + final ClientEventRecordMapper clientEventRecordMapper) { + + super(distributedPingCache, clientEventRecordMapper, EventType.INFO_LOG); super.tags = new String[] { API.LOG_EVENT_TAG_BATTERY_STATUS }; } @@ -45,9 +48,4 @@ public class BatteryStatusIndicator extends AbstractLogNumberIndicator { return IndicatorType.BATTERY_STATUS; } - @Override - public ClientIndicatorType indicatorType() { - return ClientIndicatorType.BATTERY_STATUS; - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/ClientIndicatorType.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/ClientIndicatorType.java deleted file mode 100644 index 4bfea4b4..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/ClientIndicatorType.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator; - - -public enum ClientIndicatorType { - UNKNOWN(0), - LAST_PING(1), - ERROR_LOG_COUNT(2), - WARN_LOG_COUNT(3), - INFO_LOG_COUNT(4), - WLAN_STATUS(5), - BATTERY_STATUS(5), - - - ; - - public final int id; - - ClientIndicatorType(final int id) { - this.id = id; - } - - public static ClientIndicatorType byId(final int id) { - for (final ClientIndicatorType status : ClientIndicatorType.values()) { - if (status.id == id) { - return status; - } - } - - return UNKNOWN; - } -} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java new file mode 100644 index 00000000..cf60202a --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator; + +import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo; +import static org.mybatis.dynamic.sql.SqlBuilder.isIn; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledFuture; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import ch.ethz.seb.sebserver.SEBServerInit; +import ch.ethz.seb.sebserver.SEBServerInitEvent; +import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; +import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.WebserviceInfo; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientIndicatorValueMapper; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientIndicatorValueMapper.ClientIndicatorValueRecord; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordDynamicSqlSupport; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordMapper; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientIndicatorRecord; + +@Lazy +@Component +@WebServiceProfile +/** This service is only needed within a distributed setup where more then one webservice works + * simultaneously within one SEB Server and one persistent storage. + *

+ * This service handles the SEB client indicator updates within such a setup and implements functionality to + * efficiently store and load indicator values from and to shared store. + *

+ * The update from the persistent store is done done periodically within a batch while the indicator value writes + * are done individually per SEB client when they arrive. The update can be done within a dedicated task executor with + * minimal task + * queue to do not overflow other executor services when it comes to a leak on storing lot of ping times for example. + * In this case some ping time updates will be just dropped and not go to the persistent store until the leak + * is resolved. */ +public class DistributedIndicatorValueService implements DisposableBean { + + private static final Logger log = LoggerFactory.getLogger(DistributedIndicatorValueService.class); + + private final Executor indicatorValueUpdateExecutor; + private final ClientIndicatorRecordMapper clientIndicatorRecordMapper; + private final ClientIndicatorValueMapper clientIndicatorValueMapper; + private long updateTolerance; + + private ScheduledFuture taskRef; + private final Map indicatorValueCache = new ConcurrentHashMap<>(); + private long lastUpdate = 0L; + + public DistributedIndicatorValueService( + @Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor pingUpdateExecutor, + final ClientIndicatorRecordMapper clientIndicatorRecordMapper, + final ClientIndicatorValueMapper clientIndicatorValueMapper) { + + this.indicatorValueUpdateExecutor = pingUpdateExecutor; + this.clientIndicatorRecordMapper = clientIndicatorRecordMapper; + this.clientIndicatorValueMapper = clientIndicatorValueMapper; + } + + /** Initializes the service by attaching it to the scheduler for periodical update. + * If the webservice is not initialized within a distributed setup, this will do nothing + * + * @param initEvent the SEB Server webservice init event */ + @EventListener(SEBServerInitEvent.class) + public void init(final SEBServerInitEvent initEvent) { + final ApplicationContext applicationContext = initEvent.webserviceInit.getApplicationContext(); + final WebserviceInfo webserviceInfo = applicationContext.getBean(WebserviceInfo.class); + if (webserviceInfo.isDistributed()) { + + SEBServerInit.INIT_LOGGER.info("------>"); + SEBServerInit.INIT_LOGGER.info("------> Activate distributed indicator value service:"); + + final TaskScheduler taskScheduler = applicationContext.getBean(TaskScheduler.class); + final long distributedUpdateInterval = webserviceInfo.getDistributedUpdateInterval(); + this.updateTolerance = distributedUpdateInterval * 2 / 3; + + SEBServerInit.INIT_LOGGER.info("------> with distributedUpdateInterval: {}", + distributedUpdateInterval); + SEBServerInit.INIT_LOGGER.info("------> with taskScheduler: {}", taskScheduler); + + try { + + this.taskRef = taskScheduler.scheduleAtFixedRate( + this::updateIndicatorValueCache, + distributedUpdateInterval); + + SEBServerInit.INIT_LOGGER.info("------> distributed indicator value service successfully initialized!"); + + } catch (final Exception e) { + SEBServerInit.INIT_LOGGER.error("------> Failed to initialize distributed indicator value service:", e); + log.error("Failed to initialize distributed indicator value cache update task"); + this.taskRef = null; + } + } else { + this.taskRef = null; + } + } + + /** This initializes a SEB client indicator on the persistent storage for a given SEB client + * connection identifier and of given IndicatorType. + * If there is already such an indicator for the specified SEB client connection identifier and type, + * this returns the id of the existing one. + * + * @param connectionId SEB client connection identifier + * @param type indicator type + * @param value the initial indicator value + * @return SEB client indicator value identifier (PK) */ + @Transactional + public Long initIndicatorForConnection( + final Long connectionId, + final IndicatorType type, + final Long value) { + + try { + + if (log.isDebugEnabled()) { + log.trace("*** Initialize indicator value record for SEB connection: {}", connectionId); + } + + final Long recordId = this.clientIndicatorValueMapper + .indicatorRecordIdByConnectionId(connectionId, type); + + if (recordId == null) { + final ClientIndicatorRecord clientEventRecord = new ClientIndicatorRecord( + null, connectionId, type.id, value, null); + + this.clientIndicatorRecordMapper.insert(clientEventRecord); + + try { + // This also double-check by trying again. If we have more then one entry here + // this will throw an exception that causes a rollback + return this.clientIndicatorValueMapper + .indicatorRecordIdByConnectionId(connectionId, type); + + } catch (final Exception e) { + + log.warn( + "Detected multiple client indicator entries for connection: {} and type: {}. Force rollback to prevent", + connectionId, type); + + // force rollback + throw new RuntimeException("Detected multiple client indicator value entries"); + } + } + + return recordId; + } catch (final Exception e) { + + log.error("Failed to initialize indicator value for connection -> {}", connectionId, e); + + // force rollback + throw new RuntimeException("Failed to initialize indicator value for connection -> " + connectionId, e); + } + } + + /** Deletes a existing SEB client indicator value record for a given SEB client connection identifier + * on the persistent storage. + * + * @param connectionId SEB client connection identifier */ + @Transactional + public void deleteIndicatorValues(final Long connectionId) { + try { + + if (log.isDebugEnabled()) { + log.debug("*** Delete indicator value record for SEB connection: {}", connectionId); + } + + final Collection records = this.clientIndicatorValueMapper + .selectByExample() + .where(ClientIndicatorRecordDynamicSqlSupport.clientConnectionId, isEqualTo(connectionId)) + .build() + .execute(); + + if (records == null || records.isEmpty()) { + return; + } + + final List toDelete = records.stream().map(rec -> { + this.indicatorValueCache.remove(rec.id); + return rec.id; + }).collect(Collectors.toList()); + + this.clientIndicatorRecordMapper + .deleteByExample() + .where(ClientIndicatorRecordDynamicSqlSupport.id, isIn(toDelete)) + .build() + .execute(); + + } catch (final Exception e) { + log.error("Failed to delete indicator value for connection -> {}", connectionId, e); + try { + log.info( + "Because of failed indicator value record deletion, " + + "flushing the indicator value cache to ensure no dead connections remain in the cache"); + this.indicatorValueCache.clear(); + } catch (final Exception ee) { + log.error("Failed to force flushing the indicator value cache: ", e); + } + } + } + + /** Use this to get the last indicator value with a given indicator identifier (PK) + * This fist tries to get the indicator value from internal cache. If not present, tries to get + * the indicator value from persistent storage and put it to the cache. + * + * @param indicatorPK The indicator record id (PK). + * @return The actual (last) indicator value. */ + public Long getIndicatorValue(final Long indicatorPK) { + try { + + Long value = this.indicatorValueCache.get(indicatorPK); + if (value == null) { + + if (log.isDebugEnabled()) { + log.debug("*** Get and cache ping time: {}", indicatorPK); + } + + value = this.clientIndicatorValueMapper.selectValueByPrimaryKey(indicatorPK); + } + + return value; + } catch (final Exception e) { + log.error("Error while trying to get last indicator value from storage: {}", e.getMessage()); + return 0L; + } + } + + /** Updates the internal indicator value cache by loading all actual SEB client indicators from persistent storage + * and put it in the cache. + * This is internally periodically scheduled by the task scheduler but also implements an execution drop if + * the last update was less then 2/3 of the schedule interval ago. This is to prevent task queue overflows + * and wait with update when there is a persistent storage leak or a lot of network latency. */ + private void updateIndicatorValueCache() { + if (this.indicatorValueCache.isEmpty()) { + return; + } + + final long millisecondsNow = Utils.getMillisecondsNow(); + if (millisecondsNow - this.lastUpdate < this.updateTolerance) { + log.warn("Skip indicator value update schedule because the last one was less then 2 seconds ago"); + return; + } + + if (log.isDebugEnabled()) { + log.trace("*** Update distributed indicator value cache: {}", this.indicatorValueCache); + } + + try { + + final Map mapping = this.clientIndicatorValueMapper + .selectByExample() + .build() + .execute() + .stream() + .collect(Collectors.toMap(entry -> entry.id, entry -> entry.indicatorValue)); + + if (mapping != null) { + this.indicatorValueCache.clear(); + this.indicatorValueCache.putAll(mapping); + this.lastUpdate = millisecondsNow; + } + + } catch (final Exception e) { + log.error("Error while trying to update distributed indicator value cache: {}", this.indicatorValueCache, + e); + } + + this.lastUpdate = millisecondsNow; + } + + /** Update last ping time on persistent storage asynchronously within a defines thread pool with no + * waiting queue to skip further ping updates if all update threads are busy **/ + void updatePingAsync(final Long pingRecord) { + try { + this.indicatorValueUpdateExecutor + .execute(() -> this.clientIndicatorValueMapper.updateIndicatorValue( + pingRecord, + Utils.getMillisecondsNow())); + } catch (final Exception e) { + if (log.isDebugEnabled()) { + log.warn("Failed to schedule ping task: {}" + e.getMessage()); + } + } + } + + /** Update indicator value on persistent storage asynchronously within a defined thread pool with no + * waiting queue to skip further indicator value updates if all update threads are busy **/ + void updateIndicatorValueAsync(final Long pk, final Long value) { + try { + this.indicatorValueUpdateExecutor + .execute(() -> this.clientIndicatorValueMapper.updateIndicatorValue(pk, value)); + } catch (final Exception e) { + if (log.isDebugEnabled()) { + log.warn("Failed to schedule indicator update task: {}" + e.getMessage()); + } + } + } + + /** Update an indicator value within a transaction */ + @Transactional + void updateIndicatorValue(final Long pk, final Long value) { + try { + this.clientIndicatorValueMapper.updateIndicatorValue(pk, value); + } catch (final Exception e) { + log.warn("Failed to update indicator value: {}" + e.getMessage()); + } + } + + /** Simply increment a given indicator value */ + void incrementIndicatorValue(final Long pk) { + try { + this.clientIndicatorValueMapper.incrementIndicatorValue(pk); + } catch (final Exception e) { + log.warn("Failed to increment indicator value: {}" + e.getMessage()); + } + } + + @Override + public void destroy() throws Exception { + if (this.taskRef != null) { + + SEBServerInit.INIT_LOGGER.info("----> Shout down distributed indicator service..."); + + try { + final boolean cancel = this.taskRef.cancel(true); + if (!cancel) { + log.warn("Failed to cancel distributed indicator cache update task"); + } + + SEBServerInit.INIT_LOGGER.info("----> Distributed indicator service down"); + + } catch (final Exception e) { + log.error("Failed to cancel distributed indicator cache update task: ", e); + } + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedPingService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedPingService.java deleted file mode 100644 index 57310444..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedPingService.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator; - -import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledFuture; -import java.util.stream.Collectors; - -import org.joda.time.DateTimeUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Lazy; -import org.springframework.context.event.EventListener; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import ch.ethz.seb.sebserver.SEBServerInit; -import ch.ethz.seb.sebserver.SEBServerInitEvent; -import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; -import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; -import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.WebserviceInfo; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientPingMapper; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientPingMapper.ClientLastPingRecord; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordDynamicSqlSupport; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordMapper; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientIndicatorRecord; - -@Lazy -@Component -@WebServiceProfile -/** This service is only needed within a distributed setup where more then one webservice works - * simultaneously within one SEB Server and one persistent storage. - *

- * This service handles the SEB client ping updates within such a setup and implements functionality to - * efficiently store and load ping time indicators form and to shared store. - *

- * The update from the persistent store is done periodically within a batch while the ping time writes - * are done individually per SEB client when they arrive but within a dedicated task executor with minimal task - * queue to do not overflow other executor services when it comes to a leak on storing lot of ping times. - * In this case some ping time updates will be just dropped and not go to the persistent store until the leak - * is resolved. - *

- * Note that the ping time update and read operations are also not within a transaction for performance reasons - * and because it is not a big deal to loose one ore two ping updates for a SEB client. */ -public class DistributedPingService implements DisposableBean { - - private static final Logger log = LoggerFactory.getLogger(DistributedPingService.class); - - private final Executor pingUpdateExecutor; - private final ClientIndicatorRecordMapper clientIndicatorRecordMapper; - private final ClientPingMapper clientPingMapper; - private long pingUpdateTolerance; - - private ScheduledFuture taskRef; - private final Map pingCache = new ConcurrentHashMap<>(); - private long lastUpdate = 0L; - - public DistributedPingService( - @Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor pingUpdateExecutor, - final ClientIndicatorRecordMapper clientIndicatorRecordMapper, - final ClientPingMapper clientPingMapper) { - - this.pingUpdateExecutor = pingUpdateExecutor; - this.clientIndicatorRecordMapper = clientIndicatorRecordMapper; - this.clientPingMapper = clientPingMapper; - } - - /** Initializes the service by attaching it to the scheduler for periodical update. - * If the webservice is not initialized within a distributed setup, this will do nothing - * - * @param initEvent the SEB Server webservice init event */ - @EventListener(SEBServerInitEvent.class) - public void init(final SEBServerInitEvent initEvent) { - final ApplicationContext applicationContext = initEvent.webserviceInit.getApplicationContext(); - final WebserviceInfo webserviceInfo = applicationContext.getBean(WebserviceInfo.class); - if (webserviceInfo.isDistributed()) { - - SEBServerInit.INIT_LOGGER.info("------>"); - SEBServerInit.INIT_LOGGER.info("------> Activate distributed ping service:"); - - final TaskScheduler taskScheduler = applicationContext.getBean(TaskScheduler.class); - final long distributedPingUpdateInterval = webserviceInfo.getDistributedPingUpdateInterval(); - this.pingUpdateTolerance = distributedPingUpdateInterval * 2 / 3; - - SEBServerInit.INIT_LOGGER.info("------> with distributedPingUpdateInterval: {}", - distributedPingUpdateInterval); - SEBServerInit.INIT_LOGGER.info("------> with taskScheduler: {}", taskScheduler); - - try { - this.taskRef = taskScheduler.scheduleAtFixedRate( - this::updatePingCache, - distributedPingUpdateInterval); - - SEBServerInit.INIT_LOGGER.info("------> distributed ping service successfully initialized!"); - - } catch (final Exception e) { - SEBServerInit.INIT_LOGGER.error("------> Failed to initialize distributed ping service:", e); - log.error("Failed to initialize distributed ping cache update task"); - this.taskRef = null; - } - } else { - this.taskRef = null; - } - } - - /** This initializes a SEB client ping indicator on the persistent storage for a given SEB client - * connection identifier. - * If there is already such a ping indicator for the specified SEB client connection identifier, returns - * the id of the existing one. - * - * @param connectionId SEB client connection identifier - * @return SEB client ping indicator identifier (PK) */ - @Transactional - public Long initPingForConnection(final Long connectionId) { - try { - - if (log.isDebugEnabled()) { - log.trace("*** Initialize ping record for SEB connection: {}", connectionId); - } - - final Long recordId = this.clientPingMapper - .pingRecordIdByConnectionId(connectionId); - - if (recordId == null) { - final long millisecondsNow = DateTimeUtils.currentTimeMillis(); - final ClientIndicatorRecord clientEventRecord = new ClientIndicatorRecord( - null, connectionId, ClientIndicatorType.LAST_PING.id, millisecondsNow, null); - - this.clientIndicatorRecordMapper.insert(clientEventRecord); - - try { - // This also double-check by trying again. If we have more then one entry here - // this will throw an exception that causes a rollback - return this.clientPingMapper - .pingRecordIdByConnectionId(connectionId); - - } catch (final Exception e) { - - log.warn("Detected multiple client ping entries for connection: " + connectionId - + ". Force rollback to prevent"); - - // force rollback - throw new RuntimeException("Detected multiple client ping entries"); - } - } - - return recordId; - } catch (final Exception e) { - - log.error("Failed to initialize ping for connection -> {}", connectionId, e); - - // force rollback - throw new RuntimeException("Failed to initialize ping for connection -> " + connectionId, e); - } - } - - /** Deletes a existing SEB client ping indicator for a given SEB client connection identifier - * on the persistent storage. - * - * @param connectionId SEB client connection identifier */ - @Transactional - public void deletePingIndicator(final Long connectionId) { - try { - - if (log.isDebugEnabled()) { - log.debug("*** Delete ping record for SEB connection: {}", connectionId); - } - - final Collection records = this.clientPingMapper - .selectByExample() - .where(ClientIndicatorRecordDynamicSqlSupport.clientConnectionId, isEqualTo(connectionId)) - .and(ClientIndicatorRecordDynamicSqlSupport.type, isEqualTo(ClientIndicatorType.LAST_PING.id)) - .build() - .execute(); - - if (records == null || records.isEmpty()) { - return; - } - - final Long id = records.iterator().next().id; - this.pingCache.remove(id); - this.clientIndicatorRecordMapper.deleteByPrimaryKey(id); - - } catch (final Exception e) { - log.error("Failed to delete ping for connection -> {}", connectionId, e); - try { - log.info( - "Because of failed ping record deletion, " - + "flushing the ping cache to ensure no dead connections pings remain in the cache"); - this.pingCache.clear(); - } catch (final Exception ee) { - log.error("Failed to force flushing the ping cache: ", e); - } - } - } - - /** Use this to get the last ping time indicator value with a given indicator identifier (PK) - * This fist tries to get the ping time from internal cache. If not present, tries to get - * the ping indicator value from persistent storage and put it to the cache. - * - * @param pingRecordId The ping indicator record id (PK). Get one for a given SEB client connection identifier by - * calling: initPingForConnection - * @return The actual (last) ping time. */ - public Long getLastPing(final Long pingRecordId) { - try { - - Long ping = this.pingCache.get(pingRecordId); - if (ping == null) { - - if (log.isDebugEnabled()) { - log.debug("*** Get and cache ping time: {}", pingRecordId); - } - - ping = this.clientPingMapper.selectPingTimeByPrimaryKey(pingRecordId); - } - - return ping; - } catch (final Exception e) { - log.error("Error while trying to get last ping from storage: {}", e.getMessage()); - return 0L; - } - } - - /** Updates the internal ping cache by loading all actual SEB client ping indicators from persistent storage - * and put it in the cache. - * This is internally periodically scheduled by the task scheduler but also implements an execution drop if - * the last update was less then 2/3 of the schedule interval ago. This is to prevent task queue overflows - * and wait with update when there is a persistent storage leak or a lot of network latency. */ - private void updatePingCache() { - if (this.pingCache.isEmpty()) { - return; - } - - final long millisecondsNow = Utils.getMillisecondsNow(); - if (millisecondsNow - this.lastUpdate < this.pingUpdateTolerance) { - log.warn("Skip ping update schedule because the last one was less then 2 seconds ago"); - return; - } - - if (log.isDebugEnabled()) { - log.trace("*** Update distributed ping cache: {}", this.pingCache); - } - - try { - - final Map mapping = this.clientPingMapper - .selectByExample() - .where( - ClientIndicatorRecordDynamicSqlSupport.type, - isEqualTo(ClientIndicatorType.LAST_PING.id)) - .build() - .execute() - .stream() - .collect(Collectors.toMap(entry -> entry.id, entry -> entry.lastPingTime)); - - if (mapping != null) { - this.pingCache.clear(); - this.pingCache.putAll(mapping); - this.lastUpdate = millisecondsNow; - } - - } catch (final Exception e) { - log.error("Error while trying to update distributed ping cache: {}", this.pingCache, e); - } - - this.lastUpdate = millisecondsNow; - } - - /** Update last ping time on persistent storage asynchronously within a defines thread pool with no - * waiting queue to skip further ping updates if all update threads are busy **/ - void updatePingAsync(final PingUpdate pingUpdate) { - try { - this.pingUpdateExecutor.execute(pingUpdate); - } catch (final Exception e) { - if (log.isDebugEnabled()) { - log.warn("Failed to schedule ping task: {}" + e.getMessage()); - } - } - } - - /** Create a PingUpdate for a specified SEB client connectionId. - * - * @param connectionId SEB client connection identifier - * @return PingUpdate for a specified SEB client connectionId */ - PingUpdate createPingUpdate(final Long connectionId) { - return new PingUpdate( - this.clientPingMapper, - this.initPingForConnection(connectionId)); - } - - /** Encapsulates a SEB client ping update on persistent storage */ - static final class PingUpdate implements Runnable { - - private final ClientPingMapper clientPingMapper; - final Long pingRecord; - - public PingUpdate(final ClientPingMapper clientPingMapper, final Long pingRecord) { - this.clientPingMapper = clientPingMapper; - this.pingRecord = pingRecord; - } - - @Override - /** Processes the ping update on persistent storage by using the current time stamp. */ - public void run() { - try { - this.clientPingMapper - .updatePingTime(this.pingRecord, Utils.getMillisecondsNow()); - } catch (final Exception e) { - log.error("Failed to update ping: {}", e.getMessage()); - } - } - } - - @Override - public void destroy() throws Exception { - if (this.taskRef != null) { - try { - final boolean cancel = this.taskRef.cancel(true); - if (!cancel) { - log.warn("Failed to cancel distributed ping cache update task"); - } - } catch (final Exception e) { - log.error("Failed to cancel distributed ping cache update task: ", e); - } - } - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/ErrorLogCountClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/ErrorLogCountClientIndicator.java index 9fac9609..3ed6b9d1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/ErrorLogCountClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/ErrorLogCountClientIndicator.java @@ -22,8 +22,11 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecord @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public final class ErrorLogCountClientIndicator extends AbstractLogLevelCountIndicator { - protected ErrorLogCountClientIndicator(final ClientEventRecordMapper clientEventRecordMapper) { - super(clientEventRecordMapper, EventType.ERROR_LOG); + protected ErrorLogCountClientIndicator( + final DistributedIndicatorValueService distributedPingCache, + final ClientEventRecordMapper clientEventRecordMapper) { + + super(distributedPingCache, clientEventRecordMapper, EventType.ERROR_LOG); } @Override @@ -31,9 +34,4 @@ public final class ErrorLogCountClientIndicator extends AbstractLogLevelCountInd return IndicatorType.ERROR_COUNT; } - @Override - public ClientIndicatorType indicatorType() { - return ClientIndicatorType.ERROR_LOG_COUNT; - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/InfoLogCountClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/InfoLogCountClientIndicator.java index 1c427d95..b1af59d0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/InfoLogCountClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/InfoLogCountClientIndicator.java @@ -22,8 +22,11 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecord @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class InfoLogCountClientIndicator extends AbstractLogLevelCountIndicator { - protected InfoLogCountClientIndicator(final ClientEventRecordMapper clientEventRecordMapper) { - super(clientEventRecordMapper, EventType.INFO_LOG); + protected InfoLogCountClientIndicator( + final DistributedIndicatorValueService distributedPingCache, + final ClientEventRecordMapper clientEventRecordMapper) { + + super(distributedPingCache, clientEventRecordMapper, EventType.INFO_LOG); } @Override @@ -31,9 +34,4 @@ public class InfoLogCountClientIndicator extends AbstractLogLevelCountIndicator return IndicatorType.INFO_COUNT; } - @Override - public ClientIndicatorType indicatorType() { - return ClientIndicatorType.INFO_LOG_COUNT; - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java index c119e988..5257ec70 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java @@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; @Lazy @@ -42,11 +43,16 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { private boolean missingPing = false; private boolean hidden = false; - public PingIntervalClientIndicator(final DistributedPingService distributedPingCache) { + public PingIntervalClientIndicator(final DistributedIndicatorValueService distributedPingCache) { super(distributedPingCache); this.cachingEnabled = true; } + @Override + protected long initValue() { + return Utils.getMillisecondsNow(); + } + @Override public void init( final Indicator indicatorDefinition, @@ -56,13 +62,9 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { super.init(indicatorDefinition, connectionId, active, cachingEnabled); - final long now = DateTimeUtils.currentTimeMillis(); - this.currentValue = computeValueAt(now); - if (Double.isNaN(this.currentValue)) { - this.currentValue = now; - } - + // init ping error threshold try { + indicatorDefinition .getThresholds() .stream() @@ -74,10 +76,10 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { this.pingErrorThreshold = DEFAULT_PING_ERROR_THRESHOLD; } + // init missing ping indicator if (!cachingEnabled) { try { - final double value = getValue(); - this.missingPing = this.pingErrorThreshold < value; + this.missingPing = this.pingErrorThreshold < getValue(); } catch (final Exception e) { log.error("Failed to initialize missingPing: {}", e.getMessage()); this.missingPing = true; @@ -86,11 +88,6 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { } - @Override - public ClientIndicatorType indicatorType() { - return ClientIndicatorType.LAST_PING; - } - @JsonIgnore public final boolean isMissingPing() { return this.missingPing; @@ -113,8 +110,10 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { @Override public double getValue() { - final double value = super.getValue(); - return DateTimeUtils.currentTimeMillis() - value; + if (!this.initialized) { + return Double.NaN; + } + return DateTimeUtils.currentTimeMillis() - this.currentValue; } @Override @@ -129,9 +128,12 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { @Override public final double computeValueAt(final long timestamp) { - if (!this.cachingEnabled && super.pingUpdate != null) { - final Long lastPing = this.distributedPingCache.getLastPing(super.pingUpdate.pingRecord); + if (!this.cachingEnabled && super.ditributedIndicatorValueRecordId != null) { + + final Long lastPing = this.distributedPingCache + .getIndicatorValue(super.ditributedIndicatorValueRecordId); + if (lastPing != null) { final double doubleValue = lastPing.doubleValue(); return Math.max(Double.isNaN(this.currentValue) ? doubleValue : this.currentValue, doubleValue); @@ -140,11 +142,15 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { return this.currentValue; } - return !this.valueInitializes ? timestamp : this.currentValue; + return !this.initialized ? timestamp : this.currentValue; } @Override public boolean missingPingUpdate(final long now) { + if (this.currentValue <= 0) { + return false; + } + final long value = now - (long) super.currentValue; if (this.missingPing) { if (this.pingErrorThreshold > value) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/WLANStatusIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/WLANStatusIndicator.java index 9c968239..87905dfa 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/WLANStatusIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/WLANStatusIndicator.java @@ -24,8 +24,11 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecord @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class WLANStatusIndicator extends AbstractLogNumberIndicator { - protected WLANStatusIndicator(final ClientEventRecordMapper clientEventRecordMapper) { - super(clientEventRecordMapper, EventType.INFO_LOG); + protected WLANStatusIndicator( + final DistributedIndicatorValueService distributedPingCache, + final ClientEventRecordMapper clientEventRecordMapper) { + + super(distributedPingCache, clientEventRecordMapper, EventType.INFO_LOG); super.tags = new String[] { API.LOG_EVENT_TAG_WLAN_STATUS }; } @@ -45,9 +48,4 @@ public class WLANStatusIndicator extends AbstractLogNumberIndicator { return IndicatorType.WLAN_STATUS; } - @Override - public ClientIndicatorType indicatorType() { - return ClientIndicatorType.WLAN_STATUS; - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/WarnLogCountClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/WarnLogCountClientIndicator.java index 602418a2..cd0ca3f1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/WarnLogCountClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/WarnLogCountClientIndicator.java @@ -22,8 +22,11 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecord @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class WarnLogCountClientIndicator extends AbstractLogLevelCountIndicator { - protected WarnLogCountClientIndicator(final ClientEventRecordMapper clientEventRecordMapper) { - super(clientEventRecordMapper, EventType.WARN_LOG); + protected WarnLogCountClientIndicator( + final DistributedIndicatorValueService distributedPingCache, + final ClientEventRecordMapper clientEventRecordMapper) { + + super(distributedPingCache, clientEventRecordMapper, EventType.WARN_LOG); } @Override @@ -31,8 +34,4 @@ public class WarnLogCountClientIndicator extends AbstractLogLevelCountIndicator return IndicatorType.WARN_COUNT; } - @Override - public ClientIndicatorType indicatorType() { - return ClientIndicatorType.WARN_LOG_COUNT; - } } diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index 055468b6..5f2936be 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -19,7 +19,7 @@ spring.datasource.hikari.leakDetectionThreshold=2000 sebserver.http.client.connect-timeout=15000 sebserver.http.client.connection-request-timeout=10000 sebserver.http.client.read-timeout=20000 -sebserver.webservice.distributed.pingUpdate=1000 +sebserver.webservice.distributed.updateInterval=1000 sebserver.webservice.distributed.connectionUpdate=2000 sebserver.webservice.clean-db-on-startup=false diff --git a/src/main/resources/config/application-ws.properties b/src/main/resources/config/application-ws.properties index c21b5e87..3ba4d8ec 100644 --- a/src/main/resources/config/application-ws.properties +++ b/src/main/resources/config/application-ws.properties @@ -39,7 +39,7 @@ sebserver.webservice.internalSecret=${sebserver.password} ### webservice networking sebserver.webservice.forceMaster=false sebserver.webservice.distributed=true -sebserver.webservice.distributed.pingUpdate=3000 +sebserver.webservice.distributed.updateInterval=3000 sebserver.webservice.http.external.scheme=https sebserver.webservice.http.external.servername= sebserver.webservice.http.external.port= 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 66495ec0..0b8c61ef 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 @@ -245,7 +245,7 @@ public class ModelObjectJSONGenerator { System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); domainObject = - new SimpleIndicatorValue(1L, IndicatorType.LAST_PING, 1.0); + new SimpleIndicatorValue(1L, 1.0); System.out.println(domainObject.getClass().getSimpleName() + ":"); System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); final long currentTimeMillis = System.currentTimeMillis(); @@ -267,9 +267,9 @@ public class ModelObjectJSONGenerator { null, null), Arrays.asList( - new SimpleIndicatorValue(1L, IndicatorType.LAST_PING, 1.0), - new SimpleIndicatorValue(2L, IndicatorType.ERROR_COUNT, 2.0), - new SimpleIndicatorValue(3L, IndicatorType.WARN_COUNT, 3.0))); + new SimpleIndicatorValue(1L, 1.0), + new SimpleIndicatorValue(2L, 2.0), + new SimpleIndicatorValue(3L, 3.0))); System.out.println(domainObject.getClass().getSimpleName() + ":"); System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); 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 e33853e0..c81c51ba 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 @@ -2149,7 +2149,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { assertEquals(exam.id, conData.clientConnection.examId); assertFalse(conData.indicatorValues.isEmpty()); final IndicatorValue indicatorValue = conData.indicatorValues.get(0); - assertEquals("LAST_PING", indicatorValue.getType().name); + assertEquals("1", String.valueOf(indicatorValue.getIndicatorId())); // LAST_PING indicator // disable connection final Result disableCall = restService.getBuilder(DisableClientConnection.class) diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java index 159d1489..17eda66e 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java @@ -28,7 +28,6 @@ import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; -import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper; @@ -466,7 +465,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { assertNotNull(ccdi.clientConnection.examId); assertFalse(ccdi.indicatorValues.isEmpty()); final IndicatorValue pingIndicator = ccdi.indicatorValues.iterator().next(); - assertTrue(pingIndicator.getType() == IndicatorType.LAST_PING); + assertTrue(pingIndicator.getIndicatorId() == 1L); super.sendPing(accessToken, connectionToken, 1); Thread.sleep(200); @@ -524,7 +523,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { assertNotNull(ccdi.clientConnection.examId); assertFalse(ccdi.indicatorValues.isEmpty()); final IndicatorValue pingIndicator = ccdi.indicatorValues.iterator().next(); - assertTrue(pingIndicator.getType() == IndicatorType.LAST_PING); + assertTrue(pingIndicator.getIndicatorId() == 1L); MockHttpServletResponse sendEvent = super.sendEvent( accessToken, diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java index 7d6bb3cc..d2fe117f 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java @@ -32,6 +32,7 @@ import ch.ethz.seb.sebserver.webservice.integration.api.admin.AdministrationAPII import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogLevelCountIndicator; @@ -94,27 +95,27 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { assertNotNull(connectionData); final Optional findFirst = connectionData.indicatorValues .stream() - .filter(indicator -> indicator.getType() == IndicatorType.ERROR_COUNT) + .filter(indicator -> ((ClientIndicator) indicator).getType() == IndicatorType.ERROR_COUNT) .findFirst(); assertTrue(findFirst.isPresent()); final IndicatorValue clientIndicator = findFirst.get(); - assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); this.sebClientConnectionService.notifyClientEvent( "token1", new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error")); waitForExecutor(); - assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); this.sebClientConnectionService.notifyClientEvent( "token1", new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error")); waitForExecutor(); - assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); // test reset indicator value and load it from persistent storage ((AbstractLogLevelCountIndicator) clientIndicator).reset(); - assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); } @@ -138,37 +139,37 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { assertNotNull(connectionData); final Optional findFirst = connectionData.indicatorValues .stream() - .filter(indicator -> indicator.getType() == IndicatorType.INFO_COUNT) + .filter(indicator -> ((ClientIndicator) indicator).getType() == IndicatorType.INFO_COUNT) .findFirst(); assertTrue(findFirst.isPresent()); final IndicatorValue clientIndicator = findFirst.get(); - assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientConnectionService.notifyClientEvent( "token2", new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); waitForExecutor(); - assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientConnectionService.notifyClientEvent( "token2", new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); waitForExecutor(); - assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientConnectionService.notifyClientEvent( "token2", new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); waitForExecutor(); - assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientConnectionService.notifyClientEvent( "token2", new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); waitForExecutor(); - assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientConnectionService.notifyClientEvent( "token2", new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); waitForExecutor(); - assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientConnectionService.notifyClientEvent( "token2", @@ -176,7 +177,7 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { waitForExecutor(); // test reset indicator value and load it from persistent storage ((AbstractLogLevelCountIndicator) clientIndicator).reset(); - assertEquals("3", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("3", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); } @@ -210,12 +211,12 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { assertNotNull(connectionData); final Optional findFirst = connectionData.indicatorValues .stream() - .filter(indicator -> indicator.getType() == IndicatorType.BATTERY_STATUS) + .filter(indicator -> ((ClientIndicator) indicator).getType() == IndicatorType.BATTERY_STATUS) .findFirst(); assertTrue(findFirst.isPresent()); final IndicatorValue clientIndicator = findFirst.get(); - assertEquals("--", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("--", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); this.sebClientConnectionService.notifyClientEvent( "token3", @@ -225,23 +226,23 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { "token3", new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some info other")); waitForExecutor(); - assertEquals("--", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("--", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); this.sebClientConnectionService.notifyClientEvent( "token3", new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0, " some info other")); waitForExecutor(); - assertEquals("90", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("90", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); this.sebClientConnectionService.notifyClientEvent( "token3", new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0, " some info other")); waitForExecutor(); - assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); // test reset indicator value and load it from persistent storage ((AbstractLogIndicator) clientIndicator).reset(); - assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator)); + assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); } } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/IndicatorValueJSONTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/IndicatorValueJSONTest.java index 9664020a..61a1940f 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/IndicatorValueJSONTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/IndicatorValueJSONTest.java @@ -11,19 +11,23 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator; import static org.junit.Assert.assertEquals; import org.junit.Test; +import org.mockito.Mockito; import com.fasterxml.jackson.core.JsonProcessingException; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; public class IndicatorValueJSONTest { @Test public void testJSONForExtendedIndicatorValue() throws JsonProcessingException { final JSONMapper jsonMapper = new JSONMapper(); - final ErrorLogCountClientIndicator indicator = new ErrorLogCountClientIndicator(null); + final DistributedIndicatorValueService mock = Mockito.mock(DistributedIndicatorValueService.class); + final ErrorLogCountClientIndicator indicator = new ErrorLogCountClientIndicator(mock, null); + indicator.init(new Indicator(1L, null, null, null, null, null, null, null), 2L, true, true); final String json = jsonMapper.writeValueAsString(indicator); - assertEquals("{\"indicatorType\":\"ERROR_COUNT\",\"indicatorValue\":\"NaN\"}", json); + assertEquals("{\"indicatorId\":1,\"indicatorValue\":\"NaN\"}", json); } } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicatorTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicatorTest.java index 37ec5cc4..ca4d3b11 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicatorTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicatorTest.java @@ -18,6 +18,7 @@ import org.mockito.Mockito; import com.fasterxml.jackson.core.JsonProcessingException; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; public class PingIntervalClientIndicatorTest { @@ -31,10 +32,12 @@ public class PingIntervalClientIndicatorTest { DateTimeUtils.setCurrentMillisProvider(() -> 1L); - final DistributedPingService distributedPingCache = Mockito.mock(DistributedPingService.class); + final DistributedIndicatorValueService distributedPingCache = + Mockito.mock(DistributedIndicatorValueService.class); final PingIntervalClientIndicator pingIntervalClientIndicator = new PingIntervalClientIndicator(distributedPingCache); + pingIntervalClientIndicator.init(null, null, true, true); assertEquals("0.0", String.valueOf(pingIntervalClientIndicator.getValue())); } @@ -43,10 +46,12 @@ public class PingIntervalClientIndicatorTest { DateTimeUtils.setCurrentMillisProvider(() -> 1L); - final DistributedPingService distributedPingCache = Mockito.mock(DistributedPingService.class); + final DistributedIndicatorValueService distributedPingCache = + Mockito.mock(DistributedIndicatorValueService.class); final PingIntervalClientIndicator pingIntervalClientIndicator = new PingIntervalClientIndicator(distributedPingCache); + pingIntervalClientIndicator.init(null, null, true, true); assertEquals("0.0", String.valueOf(pingIntervalClientIndicator.getValue())); DateTimeUtils.setCurrentMillisProvider(() -> 10L); @@ -58,13 +63,15 @@ public class PingIntervalClientIndicatorTest { public void testSerialization() throws JsonProcessingException { DateTimeUtils.setCurrentMillisProvider(() -> 1L); - final DistributedPingService distributedPingCache = Mockito.mock(DistributedPingService.class); + final DistributedIndicatorValueService distributedPingCache = + Mockito.mock(DistributedIndicatorValueService.class); final PingIntervalClientIndicator pingIntervalClientIndicator = new PingIntervalClientIndicator(distributedPingCache); + pingIntervalClientIndicator.init(new Indicator(2L, null, null, null, null, null, null, null), 1L, true, true); final JSONMapper jsonMapper = new JSONMapper(); final String json = jsonMapper.writeValueAsString(pingIntervalClientIndicator); - assertEquals("{\"indicatorValue\":0.0,\"indicatorType\":\"LAST_PING\"}", json); + assertEquals("{\"indicatorId\":2,\"indicatorValue\":0.0}", json); } }