From e3b44cb60b1b19b522f1cb8bef18a3e6191eda97 Mon Sep 17 00:00:00 2001 From: anhefti Date: Fri, 26 May 2023 13:26:03 +0200 Subject: [PATCH] SEBSERV-445 new SEB event store strategy with background tasks --- .../servicelayer/session/ClientIndicator.java | 13 +- .../session/EventHandlingStrategy.java | 1 + .../session/SEBClientSessionService.java | 9 +- .../impl/AsyncBatchEventSaveStrategy.java | 5 +- .../impl/ClientConnectionDataInternal.java | 1 + .../impl/EventHandlingStrategyFactory.java | 1 + .../impl/SEBClientEventBatchStore.java | 278 ++++++++++++++++++ .../impl/SEBClientSessionServiceImpl.java | 68 ++--- .../AbstractLogLevelCountIndicator.java | 25 +- .../indicator/AbstractLogNumberIndicator.java | 23 +- .../PingIntervalClientIndicator.java | 9 +- .../weblayer/api/ExamAPI_V1_Controller.java | 5 +- .../config/application-dev-ws.properties | 2 +- .../gbl/model/ModelObjectJSONGenerator.java | 10 +- .../api/exam/SebConnectionTest.java | 8 +- .../services/ClientEventServiceTest.java | 67 +++-- 16 files changed, 384 insertions(+), 141 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java 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 8c8b4cec..b61f1aad 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 @@ -14,10 +14,8 @@ 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.monitoring.IndicatorValue; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; /** A client indicator is a indicator value holder for a specific Indicator * on a running client connection. @@ -64,14 +62,9 @@ public interface ClientIndicator extends IndicatorValue { /** This gets called on a value change e.g.: when a ClientEvent was received. * NOTE: that this is called only on the same machine (server-instance) on that the ClientEvent was received. * - * @param event The ClientEvent instance */ - void notifyValueChange(ClientEvent event); - - /** This gets called on a value change e.g.: when a ClientEvent was received. - * NOTE: that this is called only on the same machine (server-instance) on that the ClientEvent was received. - * - * @param clientEventRecord The ClientEventRecord instance */ - void notifyValueChange(ClientEventRecord clientEventRecord); + * @param textValue The text based value + * @param numValue The value number */ + void notifyValueChange(String textValue, double numValue); /** This indicates if the indicator indicates an incident. This is the case if the actual indicator value * is above or below the max or min value defined by the indicator threshold settings. diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java index a9c9b7df..2ef1ee28 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java @@ -14,6 +14,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; /** A exam session SEB client event handling strategy implements a certain strategy to * store ClientEvent that are coming in within the specified endpoint in height frequency. */ +@Deprecated public interface EventHandlingStrategy extends Consumer { String EVENT_CONSUMER_STRATEGY_CONFIG_PROPERTY_KEY = "sebserver.webservice.api.exam.event-handling-strategy"; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java index b7f1ab14..1d342868 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java @@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.util.Result; public interface SEBClientSessionService { @@ -39,7 +38,13 @@ public interface SEBClientSessionService { * * @param connectionToken the connection token * @param event The SEB client event data */ - void notifyClientEvent(String connectionToken, final ClientEvent event); + void notifyClientEvent(String connectionToken, String jsonBody); + +// /** Notify a SEB client event for live indication and storing to database. +// * +// * @param connectionToken the connection token +// * @param event The SEB client event data */ +// void notifyClientEvent(String connectionToken, final ClientEvent event); /** This is used to confirm SEB instructions that must be confirmed by the SEB client. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java index 7639162e..27c35222 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java @@ -77,7 +77,7 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy { private final BlockingDeque eventQueue = new LinkedBlockingDeque<>(); private final BlockingDeque notificationQueue = new LinkedBlockingDeque<>(); private boolean workersRunning = false; - private boolean enabled = false; + private final boolean enabled = false; public AsyncBatchEventSaveStrategy( final SEBClientNotificationService sebClientNotificationService, @@ -95,7 +95,8 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy { @Override public void enable() { - this.enabled = true; + log.info("AsyncBatchEventSaveStrategy is deprecated"); + //this.enabled = true; } @EventListener(SEBServerInitEvent.class) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java index 59a64d3c..590ebe9f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java @@ -39,6 +39,7 @@ public class ClientConnectionDataInternal extends ClientConnectionData { private static final Logger log = LoggerFactory.getLogger(ClientConnectionDataInternal.class); + // TODO why list for type? Is it possible to restrict to one per type? final EnumMap> indicatorMapping; PingIntervalClientIndicator pingIndicator = null; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java index 1b5e0fb8..b507d647 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java @@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrate @Lazy @Service @WebServiceProfile +@Deprecated public class EventHandlingStrategyFactory { private final EventHandlingStrategy eventHandlingStrategy; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java new file mode 100644 index 00000000..78bb02f1 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2023 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.stream.Collectors; + +import javax.annotation.PreDestroy; + +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; + +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; +import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification; +import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification.NotificationType; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Pair; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService; + +@Lazy +@Component +@WebServiceProfile +public class SEBClientEventBatchStore { + + private static final Logger log = LoggerFactory.getLogger(SEBClientEventBatchStore.class); + + private final SEBClientNotificationService sebClientNotificationService; + private final SqlSessionFactory sqlSessionFactory; + private final TransactionTemplate transactionTemplate; + private final ExamSessionCacheService examSessionCacheService; + private final JSONMapper jsonMapper; + + private final SqlSessionTemplate sqlSessionTemplate; + private final ClientEventRecordMapper clientEventMapper; + + public SEBClientEventBatchStore( + final SEBClientNotificationService sebClientNotificationService, + final SqlSessionFactory sqlSessionFactory, + final PlatformTransactionManager transactionManager, + final ExamSessionCacheService examSessionCacheService, + final JSONMapper jsonMapper) { + + this.sebClientNotificationService = sebClientNotificationService; + this.sqlSessionFactory = sqlSessionFactory; + this.transactionTemplate = new TransactionTemplate(transactionManager); + this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + this.examSessionCacheService = examSessionCacheService; + this.jsonMapper = jsonMapper; + + this.sqlSessionTemplate = new SqlSessionTemplate( + this.sqlSessionFactory, + ExecutorType.BATCH); + this.clientEventMapper = this.sqlSessionTemplate.getMapper( + ClientEventRecordMapper.class); + } + + private final BlockingDeque eventDataQueue = new LinkedBlockingDeque<>(); + private final Collection events = new ArrayList<>(); + + public void accept(final String connectionToken, final String jsonBody) { + this.eventDataQueue.add(new EventData( + connectionToken, + Utils.getMillisecondsNow(), + jsonBody)); + } + + public void accept(final EventData eventData) { + this.eventDataQueue.add(eventData); + } + + @Scheduled( + fixedDelayString = "${sebserver.webservice.api.exam.session.event.batch.task:1000}", + initialDelay = 1000) + public void processEvents() { + + final long startTime = Utils.getMillisecondsNow(); + + //if (log.isDebugEnabled()) { + final int size = this.eventDataQueue.size(); + if (size > 1000) { + log.warn("******* There are more then 1000 SEB client logs in the waiting queue: {}", size); + } + //} + + try { + + this.events.clear(); + this.eventDataQueue.drainTo(this.events); + + if (this.events.isEmpty()) { + return; + } + + System.out.println("********** processing: " + this.events.size()); + + final List events = this.events + .stream() + .map(this::convertData) + .map(this::storeNotifications) + .filter(Objects::nonNull) + .map(this::toEventRecord) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + this.transactionTemplate + .execute(status -> { + events.stream().forEach(this.clientEventMapper::insert); + return null; + }); + + this.sqlSessionTemplate.flushStatements(); + + //if (log.isTraceEnabled()) { + log.info("****** Processing SEB events tuck: {}", Utils.getMillisecondsNow() - startTime); + //} + + } catch (final Exception e) { + log.error("Failed to process SEB events from eventDataQueue: ", e); + } + } + + private EventData convertData(final EventData eventData) { + if (eventData == null || eventData.jsonBody == null) { + return eventData; + } + + try { + + final ClientEvent eventModel = this.jsonMapper.readValue( + eventData.jsonBody, + ClientEvent.class); + + eventData.setEvent(eventModel); + return eventData; + + } catch (final Exception e) { + log.error("Failed to convert SEB event JSON data to internal data for: {}", eventData); + return eventData; + } + } + + private EventData storeNotifications(final EventData eventData) { + try { + + if (!eventData.event.eventType.isNotificationEvent) { + return eventData; + } + + System.out.println("******* storeNotifications: " + eventData); + + final ClientConnectionDataInternal clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + + final Pair typeAndPlainText = + ClientNotification.extractTypeAndPlainText(eventData.event.text); + final ClientNotification notification = new ClientNotification( + eventData.event.id, + clientConnection.getConnectionId(), + eventData.event.eventType, + eventData.event.getClientTime(), + eventData.event.getServerTime(), + (eventData.event.numValue != null) ? eventData.event.numValue.doubleValue() : null, + typeAndPlainText.b, + typeAndPlainText.a); + + switch (notification.eventType) { + case NOTIFICATION: { + this.sebClientNotificationService.newNotification(notification); + break; + } + case NOTIFICATION_CONFIRMED: { + this.sebClientNotificationService.confirmPendingNotification(notification); + break; + } + default: + } + + // skip this for further event processing + return null; + + } catch (final Exception e) { + log.error("Failed to verify and process notification for SEB event: {}", eventData); + return eventData; + } + } + + private ClientEventRecord toEventRecord(final EventData eventData) { + try { + final ClientConnectionDataInternal clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + + if (clientConnection == null) { + log.warn("Failed to retrieve ClientConnection for token {}. Skip this event", + eventData.connectionToken); + return null; + } + + // handle indicator update + clientConnection + .getIndicatorMapping(eventData.event.eventType) + .forEach(indicator -> indicator.notifyValueChange( + eventData.event.text, + (eventData.event.numValue != null) ? eventData.event.numValue : Double.NaN)); + + return ClientEvent.toRecord(eventData.event, clientConnection.clientConnection.id); + + } catch (final Exception e) { + log.error( + "Unexpected error while converting SEB event data to record for: {} Skip this event", eventData, + e); + return null; + } + } + + @PreDestroy + protected void shutdown() { + log.info("Shutdown SEBClientEventBatchStore..."); + if (this.sqlSessionTemplate != null) { + try { + this.sqlSessionTemplate.destroy(); + } catch (final Exception e) { + log.error("Failed to close and destroy the SqlSessionTemplate for this thread: {}", + Thread.currentThread(), + e); + } + } + } + + public final static class EventData { + final String connectionToken; + final Long serverTime; + final String jsonBody; + ClientEvent event; + + public EventData(final String connectionToken, final Long serverTime, final String jsonBody) { + this.connectionToken = connectionToken; + this.serverTime = serverTime; + this.jsonBody = jsonBody; + this.event = null; + } + + public EventData(final String connectionToken, final Long serverTime, final ClientEvent event) { + this.connectionToken = connectionToken; + this.serverTime = serverTime; + this.jsonBody = null; + this.event = event; + } + + void setEvent(final ClientEvent event) { + this.event = event; + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java index 4b70baa7..7e7b182f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java @@ -8,7 +8,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; -import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -26,14 +25,13 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrategy; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientVersionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchStore.EventData; @Lazy @Service @@ -45,7 +43,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { private final ClientConnectionDAO clientConnectionDAO; private final ExamSessionService examSessionService; private final ExamSessionCacheService examSessionCacheService; - private final EventHandlingStrategy eventHandlingStrategy; + private final SEBClientEventBatchStore sebClientEventBatchStore; private final SEBClientInstructionService sebInstructionService; private final ClientIndicatorFactory clientIndicatorFactory; private final InternalClientConnectionDataFactory internalClientConnectionDataFactory; @@ -55,7 +53,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { public SEBClientSessionServiceImpl( final ClientConnectionDAO clientConnectionDAO, final ExamSessionService examSessionService, - final EventHandlingStrategyFactory eventHandlingStrategyFactory, + final SEBClientEventBatchStore sebClientEventBatchStore, final SEBClientInstructionService sebInstructionService, final ClientIndicatorFactory clientIndicatorFactory, final InternalClientConnectionDataFactory internalClientConnectionDataFactory, @@ -65,7 +63,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { this.clientConnectionDAO = clientConnectionDAO; this.examSessionService = examSessionService; this.examSessionCacheService = examSessionService.getExamSessionCacheService(); - this.eventHandlingStrategy = eventHandlingStrategyFactory.get(); + this.sebClientEventBatchStore = sebClientEventBatchStore; this.sebInstructionService = sebInstructionService; this.clientIndicatorFactory = clientIndicatorFactory; this.internalClientConnectionDataFactory = internalClientConnectionDataFactory; @@ -127,32 +125,8 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { } @Override - public void notifyClientEvent( - final String connectionToken, - final ClientEvent event) { - - try { - final ClientConnectionDataInternal activeClientConnection = - this.examSessionService.getConnectionDataInternal(connectionToken); - - if (activeClientConnection != null) { - - // store event - this.eventHandlingStrategy.accept(ClientEvent.toRecord( - event, - activeClientConnection.getConnectionId())); - - // handle indicator update - activeClientConnection - .getIndicatorMapping(event.eventType) - .forEach(indicator -> indicator.notifyValueChange(event)); - - } else { - log.warn("No active ClientConnection found for connectionToken: {}", connectionToken); - } - } catch (final Exception e) { - log.error("Failed to process SEB client event: ", e); - } + public final void notifyClientEvent(final String connectionToken, final String jsonBody) { + this.sebClientEventBatchStore.accept(connectionToken, jsonBody); } @Override @@ -183,22 +157,28 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { final boolean missingPing = connection.getMissingPing(); final long millisecondsNow = Utils.getMillisecondsNow(); - final ClientEventRecord clientEventRecord = new ClientEventRecord( - null, - connection.getConnectionId(), - (missingPing) ? EventType.ERROR_LOG.id : EventType.INFO_LOG.id, - millisecondsNow, - millisecondsNow, - new BigDecimal(connection.pingIndicator.getValue()), - (missingPing) ? "Missing Client Ping" : "Client Ping Back To Normal"); + final String textValue = (missingPing) ? "Missing Client Ping" : "Client Ping Back To Normal"; + final double numValue = connection.pingIndicator.getValue(); - // store event and and flush cache - this.eventHandlingStrategy.accept(clientEventRecord); + final EventData eventData = new EventData( + connection.getClientConnection().connectionToken, + millisecondsNow, + new ClientEvent( + null, + connection.getConnectionId(), + (missingPing) ? EventType.ERROR_LOG : EventType.INFO_LOG, + millisecondsNow, + millisecondsNow, + numValue, + textValue)); + + // store missing-ping or ping-back event + this.sebClientEventBatchStore.accept(eventData); // update indicators - if (clientEventRecord.getType() != null && EventType.ERROR_LOG.id == clientEventRecord.getType()) { + if (EventType.ERROR_LOG == eventData.event.eventType) { connection.getIndicatorMapping(EventType.ERROR_LOG) - .forEach(indicator -> indicator.notifyValueChange(clientEventRecord)); + .forEach(indicator -> indicator.notifyValueChange(textValue, numValue)); } } } 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 769cacf1..8e009a02 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 @@ -15,12 +15,10 @@ import org.mybatis.dynamic.sql.SqlCriterion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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.util.Utils; 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.model.ClientEventRecord; public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicator { @@ -38,13 +36,13 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato } @Override - public void notifyValueChange(final ClientEvent event) { - valueChanged(event.text); - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { - valueChanged(clientEventRecord.getText()); + public final void notifyValueChange(final String textValue, final double numValue) { + if (this.tags == null || this.tags.length == 0 || hasTag(textValue)) { + if (super.ditributedIndicatorValueRecordId != null) { + this.distributedIndicatorValueService.incrementIndicatorValue(super.ditributedIndicatorValueRecordId); + } + this.currentValue = getValue() + 1d; + } } @Override @@ -112,13 +110,4 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato return result; } - private void valueChanged(final String eventText) { - if (this.tags == null || this.tags.length == 0 || hasTag(eventText)) { - if (super.ditributedIndicatorValueRecordId != null) { - this.distributedIndicatorValueService.incrementIndicatorValue(super.ditributedIndicatorValueRecordId); - } - this.currentValue = getValue() + 1d; - } - } - } 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 2ad92b56..b117408e 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 @@ -18,7 +18,6 @@ import org.mybatis.dynamic.sql.SqlCriterion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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.util.Utils; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; @@ -41,31 +40,19 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { } @Override - public void notifyValueChange(final ClientEvent event) { - valueChanged(event.text, event.getValue()); - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { - final BigDecimal numericValue = clientEventRecord.getNumericValue(); - if (numericValue != null) { - valueChanged(clientEventRecord.getText(), numericValue.doubleValue()); - } - } - - private void valueChanged(final String text, final double value) { - if (this.tags == null || this.tags.length == 0 || hasTag(text)) { + public void notifyValueChange(final String textValue, final double numValue) { + if (this.tags == null || this.tags.length == 0 || hasTag(textValue)) { if (super.ditributedIndicatorValueRecordId != null) { if (!this.distributedIndicatorValueService.updateIndicatorValueAsync( this.ditributedIndicatorValueRecordId, - Double.valueOf(value).longValue())) { + Double.valueOf(numValue).longValue())) { this.currentValue = computeValueAt(Utils.getMillisecondsNow()); } else { - this.currentValue = value; + this.currentValue = numValue; } } else { - this.currentValue = value; + this.currentValue = numValue; } } } 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 dbd09771..47485823 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 @@ -19,8 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; 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.webservice.datalayer.batis.model.ClientEventRecord; @Lazy @Component(IndicatorType.Names.LAST_PING) @@ -92,12 +90,7 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { } @Override - public void notifyValueChange(final ClientEvent event) { - - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { + public void notifyValueChange(final String textValue, final double numValue) { } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index 4d46c208..a3e0a8df 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -45,7 +45,6 @@ import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.RunningExamInfo; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; @@ -358,10 +357,10 @@ public class ExamAPI_V1_Controller { @ResponseStatus(value = HttpStatus.NO_CONTENT) public void event( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, - @RequestBody(required = true) final ClientEvent event) { + @RequestBody(required = true) final String jsonBody) { this.sebClientSessionService - .notifyClientEvent(connectionToken, event); + .notifyClientEvent(connectionToken, jsonBody); } private Long getInstitutionId(final Principal principal) { diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index 25b871d9..1840c26a 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -25,7 +25,7 @@ sebserver.webservice.clean-db-on-startup=false # webservice configuration sebserver.init.adminaccount.gen-on-init=false -sebserver.webservice.distributed=true +sebserver.webservice.distributed=false #sebserver.webservice.master.delay.threshold=10000 sebserver.webservice.http.external.scheme=http sebserver.webservice.http.external.servername=localhost 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 5c112723..6d7b05fd 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 @@ -72,7 +72,6 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; import ch.ethz.seb.sebserver.gbl.model.user.UserMod; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.monitoring.SimpleIndicatorValue; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.PendingNotificationIndication; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal; @@ -406,15 +405,8 @@ public class ModelObjectJSONGenerator { } @Override - public void notifyValueChange(final ClientEvent event) { + public void notifyValueChange(final String textValue, final double numValue) { // TODO Auto-generated method stub - - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { - // TODO Auto-generated method stub - } @Override 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 39c6439d..fb2062d9 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 @@ -42,6 +42,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchStore; @Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql", "classpath:data-test-additional.sql" }) public class SebConnectionTest extends ExamAPIIntegrationTester { @@ -56,6 +57,8 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { private ExamDAO examDAO; @Autowired private LmsAPIService lmsAPIService; + @Autowired + private SEBClientEventBatchStore sebClientEventBatchStore; @Before public void init() { @@ -558,6 +561,8 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { // check correct response assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); + this.sebClientEventBatchStore.processEvents(); + // check event stored on db List events = this.clientEventRecordMapper .selectByExample() @@ -582,6 +587,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { 10000.0, "testEvent2"); + this.sebClientEventBatchStore.processEvents(); // check correct response assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); @@ -616,7 +622,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { "testEvent1"); // check correct response assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); - + this.sebClientEventBatchStore.processEvents(); final List events = this.clientEventRecordMapper .selectByExample() .build() 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 4e70e74f..8625cc83 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 @@ -12,15 +12,12 @@ import static org.junit.Assert.*; import java.util.Collection; import java.util.Optional; -import java.util.concurrent.Executor; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.test.context.jdbc.Sql; -import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; +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.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; @@ -35,6 +32,7 @@ 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.SEBClientSessionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchStore; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogLevelCountIndicator; @@ -50,8 +48,12 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { @Autowired private SEBClientSessionService sebClientSessionService; @Autowired - @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) - private Executor executor; + private SEBClientEventBatchStore sebClientEventBatchStore; +// @Autowired +// @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) +// private Executor executor; + @Autowired + private JSONMapper jsonMapper; @Test public void testCreateLogEvents() { @@ -110,13 +112,15 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { this.sebClientSessionService.notifyClientEvent( "token1", - new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); this.sebClientSessionService.notifyClientEvent( "token1", - new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); @@ -126,6 +130,15 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { } + private String writeValueAsString(final ClientEvent event) { + try { + return this.jsonMapper.writeValueAsString(event); + } catch (final Exception e) { + e.printStackTrace(); + return null; + } + } + @Test public void testInfoLogWithTagCountIndicator() { @@ -156,33 +169,39 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error"))); waitForExecutor(); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error"))); waitForExecutor(); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error"))); waitForExecutor(); // test reset indicator value and load it from persistent storage ((AbstractLogLevelCountIndicator) clientIndicator).reset(); @@ -191,13 +210,7 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { } private void waitForExecutor() { - try { - while (((ThreadPoolTaskExecutor) this.executor).getActiveCount() > 0) { - Thread.sleep(20); - } - } catch (final Exception e) { - e.printStackTrace(); - } + this.sebClientEventBatchStore.processEvents(); } @Test @@ -231,23 +244,27 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some info other")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some info other"))); waitForExecutor(); this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some info other")); + writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, + " some info other"))); waitForExecutor(); assertEquals("--", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0, " some info other")); + writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0, + " some info other"))); waitForExecutor(); assertEquals("90", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0, " some info other")); + writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0, + " some info other"))); waitForExecutor(); assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS));