Merge remote-tracking branch 'origin/newSEBEventStoreStrategy' into development

This commit is contained in:
anhefti 2023-06-01 16:03:23 +02:00
commit a57e371580
30 changed files with 566 additions and 211 deletions

View file

@ -34,6 +34,7 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer {
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("asyncService-");
executor.setThreadPriority(Thread.NORM_PRIORITY);
executor.initialize();
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;

View file

@ -371,7 +371,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
}
public void updateGUI() {
if (this.needsSort) {
sortTable();
}
@ -725,10 +724,12 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
this.indicatorWeights[i] = -1;
}
}
this.monitoringData = monitoringData;
if (this.indicatorValueChanged) {
updateIndicatorWeight();
}
this.monitoringData = monitoringData;
return this.staticData == null
|| this.staticData == ClientStaticData.NULL_DATA

View file

@ -29,4 +29,28 @@ public class WebserviceConfig {
return aes256jnCryptor;
}
// @Bean
// public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
// return (tomcat) -> tomcat.addConnectorCustomizers((connector) -> {
// if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol) {
// System.out.println("*************** tomcatCustomizer");
// final AbstractHttp11Protocol<?> protocolHandler = (AbstractHttp11Protocol<?>) connector
// .getProtocolHandler();
// protocolHandler.setKeepAliveTimeout(60000);
// protocolHandler.setMaxKeepAliveRequests(3000);
// protocolHandler.setUseKeepAliveResponseHeader(true);
// protocolHandler.setMinSpareThreads(200);
// protocolHandler.setProcessorCache(-1);
// protocolHandler.setTcpNoDelay(true);
// protocolHandler.setThreadPriority(Thread.NORM_PRIORITY + 1);
// protocolHandler.setMaxConnections(2000);
// if (protocolHandler instanceof Http11NioProtocol) {
// System.out.println("*************** Http11NioProtocol");
// ((Http11NioProtocol) protocolHandler).setPollerThreadPriority(Thread.MAX_PRIORITY);
// }
//
// }
// });
// }
}

View file

@ -101,7 +101,7 @@ public class WebserviceInfo {
this.distributedUpdateInterval = environment.getProperty(
"sebserver.webservice.distributed.updateInterval",
Long.class,
3000L);
2000L);
this.activeProfiles = new HashSet<>(Arrays.asList(environment.getActiveProfiles()));

View file

@ -814,10 +814,13 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.status,
SqlBuilder.isIn(ClientConnection.SECURE_CHECK_STATES))
SqlBuilder.isEqualTo(ConnectionStatus.ACTIVE.name()))
.and(
ClientConnectionRecordDynamicSqlSupport.examId,
SqlBuilder.isEqualTo(examId))
.and(
ClientConnectionRecordDynamicSqlSupport.clientVersion,
SqlBuilder.isNotNull())
.and(
ClientConnectionRecordDynamicSqlSupport.clientVersionGranted,
SqlBuilder.isNull())

View file

@ -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.

View file

@ -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<ClientEventRecord> {
String EVENT_CONSUMER_STRATEGY_CONFIG_PROPERTY_KEY = "sebserver.webservice.api.exam.event-handling-strategy";

View file

@ -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 {
@ -29,17 +28,16 @@ public interface SEBClientSessionService {
/** Notify a ping for a certain client connection.
*
* @param connectionToken the connection token
* @param timestamp the ping time-stamp
* @param pingNumber the ping number
* @param instructionConfirm instruction confirm sent by the SEB client or null
* @return SEB instruction if available */
String notifyPing(String connectionToken, long timestamp, int pingNumber, String instructionConfirm);
String notifyPing(String connectionToken, int pingNumber, String instructionConfirm);
/** 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);
void notifyClientEvent(String connectionToken, String jsonBody);
/** This is used to confirm SEB instructions that must be confirmed by the SEB client.
*

View file

@ -77,7 +77,7 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy {
private final BlockingDeque<ClientEventRecord> eventQueue = new LinkedBlockingDeque<>();
private final BlockingDeque<ClientNotification> 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)

View file

@ -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<EventType, Collection<ClientIndicator>> indicatorMapping;
PingIntervalClientIndicator pingIndicator = null;
@ -85,9 +86,9 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
}
}
public final void notifyPing(final long timestamp, final int pingNumber) {
public final void notifyPing(final long timestamp) {
if (this.pingIndicator != null) {
this.pingIndicator.notifyPing(timestamp, pingNumber);
this.pingIndicator.notifyPing(timestamp);
}
}

View file

@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrate
@Lazy
@Service
@WebServiceProfile
@Deprecated
public class EventHandlingStrategyFactory {
private final EventHandlingStrategy eventHandlingStrategy;

View file

@ -378,6 +378,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
@Override
public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) {
// TODO do we really need to synchronize here?
synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) {
return this.examSessionCacheService.getClientConnection(connectionToken);
}

View file

@ -0,0 +1,272 @@
/*
* 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 SEBClientEventBatchService {
private static final Logger log = LoggerFactory.getLogger(SEBClientEventBatchService.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 SEBClientEventBatchService(
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<EventData> eventDataQueue = new LinkedBlockingDeque<>();
private final Collection<EventData> 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.interval:1000}",
initialDelay = 100)
public void processEvents() {
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;
}
final List<ClientEventRecord> 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();
} 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;
}
final ClientConnectionDataInternal clientConnection = this.examSessionCacheService
.getClientConnection(eventData.connectionToken);
if (clientConnection == null) {
log.error("Failed to get ClientConnectionDataInternal for: {}", eventData.connectionToken);
return null;
}
final Pair<NotificationType, String> 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;
}
}
}

View file

@ -0,0 +1,113 @@
/*
* 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.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
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 ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
@Lazy
@Component
@WebServiceProfile
public class SEBClientPingBatchService {
private static final Logger log = LoggerFactory.getLogger(SEBClientPingBatchService.class);
private final ExamSessionCacheService examSessionCacheService;
private final SEBClientInstructionService sebClientInstructionService;
private final Map<String, String> pings = new ConcurrentHashMap<>();
private final Map<String, String> instructions = new ConcurrentHashMap<>();
public SEBClientPingBatchService(
final ExamSessionCacheService examSessionCacheService,
final SEBClientInstructionService sebClientInstructionService) {
this.examSessionCacheService = examSessionCacheService;
this.sebClientInstructionService = sebClientInstructionService;
}
@Scheduled(fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}")
public void processPings() {
if (this.pings.isEmpty()) {
return;
}
final int size = this.pings.size();
if (size > 1000) {
log.warn("----> There are more then 1000 SEB client logs in the waiting queue: {}", size);
}
try {
final Set<String> connections = new HashSet<>(this.pings.keySet());
connections.stream().forEach(cid -> processPing(
cid,
this.pings.remove(cid),
Utils.getMillisecondsNow()));
} catch (final Exception e) {
log.error("Failed to process SEB pings from pingDataQueue: ", e);
}
}
public final String notifyPing(
final String connectionToken,
final String instructionConfirm) {
if (instructionConfirm != null) {
this.pings.put(connectionToken, instructionConfirm);
} else if (!this.pings.containsKey(connectionToken)) {
this.pings.put(connectionToken, StringUtils.EMPTY);
}
return this.instructions.remove(connectionToken);
}
private void processPing(
final String connectionToken,
final String instructionConfirm,
final long timestamp) {
if (connectionToken == null) {
return;
}
final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService
.getClientConnection(connectionToken);
if (activeClientConnection != null) {
activeClientConnection.notifyPing(timestamp);
} else {
log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken);
}
if (instructionConfirm != StringUtils.EMPTY) {
this.sebClientInstructionService.confirmInstructionDone(connectionToken, instructionConfirm);
}
final String instructionJSON = this.sebClientInstructionService.getInstructionJSON(connectionToken);
if (instructionJSON != null) {
this.instructions.put(connectionToken, instructionJSON);
}
}
}

View file

@ -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.SEBClientEventBatchService.EventData;
@Lazy
@Service
@ -44,33 +42,34 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
private final ClientConnectionDAO clientConnectionDAO;
private final ExamSessionService examSessionService;
private final ExamSessionCacheService examSessionCacheService;
private final EventHandlingStrategy eventHandlingStrategy;
private final SEBClientEventBatchService sebClientEventBatchStore;
private final SEBClientInstructionService sebInstructionService;
private final ClientIndicatorFactory clientIndicatorFactory;
private final InternalClientConnectionDataFactory internalClientConnectionDataFactory;
private final SecurityKeyService securityKeyService;
private final SEBClientVersionService sebClientVersionService;
private final SEBClientPingBatchService sebClientPingService;
public SEBClientSessionServiceImpl(
final ClientConnectionDAO clientConnectionDAO,
final ExamSessionService examSessionService,
final EventHandlingStrategyFactory eventHandlingStrategyFactory,
final SEBClientEventBatchService sebClientEventBatchStore,
final SEBClientInstructionService sebInstructionService,
final ClientIndicatorFactory clientIndicatorFactory,
final InternalClientConnectionDataFactory internalClientConnectionDataFactory,
final SecurityKeyService securityKeyService,
final SEBClientVersionService sebClientVersionService) {
final SEBClientVersionService sebClientVersionService,
final SEBClientPingBatchService sebClientPingService) {
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;
this.securityKeyService = securityKeyService;
this.sebClientVersionService = sebClientVersionService;
this.sebClientPingService = sebClientPingService;
}
@Override
@ -113,46 +112,15 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
@Override
public String notifyPing(
final String connectionToken,
final long timestamp,
final int pingNumber,
final String instructionConfirm) {
processPing(connectionToken, timestamp, pingNumber);
if (instructionConfirm != null) {
this.sebInstructionService.confirmInstructionDone(connectionToken, instructionConfirm);
}
return this.sebInstructionService.getInstructionJSON(connectionToken);
return this.sebClientPingService.notifyPing(connectionToken, instructionConfirm);
}
@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
@ -168,39 +136,33 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
this.internalClientConnectionDataFactory.getGroupIds(clientConnection)));
}
private void processPing(final String connectionToken, final long timestamp, final int pingNumber) {
ClientConnectionDataInternal activeClientConnection = null;
synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) {
activeClientConnection = this.examSessionCacheService.getClientConnection(connectionToken);
}
if (activeClientConnection != null) {
activeClientConnection.notifyPing(timestamp, pingNumber);
}
}
private void missingPingUpdate(final ClientConnectionDataInternal connection) {
if (connection.pingIndicator.changeOnIncident()) {
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));
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -27,7 +27,7 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
return this.EMPTY_SET;
}
public final void notifyPing(final long timestamp, final int pingNumber) {
public final void notifyPing(final long timestamp) {
super.currentValue = timestamp;
if (!this.cachingEnabled && super.ditributedIndicatorValueRecordId != null) {

View file

@ -344,10 +344,8 @@ public class DistributedIndicatorValueService implements DisposableBean {
}
/** 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
*
* TODO: we need a better handling strategy here.
* Try to apply a batch update and store the pings in a concurrent hash map **/
* waiting queue to skip further ping updates if all update threads are busy **/
// TODO: we need a better handling strategy here. Try to apply a batch update managed by SEBClientPingBatchService
void updatePingAsync(final Long pingRecord) {
try {
this.indicatorValueUpdateExecutor
@ -363,6 +361,7 @@ public class DistributedIndicatorValueService implements DisposableBean {
/** 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 **/
// TODO: we need a better handling strategy here. Try to apply a batch update managed by SEBClientEventBatchStore
boolean updateIndicatorValueAsync(final Long pk, final Long value) {
try {
this.indicatorValueUpdateExecutor

View file

@ -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) {
}

View file

@ -8,34 +8,28 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@EnableAsync
@Configuration
@WebServiceProfile
//@EnableAsync
//@Configuration
//@WebServiceProfile
@Deprecated
public class ControllerConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(threadPoolTaskExecutor());
configurer.setDefaultTimeout(30000);
}
public AsyncTaskExecutor threadPoolTaskExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("mvc-");
executor.initialize();
return executor;
}
// @Override
// public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
// configurer.setTaskExecutor(threadPoolTaskExecutor());
// configurer.setDefaultTimeout(30000);
// }
//
// public AsyncTaskExecutor threadPoolTaskExecutor() {
// final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// executor.setCorePoolSize(7);
// executor.setMaxPoolSize(42);
// executor.setQueueCapacity(11);
// executor.setThreadNamePrefix("mvc-");
// executor.initialize();
// return executor;
// }
}

View file

@ -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;
@ -329,15 +328,11 @@ public class ExamAPI_V1_Controller {
public void ping(final HttpServletRequest request, final HttpServletResponse response) {
final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN);
final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER);
//final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER);
final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM);
final String instruction = this.sebClientSessionService
.notifyPing(
connectionToken,
Utils.getMillisecondsNow(),
pingNumString != null ? Integer.parseInt(pingNumString) : -1,
instructionConfirm);
.notifyPing(connectionToken, 0, instructionConfirm);
if (instruction == null) {
response.setStatus(HttpStatus.NO_CONTENT.value());
@ -358,10 +353,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) {

View file

@ -322,21 +322,25 @@ public class ExamMonitoringController {
createMonitoringFilter(hiddenStates, hiddenClientGroups))
.getOrThrow();
MonitoringFullPageData monitoringFullPageData;
if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) {
final Collection<RemoteProctoringRoom> proctoringData = this.examProcotringRoomService
.getProctoringCollectingRooms(examId)
.getOrThrow();
return new MonitoringFullPageData(
monitoringFullPageData = new MonitoringFullPageData(
examId,
monitoringSEBConnectionData,
proctoringData);
} else {
return new MonitoringFullPageData(
monitoringFullPageData = new MonitoringFullPageData(
examId,
monitoringSEBConnectionData,
Collections.emptyList());
}
return monitoringFullPageData;
}
@RequestMapping(

View file

@ -6,6 +6,7 @@ server.address=localhost
server.port=8080
server.servlet.context-path=/
server.tomcat.uri-encoding=UTF-8
server.http2.enabled=true
logging.level.ROOT=INFO
logging.level.ch=INFO

View file

@ -40,7 +40,7 @@ sebserver.webservice.internalSecret=${sebserver.password}
### webservice networking
sebserver.webservice.forceMaster=false
sebserver.webservice.distributed=false
sebserver.webservice.distributed.updateInterval=3000
sebserver.webservice.distributed.updateInterval=2000
sebserver.webservice.http.external.scheme=https
sebserver.webservice.http.external.servername=
sebserver.webservice.http.external.port=

View file

@ -15,6 +15,12 @@ server.servlet.context-path=/
# Tomcat
server.tomcat.max-threads=2000
server.tomcat.accept-count=300
server.tomcat.socket.soKeepAlive=true
server.tomcat.socket.performanceConnectionTime=1
server.tomcat.socket.performanceLatency=2
server.tomcat.socket.performanceBandwidth=0
server.tomcat.keepAliveTimeout(20000);
server.tomcat.maxKeepAliveRequests(3000);
server.tomcat.uri-encoding=UTF-8
### encoding

View file

@ -34,7 +34,7 @@
<ttl unit="hours">24</ttl>
</expiry>
<resources>
<heap unit="entries">3000</heap>
<heap unit="entries">100000</heap>
</resources>
</cache>

View file

@ -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

View file

@ -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.SEBClientEventBatchService;
@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 SEBClientEventBatchService 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<ClientEventRecord> 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<ClientEventRecord> events = this.clientEventRecordMapper
.selectByExample()
.build()

View file

@ -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.SEBClientEventBatchService;
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 SEBClientEventBatchService 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, "<top> some error"));
writeValueAsString(
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<top> 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, "<vip> some error"));
writeValueAsString(
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<vip> 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, "<vip> some error"));
writeValueAsString(
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<vip> 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, "<vip> some info other"));
writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0,
"<vip> 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, "<battery> some info other"));
writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0,
"<battery> 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, "<battery> some info other"));
writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0,
"<battery> some info other")));
waitForExecutor();
assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS));