SEBSERV-136 SEBSERV-147 implementation and integration tests

This commit is contained in:
anhefti 2020-11-24 13:48:29 +01:00
parent 696a85a1fe
commit d3a372cfac
28 changed files with 569 additions and 77 deletions

View file

@ -165,6 +165,7 @@ public final class API {
public static final String EXAM_MONITORING_ENDPOINT = "/monitoring";
public static final String EXAM_MONITORING_INSTRUCTION_ENDPOINT = "/instruction";
public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification";
public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection";
public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states";
public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT =

View file

@ -14,15 +14,18 @@ import java.util.List;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientConnectionData {
public static final String ATTR_CLIENT_CONNECTION = "clientConnection";
public static final String ATTR_INDICATOR_VALUE = "indicatorValues";
public static final String ATTR_MISSING_PING = "missingPing";
public static final String ATTR_PENDING_NOTIFICATION = "pendingNotification";
@JsonProperty(ATTR_CLIENT_CONNECTION)
public final ClientConnection clientConnection;
@ -56,6 +59,11 @@ public class ClientConnectionData {
return this.missingPing;
}
@JsonProperty(ATTR_PENDING_NOTIFICATION)
public Boolean pendingNotification() {
return false;
}
@JsonIgnore
public Long getConnectionId() {
return this.clientConnection.id;

View file

@ -182,7 +182,7 @@ public class MonitoringClientConnection implements TemplateComposer {
final RestCall<ClientConnectionData>.RestCallBuilder getConnectionData =
restService.getBuilder(GetClientConnectionData.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
.withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
final ClientConnectionData connectionData = getConnectionData

View file

@ -181,7 +181,7 @@ public class MonitoringRunningExam implements TemplateComposer {
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCall =
restService.getBuilder(GetClientConnectionDataList.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId());
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId());
final ClientConnectionTable clientTable = new ClientConnectionTable(
this.pageService,

View file

@ -34,7 +34,7 @@ public class DisableClientConnection extends RestCall<String> {
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT);
}

View file

@ -35,7 +35,7 @@ public class GetClientConnectionData extends RestCall<ClientConnectionData> {
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT +
API.MODEL_ID_VAR_PATH_SEGMENT +
API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT);
}

View file

@ -37,7 +37,7 @@ public class GetClientConnectionDataList extends RestCall<Collection<ClientConne
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT);
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT);
}
}

View file

@ -34,7 +34,7 @@ public class PropagateInstruction extends RestCall<String> {
HttpMethod.POST,
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_MONITORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_MONITORING_INSTRUCTION_ENDPOINT);
}

View file

@ -93,7 +93,7 @@ public class InstructionProcessor {
null);
processInstruction(() -> this.restService.getBuilder(PropagateInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examId))
.withURIVariable(API.PARAM_PARENT_MODEL_ID, String.valueOf(examId))
.withBody(clientInstruction)
.call()
.getOrThrow(),
@ -124,7 +124,7 @@ public class InstructionProcessor {
}
processInstruction(() -> this.restService.getBuilder(DisableClientConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examId))
.withURIVariable(API.PARAM_PARENT_MODEL_ID, String.valueOf(examId))
.withFormParam(
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR))

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
@ -26,4 +27,8 @@ public interface ClientEventDAO extends EntityDAO<ClientEvent, ClientEvent> {
FilterMap filterMap,
Predicate<ExtendedClientEvent> predicate);
Result<List<ClientEvent>> getPendingNotifications(Long clientConnectionId);
Result<ClientEvent> confirmPendingNotification(Long notificationId, Long clientConnectionId);
}

View file

@ -13,6 +13,7 @@ import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
@ -31,6 +32,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
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.ClientEventExtensionMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtensionMapper.ConnectionEventJoinRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
@ -183,6 +185,43 @@ public class ClientEventDAOImpl implements ClientEventDAO {
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<List<ClientEvent>> getPendingNotifications(final Long clientConnectionId) {
return Result.tryCatch(() -> this.clientEventRecordMapper.selectByExample()
.where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(clientConnectionId))
.and(ClientEventRecordDynamicSqlSupport.type, isEqualTo(EventType.NOTIFICATION.id))
.build()
.execute()
.stream()
.map(ClientEventDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()));
}
@Override
@Transactional
public Result<ClientEvent> confirmPendingNotification(final Long notificationId, final Long clientConnectionId) {
return Result.tryCatch(() -> {
final Long pk = this.clientEventRecordMapper.selectIdsByExample()
.where(ClientEventRecordDynamicSqlSupport.id, isEqualTo(notificationId))
.and(ClientEventRecordDynamicSqlSupport.type, isEqualTo(EventType.NOTIFICATION.id))
.build()
.execute()
.stream().collect(Utils.toSingleton());
this.clientEventRecordMapper.updateByPrimaryKeySelective(new ClientEventRecord(
pk,
null,
EventType.NOTIFICATION_CONFIRMED.id,
null, null, null, null));
return this.clientEventRecordMapper.selectByPrimaryKey(pk);
})
.flatMap(ClientEventDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ClientEvent> createNew(final ClientEvent data) {

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2020 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;
@FunctionalInterface
public interface PendingNotificationIndication {
boolean notifictionPending();
}

View file

@ -25,7 +25,7 @@ import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
*
* SEB instructions are sent as response of a SEB Ping on a active SEB Connection
* If there is an instruction in the queue for a specified SEB Client. */
public interface SEBInstructionService {
public interface SEBClientInstructionService {
/** Get the underling WebserviceInfo
*

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2020 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;
import java.util.List;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.util.Result;
/** Service to maintain SEB Client notifications. */
public interface SEBClientNotificationService {
public static final String CACHE_CLIENT_NOTIFICATION = "LIENT_NOTIFICATION_CACHE";
/** Indicates whether the client connection with the specified identifier has any
* pending notification or not. Pending means a non-confirmed notification
*
* @param clientConnectionId the client connection identifier
* @return true if there is any pending notification for the specified client connection */
@Cacheable(
cacheNames = CACHE_CLIENT_NOTIFICATION,
key = "#clientConnectionId",
condition = "#result != null && #result")
Boolean hasAnyPendingNotification(Long clientConnectionId);
Result<List<ClientEvent>> getPendingNotifications(Long clientConnectionId);
@CacheEvict(
cacheNames = CACHE_CLIENT_NOTIFICATION,
key = "#clientConnectionId")
Result<ClientEvent> confirmPendingNotification(Long notificationId, final Long clientConnectionId);
@CacheEvict(
cacheNames = CACHE_CLIENT_NOTIFICATION,
key = "#clientConnectionId")
default void notifyNewNotification(final Long clientConnectionId) {
}
}

View file

@ -8,11 +8,9 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator;
@ -21,7 +19,6 @@ public abstract class AbstractClientIndicator implements ClientIndicator {
protected Long examId;
protected Long connectionId;
protected boolean cachingEnabled;
protected String[] tags;
protected double currentValue = Double.NaN;
@ -34,15 +31,6 @@ public abstract class AbstractClientIndicator implements ClientIndicator {
this.examId = (indicatorDefinition != null) ? indicatorDefinition.examId : null;
this.connectionId = connectionId;
this.cachingEnabled = cachingEnabled;
if (indicatorDefinition == null || indicatorDefinition.tags == null) {
this.tags = null;
} else {
this.tags = StringUtils.split(indicatorDefinition.tags, Constants.COMMA);
for (int i = 0; i < this.tags.length; i++) {
this.tags[i] = Constants.ANGLE_BRACE_OPEN + this.tags[i] + Constants.ANGLE_BRACE_CLOSE;
}
}
}
@Override
@ -55,6 +43,10 @@ public abstract class AbstractClientIndicator implements ClientIndicator {
return this.connectionId;
}
public void reset() {
this.currentValue = Double.NaN;
}
@Override
public double getValue() {
if (Double.isNaN(this.currentValue) || !this.cachingEnabled) {

View file

@ -17,6 +17,12 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.mybatis.dynamic.sql.SqlCriterion;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
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;
@ -28,6 +34,7 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractClientIndic
private final Set<EventType> observed;
private final List<Integer> eventTypeIds;
private final ClientEventRecordMapper clientEventRecordMapper;
protected String[] tags;
protected AbstractLogLevelCountIndicator(
final ClientEventRecordMapper clientEventRecordMapper,
@ -38,6 +45,20 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractClientIndic
this.eventTypeIds = Utils.immutableListOf(Arrays.stream(eventTypes)
.map(et -> et.id)
.collect(Collectors.toList()));
}
@Override
public void init(final Indicator indicatorDefinition, final Long connectionId, final boolean cachingEnabled) {
super.init(indicatorDefinition, connectionId, cachingEnabled);
if (indicatorDefinition == null || indicatorDefinition.tags == null) {
this.tags = null;
} else {
this.tags = StringUtils.split(indicatorDefinition.tags, Constants.COMMA);
for (int i = 0; i < this.tags.length; i++) {
this.tags[i] = Constants.ANGLE_BRACE_OPEN + this.tags[i] + Constants.ANGLE_BRACE_CLOSE;
}
}
}
@Override
@ -47,12 +68,40 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractClientIndic
.where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(this.connectionId))
.and(ClientEventRecordDynamicSqlSupport.type, isIn(this.eventTypeIds))
.and(ClientEventRecordDynamicSqlSupport.serverTime, isLessThan(timestamp))
.and(
ClientEventRecordDynamicSqlSupport.text,
isLikeWhenPresent(getfirstTagSQL()),
getSubTagSQL())
.build()
.execute();
return errors.doubleValue();
}
private String getfirstTagSQL() {
if (this.tags == null || this.tags.length == 0) {
return null;
}
return Utils.toSQLWildcard(this.tags[0]);
}
@SuppressWarnings("unchecked")
private SqlCriterion<String>[] getSubTagSQL() {
if (this.tags == null || this.tags.length == 0 || this.tags.length == 1) {
return new SqlCriterion[0];
}
final SqlCriterion<String>[] result = new SqlCriterion[this.tags.length - 1];
for (int i = 1; i < this.tags.length; i++) {
result[i - 1] = SqlBuilder.or(
ClientEventRecordDynamicSqlSupport.text,
isLike(Utils.toSQLWildcard(this.tags[1])));
}
return result;
}
@Override
public void notifyValueChange(final ClientEvent event) {
if (this.tags == null || this.tags.length == 0) {

View file

@ -23,6 +23,7 @@ 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.EventType;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.PendingNotificationIndication;
public class ClientConnectionDataInternal extends ClientConnectionData {
@ -31,12 +32,15 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
final EnumMap<EventType, Collection<ClientIndicator>> indicatorMapping;
PingIntervalClientIndicator pingIndicator = null;
private final PendingNotificationIndication pendingNotificationIndication;
protected ClientConnectionDataInternal(
final ClientConnection clientConnection,
final PendingNotificationIndication pendingNotificationIndication,
final List<ClientIndicator> clientIndicators) {
super(clientConnection, clientIndicators);
this.pendingNotificationIndication = pendingNotificationIndication;
this.indicatorMapping = new EnumMap<>(EventType.class);
for (final ClientIndicator clientIndicator : clientIndicators) {
@ -62,17 +66,21 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
}
Collection<ClientIndicator> getIndicatorMapping(final EventType eventType) {
if (!this.indicatorMapping.containsKey(eventType)) {
return Collections.emptyList();
}
return this.indicatorMapping.get(eventType);
return this.indicatorMapping.getOrDefault(
eventType,
Collections.emptyList());
}
@Override
@JsonProperty("missingPing")
@JsonProperty(ATTR_MISSING_PING)
public Boolean getMissingPing() {
return this.pingIndicator.missingPing;
}
@Override
@JsonProperty(ATTR_PENDING_NOTIFICATION)
public Boolean pendingNotification() {
return this.pendingNotificationIndication.notifictionPending();
}
}

View file

@ -34,7 +34,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
@Lazy
@Service
@ -45,14 +45,14 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
private final ClientConnectionDAO clientConnectionDAO;
private final SEBInstructionService sebInstructionService;
private final SEBClientInstructionService sebInstructionService;
private final ExamAdminService examAdminService;
private final ExamSessionService examSessionService;
public ExamProctoringRoomServiceImpl(
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
final ClientConnectionDAO clientConnectionDAO,
final SEBInstructionService sebInstructionService,
final SEBClientInstructionService sebInstructionService,
final ExamAdminService examAdminService,
final ExamSessionService examSessionService) {

View file

@ -54,7 +54,7 @@ public class ExamSessionCacheService {
private final ExamDAO examDAO;
private final ClientConnectionDAO clientConnectionDAO;
private final ClientIndicatorFactory clientIndicatorFactory;
private final InternalClientConnectionDataFactory internalClientConnectionDataFactory;
private final ExamConfigService sebExamConfigService;
private final ClientEventRecordMapper clientEventRecordMapper;
private final ExamUpdateHandler examUpdateHandler;
@ -62,7 +62,7 @@ public class ExamSessionCacheService {
protected ExamSessionCacheService(
final ExamDAO examDAO,
final ClientConnectionDAO clientConnectionDAO,
final ClientIndicatorFactory clientIndicatorFactory,
final InternalClientConnectionDataFactory internalClientConnectionDataFactory,
final ExamConfigService sebExamConfigService,
final ClientEventRecordMapper clientEventRecordMapper,
final ExamUpdateHandler examUpdateHandler,
@ -70,7 +70,7 @@ public class ExamSessionCacheService {
this.examDAO = examDAO;
this.clientConnectionDAO = clientConnectionDAO;
this.clientIndicatorFactory = clientIndicatorFactory;
this.internalClientConnectionDataFactory = internalClientConnectionDataFactory;
this.sebExamConfigService = sebExamConfigService;
this.clientEventRecordMapper = clientEventRecordMapper;
this.examUpdateHandler = examUpdateHandler;
@ -146,9 +146,7 @@ public class ExamSessionCacheService {
if (clientConnection == null) {
return null;
} else {
return new ClientConnectionDataInternal(
clientConnection,
this.clientIndicatorFactory.createFor(clientConnection));
return this.internalClientConnectionDataFactory.createClientConnectionData(clientConnection);
}
}
@ -239,7 +237,8 @@ public class ExamSessionCacheService {
.byConnectionToken(connectionToken);
if (result.hasError()) {
log.error("Failed to find/load ClientConnection with connectionToken {}", connectionToken, result.getError());
log.error("Failed to find/load ClientConnection with connectionToken {}", connectionToken,
result.getError());
return null;
}
return result.get();

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 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 org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
@Lazy
@Service
@WebServiceProfile
public class InternalClientConnectionDataFactory {
private final ClientIndicatorFactory clientIndicatorFactory;
private final SEBClientNotificationService sebClientNotificationService;
public InternalClientConnectionDataFactory(
final ClientIndicatorFactory clientIndicatorFactory,
final SEBClientNotificationService sebClientNotificationService) {
this.clientIndicatorFactory = clientIndicatorFactory;
this.sebClientNotificationService = sebClientNotificationService;
}
public ClientConnectionDataInternal createClientConnectionData(final ClientConnection clientConnection) {
return new ClientConnectionDataInternal(
clientConnection,
() -> this.sebClientNotificationService
.hasAnyPendingNotification(clientConnection.id),
this.clientIndicatorFactory.createFor(clientConnection));
}
}

View file

@ -28,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
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.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@ -39,7 +40,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrate
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.PingHandlingStrategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException;
@Lazy
@ -63,7 +65,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
private final ClientConnectionDAO clientConnectionDAO;
private final PingHandlingStrategy pingHandlingStrategy;
private final SEBClientConfigDAO sebClientConfigDAO;
private final SEBInstructionService sebInstructionService;
private final SEBClientInstructionService sebInstructionService;
private final SEBClientNotificationService sebClientNotificationService;
private final WebserviceInfo webserviceInfo;
private final ExamAdminService examAdminService;
@ -72,7 +75,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
final EventHandlingStrategyFactory eventHandlingStrategyFactory,
final PingHandlingStrategyFactory pingHandlingStrategyFactory,
final SEBClientConfigDAO sebClientConfigDAO,
final SEBInstructionService sebInstructionService,
final SEBClientInstructionService sebInstructionService,
final SEBClientNotificationService sebClientNotificationService,
final ExamAdminService examAdminService) {
this.examSessionService = examSessionService;
@ -83,6 +87,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
this.eventHandlingStrategy = eventHandlingStrategyFactory.get();
this.sebClientConfigDAO = sebClientConfigDAO;
this.sebInstructionService = sebInstructionService;
this.sebClientNotificationService = sebClientNotificationService;
this.webserviceInfo = sebInstructionService.getWebserviceInfo();
this.examAdminService = examAdminService;
}
@ -519,9 +524,14 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
event,
activeClientConnection.getConnectionId()));
// update indicators
activeClientConnection.getIndicatorMapping(event.eventType)
.forEach(indicator -> indicator.notifyValueChange(event));
if (event.eventType == EventType.NOTIFICATION || event.eventType == EventType.NOTIFICATION_CONFIRMED) {
// notify notification service
this.sebClientNotificationService.notifyNewNotification(activeClientConnection.getConnectionId());
} else {
// update indicators
activeClientConnection.getIndicatorMapping(event.eventType)
.forEach(indicator -> indicator.notifyValueChange(event));
}
} else {
log.warn("No active ClientConnection found for connectionToken: {}", connectionToken);
}

View file

@ -35,14 +35,14 @@ import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientInstructionRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientInstructionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
@Lazy
@Service
@WebServiceProfile
public class SEBInstructionServiceImpl implements SEBInstructionService {
public class SEBClientInstructionServiceImpl implements SEBClientInstructionService {
private static final Logger log = LoggerFactory.getLogger(SEBInstructionServiceImpl.class);
private static final Logger log = LoggerFactory.getLogger(SEBClientInstructionServiceImpl.class);
private static final int INSTRUCTION_QUEUE_MAX_SIZE = 10;
private static final String JSON_INST = "instruction";
@ -57,7 +57,7 @@ public class SEBInstructionServiceImpl implements SEBInstructionService {
private long lastRefresh = 0;
public SEBInstructionServiceImpl(
public SEBClientInstructionServiceImpl(
final WebserviceInfo webserviceInfo,
final ClientConnectionDAO clientConnectionDAO,
final ClientInstructionDAO clientInstructionDAO,

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2020 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.Collections;
import java.util.List;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
@Lazy
@Service
@WebServiceProfile
public class SEBClientNotificationServiceImpl implements SEBClientNotificationService {
private final ClientEventDAO clientEventDAO;
public SEBClientNotificationServiceImpl(final ClientEventDAO clientEventDAO) {
this.clientEventDAO = clientEventDAO;
}
@Override
public Boolean hasAnyPendingNotification(final Long clientConnectionId) {
return !getPendingNotifications(clientConnectionId)
.getOr(Collections.emptyList())
.isEmpty();
}
@Override
public Result<List<ClientEvent>> getPendingNotifications(final Long clientConnectionId) {
return this.clientEventDAO.getPendingNotifications(clientConnectionId);
}
@Override
public Result<ClientEvent> confirmPendingNotification(final Long notificatioId, final Long clientConnectionId) {
return this.clientEventDAO.confirmPendingNotification(notificatioId, clientConnectionId);
}
}

View file

@ -41,6 +41,7 @@ import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@ -51,7 +52,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
@WebServiceProfile
@RestController
@ -62,21 +64,24 @@ public class ExamMonitoringController {
private final SEBClientConnectionService sebClientConnectionService;
private final ExamSessionService examSessionService;
private final SEBInstructionService sebInstructionService;
private final SEBClientInstructionService sebClientInstructionService;
private final AuthorizationService authorization;
private final PaginationService paginationService;
private final SEBClientNotificationService sebClientNotificationService;
public ExamMonitoringController(
final SEBClientConnectionService sebClientConnectionService,
final SEBInstructionService sebInstructionService,
final SEBClientInstructionService sebClientInstructionService,
final AuthorizationService authorization,
final PaginationService paginationService) {
final PaginationService paginationService,
final SEBClientNotificationService sebClientNotificationService) {
this.sebClientConnectionService = sebClientConnectionService;
this.examSessionService = sebClientConnectionService.getExamSessionService();
this.sebInstructionService = sebInstructionService;
this.sebClientInstructionService = sebClientInstructionService;
this.authorization = authorization;
this.paginationService = paginationService;
this.sebClientNotificationService = sebClientNotificationService;
}
/** This is called by Spring to initialize the WebDataBinder and is used here to
@ -150,7 +155,7 @@ public class ExamMonitoringController {
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT,
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ -159,7 +164,7 @@ public class ExamMonitoringController {
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long examId,
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates) {
// check overall privilege
@ -194,7 +199,8 @@ public class ExamMonitoringController {
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ -203,7 +209,7 @@ public class ExamMonitoringController {
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long examId,
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
// check overall privilege
@ -226,7 +232,8 @@ public class ExamMonitoringController {
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.EXAM_MONITORING_INSTRUCTION_ENDPOINT,
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_INSTRUCTION_ENDPOINT,
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void registerInstruction(
@ -234,14 +241,64 @@ public class ExamMonitoringController {
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long examId,
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@Valid @RequestBody final ClientInstruction clientInstruction) {
this.sebInstructionService.registerInstruction(clientInstruction);
this.sebClientInstructionService.registerInstruction(clientInstruction);
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT,
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_NOTIFICATION_ENDPOINT +
API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Collection<ClientEvent> pendingNotifications(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
final ClientConnectionData connection = getConnectionDataForSingleConnection(
institutionId,
examId,
connectionToken);
return this.sebClientNotificationService
.getPendingNotifications(connection.getConnectionId())
.getOrThrow();
}
@RequestMapping(
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_NOTIFICATION_ENDPOINT +
API.MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void confirmNotification(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long notificationId,
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
final ClientConnectionData connection = getConnectionDataForSingleConnection(
institutionId,
examId,
connectionToken);
this.sebClientNotificationService.confirmPendingNotification(
notificationId,
connection.getConnectionId())
.getOrThrow();
}
@RequestMapping(
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT,
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void disableConnection(
@ -249,7 +306,7 @@ public class ExamMonitoringController {
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long examId,
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@RequestParam(
name = Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
required = true) final String connectionToken) {
@ -266,7 +323,6 @@ public class ExamMonitoringController {
.disableConnection(connectionToken, institutionId)
.getOrThrow();
}
}
private boolean hasRunningExamPrivilege(final Long examId, final Long institution) {

View file

@ -44,7 +44,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
@WebServiceProfile
@RestController
@ -55,14 +55,14 @@ public class ExamProctoringController {
private final ExamProctoringRoomService examProcotringRoomService;
private final ExamAdminService examAdminService;
private final SEBInstructionService sebInstructionService;
private final SEBClientInstructionService sebInstructionService;
private final AuthorizationService authorization;
private final ExamSessionService examSessionService;
public ExamProctoringController(
final ExamProctoringRoomService examProcotringRoomService,
final ExamAdminService examAdminService,
final SEBInstructionService sebInstructionService,
final SEBClientInstructionService sebInstructionService,
final AuthorizationService authorization,
final ExamSessionService examSessionService) {

View file

@ -918,8 +918,8 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
final Result<Indicator> newIndicatorResult = restService
.getBuilder(NewIndicator.class)
.withFormParam(Domain.INDICATOR.ATTR_EXAM_ID, exam.getModelId())
.withFormParam(Domain.INDICATOR.ATTR_NAME, "Ping")
.withFormParam(Domain.INDICATOR.ATTR_TYPE, IndicatorType.LAST_PING.name)
.withFormParam(Domain.INDICATOR.ATTR_NAME, "Errors")
.withFormParam(Domain.INDICATOR.ATTR_TYPE, IndicatorType.ERROR_COUNT.name)
.withFormParam(Domain.INDICATOR.ATTR_COLOR, "000001")
.call();
@ -927,7 +927,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
assertFalse(newIndicatorResult.hasError());
final Indicator newIndicator = newIndicatorResult.get();
assertEquals("Ping", newIndicator.name);
assertEquals("Errors", newIndicator.name);
assertEquals("000001", newIndicator.defaultColor);
final Indicator indicatorToSave = new Indicator(
@ -951,7 +951,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
assertFalse(savedIndicatorResult.hasError());
final Indicator savedIndicator = savedIndicatorResult.get();
assertEquals("Ping", savedIndicator.name);
assertEquals("Errors", savedIndicator.name);
assertEquals("000001", savedIndicator.defaultColor);
final Collection<Threshold> thresholds = savedIndicator.getThresholds();
assertFalse(thresholds.isEmpty());
@ -2089,7 +2089,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
// get SEB connections
Result<Collection<ClientConnectionData>> connectionsCall =
restService.getBuilder(GetClientConnectionDataList.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
.call();
assertNotNull(connectionsCall);
@ -2122,7 +2122,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
// send quit instruction
connectionsCall =
restService.getBuilder(GetClientConnectionDataList.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
.call();
assertNotNull(connectionsCall);
@ -2141,7 +2141,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
null);
final Result<String> instructionCall = restService.getBuilder(PropagateInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.id))
.withURIVariable(API.PARAM_PARENT_MODEL_ID, String.valueOf(exam.id))
.withBody(clientInstruction)
.call();
@ -2155,7 +2155,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
connectionsCall =
restService.getBuilder(GetClientConnectionDataList.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
.call();
assertNotNull(connectionsCall);
@ -2171,7 +2171,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
// disable connection
final Result<String> disableCall = restService.getBuilder(DisableClientConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
.withFormParam(
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
conData.clientConnection.connectionToken)
@ -2180,7 +2180,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
assertFalse(disableCall.hasError());
connectionsCall =
restService.getBuilder(GetClientConnectionDataList.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
.call();
assertNotNull(connectionsCall);

View file

@ -0,0 +1,163 @@
/*
* Copyright (c) 2020 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.integration.services;
import static org.junit.Assert.*;
import java.util.Collection;
import java.util.Optional;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.jdbc.Sql;
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;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
import ch.ethz.seb.sebserver.webservice.integration.api.admin.AdministrationAPIIntegrationTester;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.AbstractLogLevelCountIndicator;
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql", "classpath:data-test-additional.sql" })
public class ClientEventServiceTest extends AdministrationAPIIntegrationTester {
@Autowired
private ClientConnectionDAO clientConnectionDAO;
@Autowired
private ClientEventDAO clientEventDAO;
@Autowired
private SEBClientConnectionService sebClientConnectionService;
@Test
public void testCreateLogEvents() {
final ClientConnection connection = this.clientConnectionDAO
.createNew(new ClientConnection(null, 1L, 2L, ConnectionStatus.ACTIVE, "token", "userId", "", "", 1L,
null, false))
.getOrThrow();
assertNotNull(connection.id);
this.clientEventDAO
.createNew(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "text"))
.getOrThrow();
final Collection<ClientEvent> allEvents = this.clientEventDAO.allMatching(new FilterMap()).getOrThrow();
assertNotNull(allEvents);
assertFalse(allEvents.isEmpty());
final ClientEvent event = allEvents.iterator().next();
assertNotNull(event);
assertEquals("text", event.text);
}
@Test
public void testErrorLogCountIndicator() {
final ClientConnection connection = this.clientConnectionDAO
.createNew(new ClientConnection(null, 1L, 2L, ConnectionStatus.ACTIVE, "token1", "userId", "", "", 1L,
null, false))
.getOrThrow();
assertNotNull(connection.id);
final ClientConnectionData connectionData =
this.sebClientConnectionService.getExamSessionService().getConnectionData("token1")
.getOrThrow();
assertNotNull(connectionData);
final Optional<? extends IndicatorValue> findFirst = connectionData.indicatorValues
.stream()
.filter(indicator -> indicator.getType() == IndicatorType.ERROR_COUNT)
.findFirst();
assertTrue(findFirst.isPresent());
final IndicatorValue clientIndicator = findFirst.get();
assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator));
this.sebClientConnectionService.notifyClientEvent(
"token1",
new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error"));
assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator));
this.sebClientConnectionService.notifyClientEvent(
"token1",
new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error"));
assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator));
// test reset indicator value and load it from persistent storage
((AbstractLogLevelCountIndicator) clientIndicator).reset();
assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator));
}
@Test
public void testInfoLogWithTagCountIndicator() {
final ClientConnection connection = this.clientConnectionDAO
.createNew(new ClientConnection(null, 1L, 2L, ConnectionStatus.ACTIVE, "token2", "userId", "", "", 1L,
null, false))
.getOrThrow();
assertNotNull(connection.id);
final ClientConnectionData connectionData =
this.sebClientConnectionService.getExamSessionService().getConnectionData("token2")
.getOrThrow();
assertNotNull(connectionData);
final Optional<? extends IndicatorValue> findFirst = connectionData.indicatorValues
.stream()
.filter(indicator -> indicator.getType() == IndicatorType.INFO_COUNT)
.findFirst();
assertTrue(findFirst.isPresent());
final IndicatorValue clientIndicator = findFirst.get();
assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator));
this.sebClientConnectionService.notifyClientEvent(
"token2",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"));
assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator));
this.sebClientConnectionService.notifyClientEvent(
"token2",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<top> some error"));
assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator));
this.sebClientConnectionService.notifyClientEvent(
"token2",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"));
assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator));
this.sebClientConnectionService.notifyClientEvent(
"token2",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<vip> some error"));
assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator));
this.sebClientConnectionService.notifyClientEvent(
"token2",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"));
assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator));
this.sebClientConnectionService.notifyClientEvent(
"token2",
new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "<vip> some error"));
// test reset indicator value and load it from persistent storage
((AbstractLogLevelCountIndicator) clientIndicator).reset();
assertEquals("3", IndicatorValue.getDisplayValue(clientIndicator));
}
}

View file

@ -12,13 +12,18 @@ INSERT IGNORE INTO exam VALUES
;
INSERT IGNORE INTO indicator VALUES
(1, 2, 'LAST_PING', 'Ping', 'dcdcdc', null, null)
(1, 2, 'LAST_PING', 'Ping', 'dcdcdc', null, null),
(2, 2, 'ERROR_COUNT', 'errors', 'dcdcdc', null, null),
(3, 2, 'INFO_COUNT', 'errors <vip,top>', 'dcdcdc', null, 'vip,top')
;
INSERT IGNORE INTO threshold VALUES
(1, 1, 1000.0000, '22b14c', null),
(2, 1, 2000.0000, 'ff7e00', null),
(3, 1, 5000.0000, 'ed1c24', null)
(3, 1, 5000.0000, 'ed1c24', null),
(4, 2, 1, '22b14c', null),
(5, 2, 2, 'ff7e00', null),
(6, 2, 5, 'ed1c24', null)
;
INSERT IGNORE INTO view VALUES