SEBSERV-136 SEBSERV-147 implementation and integration tests
This commit is contained in:
parent
696a85a1fe
commit
d3a372cfac
28 changed files with 569 additions and 77 deletions
|
@ -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 =
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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
|
||||
*
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue