diff --git a/src/main/java/ch/ethz/seb/sebserver/SEBServerInitEvent.java b/src/main/java/ch/ethz/seb/sebserver/SEBServerInitEvent.java index 5260573b..6d6d29bf 100644 --- a/src/main/java/ch/ethz/seb/sebserver/SEBServerInitEvent.java +++ b/src/main/java/ch/ethz/seb/sebserver/SEBServerInitEvent.java @@ -10,11 +10,16 @@ package ch.ethz.seb.sebserver; import org.springframework.context.ApplicationEvent; +import ch.ethz.seb.sebserver.webservice.WebserviceInit; + public class SEBServerInitEvent extends ApplicationEvent { private static final long serialVersionUID = -3608628289559324471L; - public SEBServerInitEvent(final Object source) { - super(source); + public final WebserviceInit webserviceInit; + + public SEBServerInitEvent(final WebserviceInit webserviceInit) { + super(webserviceInit); + this.webserviceInit = webserviceInit; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/GuiInit.java b/src/main/java/ch/ethz/seb/sebserver/gui/GuiInit.java index acd46643..b66ad931 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/GuiInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/GuiInit.java @@ -43,16 +43,10 @@ public class GuiInit implements ApplicationListener { this.sebServerInit.init(); - SEBServerInit.INIT_LOGGER.info("---->"); - SEBServerInit.INIT_LOGGER.info("----> **** GUI Service starting up... ****"); - - SEBServerInit.INIT_LOGGER.info("---->"); - SEBServerInit.INIT_LOGGER.info("----> GUI Service successfully successfully started up!"); - SEBServerInit.INIT_LOGGER.info("---->"); - -// final String webServiceProtocol = this.environment.getProperty("sebserver.gui.webservice.protocol", "http"); -// final String webServiceAddress = this.environment.getRequiredProperty("sebserver.gui.webservice.address"); -// final String webServicePort = this.environment.getProperty("sebserver.gui.webservice.port", "80"); + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); + SEBServerInit.INIT_LOGGER.info("----> *** GUI Service starting up... ***"); + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); + SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info( "----> Webservice connection: {}", @@ -82,6 +76,11 @@ public class GuiInit implements ApplicationListener { SEBServerInit.INIT_LOGGER.info("---->"); SEBServerInit.INIT_LOGGER.info("----> Webservice admin API basic access: --" + webServiceAPIBasicAccess + "--"); + SEBServerInit.INIT_LOGGER.info("---->"); + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); + SEBServerInit.INIT_LOGGER.info("----> *** GUI Service successfully successfully started up! ***"); + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); + } } 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 3d04379b..917f57b9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java @@ -60,6 +60,8 @@ public class WebserviceInfo { private final boolean isDistributed; private final String webserviceUUID; + private final long distributedPingUpdateInterval; + private Map lmsExternalAddressAlias; public WebserviceInfo(final Environment environment) { @@ -74,6 +76,11 @@ 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", + Long.class, + 3000L); + if (StringUtils.isEmpty(this.webserverName)) { log.warn("NOTE: External server name, property : 'sebserver.webservice.http.external.servername' " + "is not set from configuration. The external server name is set to the server address!"); @@ -160,6 +167,10 @@ public class WebserviceInfo { return this.serverURLPrefix + this.discoveryEndpoint; } + public long getDistributedPingUpdateInterval() { + return this.distributedPingUpdateInterval; + } + public String getLocalHostName() { try { return InetAddress.getLocalHost().getHostName(); 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 2ab1f59d..f1d49a8e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java @@ -15,6 +15,7 @@ import javax.annotation.PreDestroy; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Import; @@ -31,6 +32,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.WebserviceInfoDAO; @Import(DataSourceAutoConfiguration.class) public class WebserviceInit implements ApplicationListener { + private final ApplicationContext applicationContext; private final SEBServerInit sebServerInit; private final Environment environment; private final WebserviceInfo webserviceInfo; @@ -41,20 +43,26 @@ public class WebserviceInit implements ApplicationListener **** Webservice starting up... ****"); - + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); + SEBServerInit.INIT_LOGGER.info("----> *** Webservice starting up... ***"); + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> Register Webservice: {}", this.webserviceInfo.getWebserviceUUID()); try { - this.webserviceInfoDAO.register( + final boolean register = this.webserviceInfoDAO.register( this.webserviceInfo.getWebserviceUUID(), InetAddress.getLocalHost().getHostAddress()); + if (register) { + SEBServerInit.INIT_LOGGER.info("----> Successfully register Webservice instance"); + } } catch (final Exception e) { - SEBServerInit.INIT_LOGGER.error("Failed to register webservice: ", e); + SEBServerInit.INIT_LOGGER.error("----> Failed to register webservice: ", e); } SEBServerInit.INIT_LOGGER.info("----> "); @@ -81,23 +93,31 @@ public class WebserviceInit implements ApplicationListener "); - SEBServerInit.INIT_LOGGER.info("----> **** Webservice successfully started up! **** "); + // Run the data base integrity checks and fixes if configured + this.dbIntegrityChecker.checkIntegrity(); + + // Create an initial admin account if requested and not already in the data-base + this.adminUserInitializer.initAdminAccount(); + + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); + SEBServerInit.INIT_LOGGER.info("----> *** Webservice Info: ***"); + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); SEBServerInit.INIT_LOGGER.info("---->"); - SEBServerInit.INIT_LOGGER.info("----> *** Info:"); SEBServerInit.INIT_LOGGER.info("----> JDBC connection pool max size: {}", this.environment.getProperty("spring.datasource.hikari.maximumPoolSize")); if (this.webserviceInfo.isDistributed()) { + SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> Distributed Setup: {}", this.webserviceInfo.getWebserviceUUID()); - SEBServerInit.INIT_LOGGER.info("------> Ping update time: {}", + SEBServerInit.INIT_LOGGER.info("----> Ping update time: {}", this.environment.getProperty("sebserver.webservice.distributed.pingUpdate")); - SEBServerInit.INIT_LOGGER.info("------> Connection update time: {}", + SEBServerInit.INIT_LOGGER.info("----> Connection update time: {}", this.environment.getProperty("sebserver.webservice.distributed.connectionUpdate", "2000")); } try { + SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address")); SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port")); SEBServerInit.INIT_LOGGER.info("---->"); @@ -122,19 +142,18 @@ public class WebserviceInit implements ApplicationListener"); SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty()); - // Run the data base integrity checks and fixes if configured - this.dbIntegrityChecker.checkIntegrity(); - - // Create an initial admin account if requested and not already in the data-base - this.adminUserInitializer.initAdminAccount(); + SEBServerInit.INIT_LOGGER.info("---->"); + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); + SEBServerInit.INIT_LOGGER.info("----> *** Webservice successfully started up! ***"); + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); } @PreDestroy public void gracefulShutdown() { - SEBServerInit.INIT_LOGGER.info("**** Gracefully Shutdown of SEB Server instance {} ****", + SEBServerInit.INIT_LOGGER.info("*********************************************************"); + SEBServerInit.INIT_LOGGER.info("**** Gracefully Shutdown of SEB Server instance {}", this.webserviceInfo.getHostAddress()); - SEBServerInit.INIT_LOGGER.info("---->"); SEBServerInit.INIT_LOGGER.info("----> Unregister Webservice: {}", this.webserviceInfo.getWebserviceUUID()); 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/ClientPingMapper.java index 302fb5fd..3327d646 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/ClientPingMapper.java @@ -50,12 +50,12 @@ public interface ClientPingMapper { int update(UpdateStatementProvider updateStatement); @SelectProvider(type = SqlProviderAdapter.class, method = "select") - @ResultType(ClientEventLastPingRecord.class) + @ResultType(ClientLastPingRecord.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_) { return SelectDSL.selectWithMapper( @@ -78,7 +78,7 @@ public interface ClientPingMapper { .execute(); } - default QueryExpressionDSL>> selectByExample() { + default QueryExpressionDSL>> selectByExample() { return SelectDSL.selectWithMapper( this::selectMany, @@ -95,12 +95,12 @@ public interface ClientPingMapper { .execute(); } - final class ClientEventLastPingRecord { + final class ClientLastPingRecord { public final Long id; public final Long lastPingTime; - public ClientEventLastPingRecord( + public ClientLastPingRecord( final Long id, final Long value) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java index 0194f982..f061ac83 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java @@ -41,6 +41,8 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionR import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordDynamicSqlSupport; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientInstructionRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientInstructionRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport; @@ -63,15 +65,18 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO { private final ClientConnectionRecordMapper clientConnectionRecordMapper; private final ClientEventRecordMapper clientEventRecordMapper; private final ClientInstructionRecordMapper clientInstructionRecordMapper; + private final ClientIndicatorRecordMapper clientIndicatorRecordMapper; protected ClientConnectionDAOImpl( final ClientConnectionRecordMapper clientConnectionRecordMapper, final ClientEventRecordMapper clientEventRecordMapper, - final ClientInstructionRecordMapper clientInstructionRecordMapper) { + final ClientInstructionRecordMapper clientInstructionRecordMapper, + final ClientIndicatorRecordMapper clientIndicatorRecordMapper) { this.clientConnectionRecordMapper = clientConnectionRecordMapper; this.clientEventRecordMapper = clientEventRecordMapper; this.clientInstructionRecordMapper = clientInstructionRecordMapper; + this.clientIndicatorRecordMapper = clientIndicatorRecordMapper; } @Override @@ -474,7 +479,15 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO { return Collections.emptyList(); } - // first delete all related client events + // delete all related client indicators + this.clientIndicatorRecordMapper.deleteByExample() + .where( + ClientIndicatorRecordDynamicSqlSupport.clientConnectionId, + SqlBuilder.isIn(ids)) + .build() + .execute(); + + // delete all related client events this.clientEventRecordMapper.deleteByExample() .where( ClientEventRecordDynamicSqlSupport.clientConnectionId, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java index b2970486..660f2bdb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java @@ -237,18 +237,19 @@ public class ExamAdminServiceImpl implements ExamAdminService { @Override public Result isProctoringEnabled(final Long examId) { - final Result result = this.additionalAttributesDAO.getAdditionalAttribute( + return this.additionalAttributesDAO.getAdditionalAttribute( EntityType.EXAM, examId, ProctoringServiceSettings.ATTR_ENABLE_PROCTORING) .map(rec -> BooleanUtils.toBoolean(rec.getValue())) - .onError(error -> log.warn("Failed to verify proctoring enabled for exam: {}, {}", - examId, - error.getMessage())); - if (result.hasError()) { - return Result.of(false); - } - return result; + .onErrorDo(error -> { + if (log.isDebugEnabled()) { + log.warn("Failed to verify proctoring enabled for exam: {}, {}", + examId, + error.getMessage()); + } + return false; + }); } @Override 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 12d9f913..3efb8302 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 @@ -15,6 +15,7 @@ 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. @@ -31,6 +32,11 @@ 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 exam identifier of the client connection of this ClientIndicator * * @return 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 f5ca4512..04e19675 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 @@ -44,7 +44,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.DistributedPingCache; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedPingService; import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException; @Lazy @@ -70,7 +70,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic private final SEBClientInstructionService sebInstructionService; private final SEBClientNotificationService sebClientNotificationService; private final ExamAdminService examAdminService; - private final DistributedPingCache distributedPingCache; + private final DistributedPingService distributedPingCache; private final boolean isDistributedSetup; protected SEBClientConnectionServiceImpl( @@ -80,7 +80,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic final SEBClientInstructionService sebInstructionService, final SEBClientNotificationService sebClientNotificationService, final ExamAdminService examAdminService, - final DistributedPingCache distributedPingCache) { + final DistributedPingService distributedPingCache) { this.examSessionService = examSessionService; this.examSessionCacheService = examSessionService.getExamSessionCacheService(); 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 f4097992..5cb1a7dc 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 @@ -11,18 +11,14 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator; import java.util.Collections; import java.util.EnumSet; import java.util.Set; -import java.util.concurrent.Executor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Qualifier; -import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; -import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientPingMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedPingService.PingUpdate; public abstract class AbstractPingIndicator extends AbstractClientIndicator { @@ -30,17 +26,11 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator { private final Set EMPTY_SET = Collections.unmodifiableSet(EnumSet.noneOf(EventType.class)); - private final Executor executor; - protected final DistributedPingCache distributedPingCache; - + protected final DistributedPingService distributedPingCache; protected PingUpdate pingUpdate = null; - protected AbstractPingIndicator( - final DistributedPingCache distributedPingCache, - @Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor executor) { - + protected AbstractPingIndicator(final DistributedPingService distributedPingCache) { super(); - this.executor = executor; this.distributedPingCache = distributedPingCache; } @@ -55,9 +45,9 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator { if (!this.cachingEnabled && this.active) { try { - createPingUpdate(); + this.pingUpdate = this.distributedPingCache.createPingUpdate(connectionId); } catch (final Exception e) { - createPingUpdate(); + this.pingUpdate = this.distributedPingCache.createPingUpdate(connectionId); } } } @@ -74,15 +64,7 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator { } } - // 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 - try { - this.executor.execute(this.pingUpdate); - } catch (final Exception e) { - if (log.isDebugEnabled()) { - log.warn("Failed to schedule ping task: {}" + e.getMessage()); - } - } + this.distributedPingCache.updatePingAsync(this.pingUpdate); } } @@ -93,21 +75,15 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator { } try { - createPingUpdate(); + this.pingUpdate = this.distributedPingCache.createPingUpdate(this.connectionId); if (this.pingUpdate == null) { - createPingUpdate(); + this.pingUpdate = this.distributedPingCache.createPingUpdate(this.connectionId); } } catch (final Exception e) { log.error("Failed to recover ping record for connection: {}", this.connectionId, e); } } - private void createPingUpdate() { - this.pingUpdate = new PingUpdate( - this.distributedPingCache.getClientPingMapper(), - this.distributedPingCache.initPingForConnection(this.connectionId)); - } - @Override public Set observedEvents() { return this.EMPTY_SET; @@ -115,26 +91,4 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator { public abstract ClientEventRecord updateLogEvent(final long now); - 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 - public void run() { - try { - this.clientPingMapper - .updatePingTime(this.pingRecord, Utils.getMillisecondsNow()); - } catch (final Exception e) { - log.error("Failed to update ping: {}", e.getMessage()); - } - } - - } - } 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 0ab30a3c..53994747 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 @@ -45,4 +45,9 @@ 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/DistributedPingCache.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedPingService.java similarity index 66% rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedPingCache.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedPingService.java index 71c96bde..91a1156d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedPingCache.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedPingService.java @@ -13,6 +13,7 @@ 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; @@ -20,50 +21,78 @@ 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.Value; +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.ClientEventLastPingRecord; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; +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 -public class DistributedPingCache implements DisposableBean { +public class DistributedPingService implements DisposableBean { - private static final Logger log = LoggerFactory.getLogger(DistributedPingCache.class); + private static final Logger log = LoggerFactory.getLogger(DistributedPingService.class); + private final Executor pingUpdateExecutor; private final ClientIndicatorRecordMapper clientIndicatorRecordMapper; private final ClientPingMapper clientPingMapper; - private final long pingUpdateTolerance; + private long pingUpdateTolerance; private ScheduledFuture taskRef; private final Map pingCache = new ConcurrentHashMap<>(); private long lastUpdate = 0L; - public DistributedPingCache( + public DistributedPingService( + @Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor pingUpdateExecutor, final ClientIndicatorRecordMapper clientIndicatorRecordMapper, - final ClientPingMapper clientPingMapper, - final WebserviceInfo webserviceInfo, - final TaskScheduler taskScheduler, - @Value("${sebserver.webservice.distributed.pingUpdate:3000}") final long pingUpdate) { + final ClientPingMapper clientPingMapper) { + this.pingUpdateExecutor = pingUpdateExecutor; this.clientIndicatorRecordMapper = clientIndicatorRecordMapper; this.clientPingMapper = clientPingMapper; - this.pingUpdateTolerance = pingUpdate * 2 / 3; + } + + @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::updatePings, pingUpdate); + this.taskRef = taskScheduler.scheduleAtFixedRate( + this::persistentPingUpdate, + 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; } @@ -141,10 +170,10 @@ public class DistributedPingCache implements DisposableBean { log.debug("*** Delete ping record for SEB connection: {}", connectionId); } - final Collection records = this.clientPingMapper + final Collection records = this.clientPingMapper .selectByExample() - .where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(connectionId)) - .and(ClientEventRecordDynamicSqlSupport.type, isEqualTo(ClientIndicatorType.LAST_PING.id)) + .where(ClientIndicatorRecordDynamicSqlSupport.clientConnectionId, isEqualTo(connectionId)) + .and(ClientIndicatorRecordDynamicSqlSupport.type, isEqualTo(ClientIndicatorType.LAST_PING.id)) .build() .execute(); @@ -194,8 +223,7 @@ public class DistributedPingCache implements DisposableBean { } } - private void updatePings() { - + public void persistentPingUpdate() { if (this.pingCache.isEmpty()) { return; } @@ -215,7 +243,7 @@ public class DistributedPingCache implements DisposableBean { final Map mapping = this.clientPingMapper .selectByExample() .where( - ClientEventRecordDynamicSqlSupport.type, + ClientIndicatorRecordDynamicSqlSupport.type, isEqualTo(ClientIndicatorType.LAST_PING.id)) .build() .execute() @@ -235,6 +263,45 @@ public class DistributedPingCache implements DisposableBean { 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()); + } + } + } + + PingUpdate createPingUpdate(final Long connectionId) { + return new PingUpdate( + this.getClientPingMapper(), + this.initPingForConnection(connectionId)); + } + + 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 + 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) { 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 f8e2fa8a..9fac9609 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 @@ -31,4 +31,9 @@ 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 4c9efb03..1c427d95 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 @@ -31,4 +31,9 @@ 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 7f336f88..cb2b7382 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 @@ -10,12 +10,10 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator; import java.math.BigDecimal; import java.util.Comparator; -import java.util.concurrent.Executor; import org.joda.time.DateTimeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Scope; @@ -24,7 +22,6 @@ import org.springframework.stereotype.Component; import com.fasterxml.jackson.annotation.JsonIgnore; import ch.ethz.seb.sebserver.gbl.Constants; -import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; 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; @@ -47,10 +44,8 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { private boolean missingPing = false; private boolean hidden = false; - public PingIntervalClientIndicator( - final DistributedPingCache distributedPingCache, - @Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor executor) { - super(distributedPingCache, executor); + public PingIntervalClientIndicator(final DistributedPingService distributedPingCache) { + super(distributedPingCache); this.cachingEnabled = true; } @@ -89,6 +84,11 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { } + @Override + public ClientIndicatorType indicatorType() { + return ClientIndicatorType.LAST_PING; + } + @JsonIgnore public final boolean isMissingPing() { return this.missingPing; 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 89ce803f..9c968239 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 @@ -45,4 +45,9 @@ 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 3f931a95..602418a2 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 @@ -30,4 +30,9 @@ public class WarnLogCountClientIndicator extends AbstractLogLevelCountIndicator public IndicatorType getType() { return IndicatorType.WARN_COUNT; } + + @Override + public ClientIndicatorType indicatorType() { + return ClientIndicatorType.WARN_LOG_COUNT; + } } 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 43146d4d..37ec5cc4 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 @@ -10,8 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator; import static org.junit.Assert.assertEquals; -import java.util.concurrent.Executor; - import org.joda.time.DateTimeUtils; import org.junit.After; import org.junit.Test; @@ -20,7 +18,6 @@ import org.mockito.Mockito; import com.fasterxml.jackson.core.JsonProcessingException; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO; public class PingIntervalClientIndicatorTest { @@ -34,12 +31,10 @@ public class PingIntervalClientIndicatorTest { DateTimeUtils.setCurrentMillisProvider(() -> 1L); - final ClientEventDAO clientEventDAO = Mockito.mock(ClientEventDAO.class); - final DistributedPingCache distributedPingCache = Mockito.mock(DistributedPingCache.class); - final Executor executor = Mockito.mock(Executor.class); + final DistributedPingService distributedPingCache = Mockito.mock(DistributedPingService.class); final PingIntervalClientIndicator pingIntervalClientIndicator = - new PingIntervalClientIndicator(distributedPingCache, executor); + new PingIntervalClientIndicator(distributedPingCache); assertEquals("0.0", String.valueOf(pingIntervalClientIndicator.getValue())); } @@ -48,12 +43,10 @@ public class PingIntervalClientIndicatorTest { DateTimeUtils.setCurrentMillisProvider(() -> 1L); - final ClientEventDAO clientEventDAO = Mockito.mock(ClientEventDAO.class); - final DistributedPingCache distributedPingCache = Mockito.mock(DistributedPingCache.class); - final Executor executor = Mockito.mock(Executor.class); + final DistributedPingService distributedPingCache = Mockito.mock(DistributedPingService.class); final PingIntervalClientIndicator pingIntervalClientIndicator = - new PingIntervalClientIndicator(distributedPingCache, executor); + new PingIntervalClientIndicator(distributedPingCache); assertEquals("0.0", String.valueOf(pingIntervalClientIndicator.getValue())); DateTimeUtils.setCurrentMillisProvider(() -> 10L); @@ -65,12 +58,10 @@ public class PingIntervalClientIndicatorTest { public void testSerialization() throws JsonProcessingException { DateTimeUtils.setCurrentMillisProvider(() -> 1L); - final ClientEventDAO clientEventDAO = Mockito.mock(ClientEventDAO.class); - final DistributedPingCache distributedPingCache = Mockito.mock(DistributedPingCache.class); - final Executor executor = Mockito.mock(Executor.class); + final DistributedPingService distributedPingCache = Mockito.mock(DistributedPingService.class); final PingIntervalClientIndicator pingIntervalClientIndicator = - new PingIntervalClientIndicator(distributedPingCache, executor); + new PingIntervalClientIndicator(distributedPingCache); final JSONMapper jsonMapper = new JSONMapper(); final String json = jsonMapper.writeValueAsString(pingIntervalClientIndicator); assertEquals("{\"indicatorValue\":0.0,\"indicatorType\":\"LAST_PING\"}", json);