Merge remote-tracking branch 'origin/newSEBEventStoreStrategy' into development
This commit is contained in:
commit
a57e371580
30 changed files with 566 additions and 211 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrate
|
|||
@Lazy
|
||||
@Service
|
||||
@WebServiceProfile
|
||||
@Deprecated
|
||||
public class EventHandlingStrategyFactory {
|
||||
|
||||
private final EventHandlingStrategy eventHandlingStrategy;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<ttl unit="hours">24</ttl>
|
||||
</expiry>
|
||||
<resources>
|
||||
<heap unit="entries">3000</heap>
|
||||
<heap unit="entries">100000</heap>
|
||||
</resources>
|
||||
</cache>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
Loading…
Reference in a new issue